Question

I'm having trouble using the preserve method in meteor. It doesn't seem to do what I want it to do.

Basically, I have three nested templates which, when rendered, look something like this in their closed state:

<div class="Dropdown"><!--From Template1-->
    <div class="Group"><!--From Template2-->
        <div class="SubGroup"><!--From Template3-->
            I'm the subgroup's content!
        </div>
    </div>
</div>

To view the content in the .SubGroup node, all template wrappers need to bear the .Open class name. This is done on a click event. Here's what it looks like in open state:

<div class="Dropdown Open"><!--From Template1-->
    <div class="Group Open"><!--From Template2-->
        <div class="SubGroup Open"><!--From Template3-->
            I'm the subgroup's content!
        </div>
    </div>
</div>

The issue is, when events fire and alter the database from within .SubGroup's content, it looks like Template2 and Template3 both get re-rendered and lose their programmatically applied .Open class.

I've tried using Template.Template2.preserve(['.Group']); on each template with just about every selector I think could affect it. I've also tried {{#constant}} and {{#isolated}} helpers, but have yet to get expected results with these.

What's the right way to keep Meteor from wiping my class names out?

Was it helpful?

Solution

You should use Session variables coupled with Handlebars helpers.

<template name="dropdown">
    opened helper resolves to current dropdown state
    <div class="dropdown {{opened}}">
        iterate over each group using a cursor
        {{#each groups}}
            call subtemplate fed with current group document
            fetched from the cursor
            {{> group}}
        {{/each}}
    </div>
</template>

<template name="group">
    assign a unique id to the group div, using the document._id
    <div id="group-{{_id}}" class="group {{opened}}">
        ... and so on
    </div>
</template>

Template.dropdown.helpers({
    opened:function(){
        // Session variable will be undefined on first page view (closed state)
        // then it will have the value set in the click handler
        return Session.get("dropdown-opened")?"open":"";
    },
    groups:function(){
        return Groups.find();
    }
});

Template.dropdown.events({
    "click .dropdown":function(){
        var opened=Session.get("dropdown-opened");
        // toggle open state
        Session.set("dropdown-opened",!opened);
    }
});

// executed once on each template instance creation
Template.group.created=function(){
    // this.data._id is the fetched document._id
    // comment this line if you don't want your stuff to be collapsed
    // when the template is re-created (ie when you change page)
    Session.set("group-"+this.data._id+"-opened",false);
};

Template.group.helpers({
    opened:function(){
        // this._id is the fetched document._id
        return Session.get("group-"+this._id+"-opened")?"open":"";
    },
    subGroups:function(){...}
});

Template.group.events({
    "click .group":function(event,template){
        // once again, template.data._id corresponds to the id of the
        // document used to feed the group template
        var sessionKey="group-"+template.data._id+"-opened";
        var opened=Session.get(sessionKey);
        Session.set(sessionKey,!opened);
    }
});

This is untested code but I do similar stuff on my app and it works like a charm. I think this is the Meteor way (Session+helpers) of achieving this kind of things (as opposed to using jQuery to manipulate the DOM and class names). Unfortunately, this kind of pattern is quite verbose and can be a bit obscure for developers coming from non-Meteor classic front-end JS web-app development, but I'm sure this is gonna be improved.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top