2

I'm using Template.rendered to setup a dropdown replacement like so:

Template.productEdit.rendered = function() {
    if( ! this.rendered) {
        $('.ui.dropdown').dropdown();
        this.rendered = true;
    }
};

But how do I re-run this when the DOM mutates? Helpers return new values for the select options, but I don't know where to re-execute my .dropdown()

Ian Jones
  • 1,369
  • 1
  • 10
  • 15
  • Which dropdown plugin is this? I'm guessing it emits some kind of event which you can attach functions to – Suppen Feb 14 '15 at 11:23
  • in which case this is more of a general javascript/jquery question than a meteor question – Suppen Feb 14 '15 at 11:35
  • Nope, it's definitely a Meteor question. Meteor changes the DOM, so I need to trigger the dropdown to reset immediately after the DOM changes. I need a callback from Meteor to in which to trigger the dropdown plugin to be reset. – Ian Jones Feb 14 '15 at 12:53

3 Answers3

2

I think you don't want this to run before the whole DOM has rendered, or else the event handler will run on EVERY element being inserted:

var rendered = false;
Template.productEdit.rendered = function() {rendered: true};

To avoid rerunning this on elements which are already dropdowns, you could give new ones a class which you remove when you make them into dropdowns

<div class="ui dropdown not-dropdownified"></div>

You could add an event listener for DOMSubtreeModified, which will do something only after the page has rendered:

Template.productEdit.events({
    "DOMSubtreeModified": function() {
        if (rendered) {
            var newDropdowns = $('.ui.dropdown.not-dropdownified');
            newDropdowns.removeClass("not-dropdownified");
            newDropdowns.dropdown();
        }
    }
});

This should reduce the number of operations done when the event is triggered, and could stop the callstack from being exhausted

Suppen
  • 836
  • 1
  • 6
  • 21
  • Thanks, although for me this crashed the browser. 'Maximum Call Stack Exceeded'. I did read that observing MutationEvent can be dangerous, I'm going to have to figure out a workaround. – Ian Jones Feb 14 '15 at 13:19
  • See the edit about a possible way to get around this – Suppen Feb 14 '15 at 13:26
  • Also, could you add the javascript tag to this question? I think that might make the syntax highlighting of code work – Suppen Feb 14 '15 at 13:26
  • 1
    I'm trying the newer Mutation method described here, because apparently DOMSubtreeModified is deprecated: http://stackoverflow.com/questions/2844565/is-there-a-jquery-dom-change-listener/11546242#11546242 – Ian Jones Feb 14 '15 at 13:42
1

Here's my tentative answer, it works but I'm still hoping Meteor has some sort of template mutation callback instead of this more cumbersome approach:

Template.productEdit.rendered = function() {
    if( ! this.rendered) {
        $('.ui.dropdown').dropdown();

        var mutationOptions = {
            childList: true,
            subtree: true
        }

        var mutationObserver = new MutationObserver(function(mutations, observer){
            observer.disconnect(); // otherwise subsequent DOM changes will recursively trigger this callback

            var selectChanged = false;

            mutations.map(function(mu) {
                var mutationTargetName = Object.prototype.toString.call(mu.target).match(/^\[object\s(.*)\]$/)[1];
                if(mutationTargetName === 'HTMLSelectElement') {
                    console.log('Select Changed');
                    selectChanged = true;
                }
            });

            if(selectChanged) {
                console.log('Re-init Select');
                $('.ui.dropdown').dropdown('restore defaults');
                $('.ui.dropdown').dropdown('refresh');
                $('.ui.dropdown').dropdown('setup select');
            }

            mutationObserver.observe(document, mutationOptions); // Start observing again
        });

        mutationObserver.observe(document, mutationOptions);

        this.rendered = true;
    }
};

This approach uses MutationObserver with some syntax help I found here

Ian Jones
  • 1,369
  • 1
  • 10
  • 15
-1

Taking ad educated guess, and assuming you are using the Semantic UI Dropdown plugin, there are four callbacks you can define:

onChange(value, text, $choice): Is called after a dropdown item is selected. receives the name and value of selection and the active menu element

onNoResults(searchValue): Is called after a dropdown is searched with no matching values

onShow: Is called after a dropdown is shown.

onHide: Is called after a dropdown is hidden.

To use them, give the dropdown() function a parameter:

$(".ui.dropdown").dropdown({
    onChange: function(value, text, $choice) {alert("You chose " + text + " with the value " + value);},
    onNoResults: function(searchValue) {alert("Your search for " + searchValue + " returned no results");}
    onShow: function() {alert("Dropdown shown");},
    onHide: function() {alert("Dropdown hidden");}
});

I suggest you read the documentation of all plugins you use.

Suppen
  • 836
  • 1
  • 6
  • 21
  • Sorry, but it's not a general JS question. It's about Meteor DOM changes and triggering subsequent code, in this case a dropdown plugin. The above plugin API methods don't suffice. The onChange() for example is for when a user selects a new value, but I need to reset the plugin based on the underlying DOM change due to Meteor reactivity. – Ian Jones Feb 14 '15 at 12:54
  • 1
    I still find it to be a general jQuery/javascript question, and a possible duplicate of http://stackoverflow.com/questions/9488653/jquery-how-to-listen-for-dom-changes At least I got it to run a function every time the DOM changed using answers from there. You could possibly stuff it into a Meteor event handler – Suppen Feb 14 '15 at 13:04
  • I'm still looking for a proper Meteor callback after it has modified a template but there doesn't seem to be one? I'll look at MutationEvent as suggested, it looks like a pretty good solution thank you for the suggestion. – Ian Jones Feb 14 '15 at 13:08