How to automatically react to DOM changes?

I have a list of items in the DOM:

<ul id="my_list">
    <li class="relevant">item 1 <a onclick="remove(1)">Remove</a></li>
    <li class="relevant">item 2 <a onclick="remove(2)">Remove</a></li>
    ...
</ul>

The user can remove items from the list by clicking the respective ‘remove’ links in the item. I want to reactively monitor this list for changes, so that when there are no items left it triggers a function. For example:

var num_list_items = $("#my_list li.relevant").length;
if( num_list_items == 0 ){
    do_something();
}

My app is being built in Meteor, so would ideally like to know if there is any native functionality that can do this. If not, any other suggestions are welcome.

i would suggest that you put the clicking events on the template events

> <ul id="my_list">
>     <li class="relevant">item 1 <a onclick="remove(1)">Remove</a></li>
>     <li class="relevant">item 2 <a onclick="remove(2)">Remove</a></li>
>     ...
> </ul>

instead like this…

> <ul id="my_list">
>     <li class="relevant">item 1 <a id="remove">Remove</a></li>
>     <li class="relevant">item 2 <a id="remove">Remove</a></li>
>     ...
> </ul>

then on your JS file…

Template.templatename.events({
‘click a#remove’ : function () {
/// check here every time remove was click check the length of the list
/// have some sort of identifications of item to be removed…
}

});

i hope this helps… this is just a suggestion…

Use a reactive source like Session, ReactiveVar or ReactiveDict as your list. Use helpers to display your list on your template. Then this is an example of how you would watch your list for changes:

Template.myTemplate.onRendered(function () {
    this.autorun(function () {
        var myList = Session.get('myList');
        if (!myList.length)
            doSomething();
    });
});

The general pattern is that you do not care about DOM. What you do in Meteor is you have some reactive state and you define how that reactive state gets rendered to DOM. You might attach event handlers to DOM, but only modify that reactive state in them. And then leave to Meteor to render that to DOM again. And then if you would like to know when state changes in some way, observe that state and this is it.

Can you provide a full solution?

Unfortunately the auto-rerendering of the DOM by Meteor interfers with the way the app. works.

Appreciate the comment. There are other user events that affect the list too, like adding a new item will trigger the same event. Although I could wrap this event in a function and have this function called from both the template helper and the ‘add item’ function, I want to ideally avoid doing this and instead set up a reactive observer that looks at the list for changes (not changes in the Mongo data, more DOM changes) and always react accordingly.

@tidee then you should change the way your app works. @mitar has given a very good explanation.
This is a very basic example, but your code should look more like:

Template.myTemplate.helpers({
    myList: function () {
        return Session.get('myList');
    }
});

Template.myTemplate.events({
    'click .remove-item': function (event) {
        var myList = Session.get('myList');
        myList = _.without(myList, event.target.id);
        Session.set('myList');
    }
});

<ul id="my_list">
    {{#each myList}}
        <li class="relevant">
            {{this}}
            <button class="remove-item" id="{{this}}">Remove</button>
        </li>
    {{/each}}
</ul>

I have mananged to use this:

Template.myListTpl.onRendered( function() {
    ...
    var observed = $("#my_list").get(0);
    var whatIsObserved = { childList : true };
    var mo = new MutationObserver(function(mutations){
        mutations.forEach(function(mutation){
            if(mutation.type === 'childList'){
                if(mutation.target.querySelectorAll('.relevant').length === 0){
                    do_something();
                }
            }
        });
    });
    mo.observe(observed, whatIsObserved);
    ...
});

This is not ideal given the lack of backwards compatibility, so an equivalent in Meteor would be useful.

Although I can see that the use of Sessions could be employed here, I have read that it is advised not to over use Sessions, so would prefer to avoid if possible. My guess is that the data for the list is initially added to the ‘myList’ Session from a database query - is this correct. If so, at what point/in what area, e.g. onRendered, would this occur to make it availale to the template helper in time?

@tidee Session is just easy to show examples with. I did mention you can also use ReactiveDict, ReactiveVar, even null Mongo Collections…

Any chance of an example with ReactiveVar ?

Hello! I am doing something similar to this but instead of removing, I want to add my item to cart. How can I do that? Like when I click on an li element, it adds to the cart beside it where I can see it appear and update details of the order. Please advise :slight_smile: I’m new to meteor and would like some help on this!

                                        {{#with item1}}
                                            <li class="item" id="menuItem1">{{name}}<br>
                                                <span class="glyphicon glyphicon-plus" id="plus"></span>
                                                <span id="price">&#163;{{price}}</span>
                                                <p>{{desc}}</p>
                                            </li>


this is my current code. As shown in the image, clicking on any li element, adds the item to the cart. (It is now hard-coded)

Thanks in advance!