Model Update is Stripping Event Handlers

Hi Team,

We have been long encountering strange issues with client side events that spontaneously unwire. Until recently we have been metaphorically jiggling the antenna until these issues went away, however recently we had one instance that was significantly blocking our progress and I have been able to identify the cause of the issues in some detail.

In Brief:

The problem is occurring when the following circumstances exist:

  • There is a field editor on the page, the editor is changing the model ‘Client’
  • There is a template on the page, this is bound to the model ‘Client’
  • There is an element inside the template, this element has an event wired up on click.
  • Any time the model ‘Client’ changes, events within the template spontaneously unwire.
  • Canceling model changes does not resolve the issue.

More detail:

When the page first loads, the a#settingsanchor selector is bound:

If you change the model, either by changing a field in the editor or in the console with this:

skuid.model.getModel('Client').updateRow(skuid.model.getModel('Client').getFirstRow(), {"Dependants": "Some"});

The events are gone:

By delegating the event to an element outside the template, the events are not unwired. The following snippet of code alerts three times on click at page load, and only once after the model is updated:

(function (skuid) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; skuid.$(function () {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var $ = skuid.$;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Stops working when model updated:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $("#settingsanchor").click(function () {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; alert("Handler on object called.");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Stops working when model updated:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $('#navcontainer').on('click', '#settingsanchor', function () {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; alert("Handler on outmost div called.");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Always works:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$(document).on('click', '#settingsanchor', function () {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; alert("Handler on document called.");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });<br>&nbsp;&nbsp;&nbsp; })(skuid);

Thoughts and feedback would be appreciated.

Kind Regards,
Dan Arnison

Hi Dan,

When models are refreshed in Skuid, portions of the DOM are completely removed and then rebuilt.  Your event handlers that are not delegated will be removed along with the elements.  However, your event handler attached to the $(document) will not be removed since this part of the page is never destroyed and rebuilt.  A way to avoid this issue completely would be to use custom field renderers for fields that you would need to attach an event handler to.  Your method of delegating on the body or document element will also work.

Hi Ben,

Thank you for the feedback. Unfortunately the elements we are loosing require a jQueryUI widget, so delegating events up to the document is not feasible, and we really need the elements in a template control which rules out custom field renderers.

I have developed a workaround that involves wrapping the snippet that initialises the autocomplete with an editor, listening for updates, and re-running this code each time this occurs.

The code is reproduced below for the benefit of the community:

(function(skuid){ var $ = skuid.$; cp.modelWireupContainer = function(models, snippet, settings) { var options = { refreshOnChange: false, refreshOnSave: true }; $.extend(options, settings); if(!models.length) models = [models]; snippet = snippet; var editor = new skuid.ui.Editor($('<div>')); models.forEach(function(modelName, ndx){ var model = skuid.model.getModel(modelName); if(!(model == null || model == undefined)) editor.registerModel(model); }); editor.handleChange = function(e){ if(options.refreshOnChange == true) snippet(); }; editor.handleSave = function(totalSuccess){, totalSuccess); if(options.refreshOnSave == true) snippet(); }; editor.handleCancel = function(){ if(options.refreshOnChange == true || options.refreshOnSave == true) snippet(); }; snippet(); }; })(skuid);

This can be used with:

new cp.modelWireupContainer(['modelName'], function(){ $('#element').autocomplete(et cetera); });