Listen for changes against an Object In an array without nesting event listeners

不羁岁月 提交于 2019-12-12 03:38:17

问题


(disclaimer: this question is much simpler than the title suggests!)

I have a re-occuring problem in my program architecture with listening to changes against a model.

I have set-up some collections (much like in Backbone.js) which are just lightweight classes built around an array. The collection is a collection of models. When the user makes changes to a model I keep ending up with dreadful nested events. This is a typical, simple, scenario that I'm trying to avoid:

$("#user-details").on("click", "#save", onSaveClick);

function onSaveClick()
{       
    var givennames = $("#givenname").val(),

    // Get the model from the collection
    var user = users.get($(this).attr("data-id"));

    // set the property
    user.set("GivenNames", givennames);

    // listen for the save event
    $(user).on("save", function(){
        // Alert the view
    });

    user.save();    // save event triggers a "save" event on the model
}

If the same user is saved twice the event gets added/fired multiple times. Is there a better pattern for this?

Should the event be bubbling up through the collection and handled that way perhaps?

Here's an actual example (the one I'm most ashamed of)

$("#user-devices").on("click", ".device button", function(){

            var button = $(this),
                deviceId = $(this).closest(".device").attr("data-id"),
                device = userDevices.get(deviceId);


            $(device).on("activate", function(event, device){

                button.removeClass("btn-danger").addClass("btn-success")

                $("#activation-dialog").modal("show");
            })

            if (device.get("Active") === "1")
            {
                $("#reactivation-warning-dialog").modal("show");
                $("#reactivation-warning-dialog .btn-primary").on("click", function(){
                    device.activate();
                });

            }
            else
            {
                device.activate();  
            }
        });

回答1:


You could just check if the save event is already binded, in that case not bind it again, like this:

// if no 'save' event already binded
if (!$(user).data('events') || !$(user).data('events').save) { 
    // listen for the save event
    $(user).on("save", function(){
        // Alert the view
    });
}

See working example


To put some sugar on top, we can make the "check if event exist" logic into a custom jquery pseudo-selector, that would be defined as follows:

$.expr[':'].hasEvent = function(obj, idx, meta, stack) {
    return ($(obj).data('events') != undefined 
            && $(obj).data('events')[meta[3]] != undefined);
};

Then you can use it this way:

$(user).not(":hasEvent(save)").on("save", function(){
     // Alert the view
});

Working example


UPDATE FOR JQUERY >= 1.8

Starting with jQuery 1.8 there were some changes to the events object which makes my above code not to work, see this excerpt from the jQuery 1.8 release notes:

$(element).data(“events”): In version 1.6, jQuery separated its internal data from the user’s data to prevent name collisions. However, some people were using the internal undocumented “events” data structure so we made it possible to still retrieve that via .data(). This is now removed in 1.8, but you can still get to the events data for debugging purposes via $._data(element, "events"). Note that this is not a supported public interface; the actual data structures may change incompatibly from version to version.

So here I post the jQuery >= 1.8 updated versions for of my above examples:

Checking if the save event is already binded, in that case not bind it again:

// if no 'save' event already binded
if (!$._data(user, 'events') || !$._data(user, 'events').save) { 
    // listen for the save event
    $(user).on("save", function(){
        // Alert the view
    });
}

And the custom jquery pseudo-selector:

$.expr[':'].hasEvent = function(obj, idx, meta, stack) {
    return ($._data(obj, 'events') != undefined 
            && $._data(obj, 'events')[meta[3]] != undefined);
};



回答2:


I agree that the nested listener is a little weird--after all, you're already in a save listener, just for the button click rather than the model object.

I think I'd rewrite the code something like this:

$("#user-devices").on("click", ".device button", function(){

    var button = $(this),
        deviceId = $(this).closest(".device").attr("data-id"),
        device = userDevices.get(deviceId);

    if (device.get("Active") === "1")
    {
        $("#reactivation-warning-dialog").modal("show");
        $("#reactivation-warning-dialog .btn-primary").on("click", device.activate);

    }
    else
    {
        device.activate();
    }
});

//in the device "class"
{ 
   ...
   ...

   activate: function() {
     //persist and stuf.

     //on success...
     this.render();
   }

   render: function() {
      button.removeClass("btn-danger").addClass("btn-success");
      $("#activation-dialog").modal("show");
   }
}


来源:https://stackoverflow.com/questions/13341307/listen-for-changes-against-an-object-in-an-array-without-nesting-event-listeners

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!