Meteor: Preserving ClassName Flags

410 views Asked by At

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?

1

There are 1 answers

1
saimeunt On BEST ANSWER

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.