Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Meteor - How can I pass data between helpers and events for a template?

I'm a bit new to Meteor and something I'm having trouble with is reactive data -- particularly in instances where I need to change the data shown based on a mouse or keyboard event. Doing this kind of stuff the normal js way seems to give me trouble in meteor since everything I change gets re-rendered and reset constantly.

So, I thought I'd see if this would be a case in which I could use Meteor's Deps object, however I can't quite grasp it. Here's the code I'm using:

(function(){

    var tenants = [];
    var selectedTenant = 0;

    var tenantsDep = new Deps.Dependency;

    Template.tenantsBlock.tenantsList = function()
    {
        tenants = [];
        var property = $properties.findOne({userId: Meteor.userId(), propertyId: Session.get('property')});
        var tenancies = _Utils.resolveTenancies(property, true, null, true);

        for(var i = 0; i < tenancies.length; i++)
        {
            if(tenancies[i].tenancyId == Session.get('tenancy'))
            {
                tenants = tenants.concat(tenancies[i].otherTenants, tenancies[i].primaryTenant);
            }
        }

        tenants[selectedTenant].selected = 'Selected';

        tenantsDep.changed();

        return tenants;
    };

    Template.tenantsBlock.onlyOneTenant = function()
    {
        tenantsDep.depend();

        return tenants.length > 1 ? '' : 'OneChild';
    };

    Template.tenantsBlock.phoneNumber = function()
    {
        tenantsDep.depend();

        for(var i = 0; i < tenants[selectedTenant].details.length; i++)
            if(_Utils.getDynamicContactIconClass(tenants[selectedTenant].details[i].key) == 'Phone')
                return tenants[selectedTenant].details[i].value;

        return null;
    };

    Template.tenantsBlock.emailAddress = function()
    {
        tenantsDep.depend();

        for(var i = 0; i < tenants[selectedTenant].details.length; i++)
            if(_Utils.getDynamicContactIconClass(tenants[selectedTenant].details[i].key) == 'Email')
                return tenants[selectedTenant].details[i].value;

        return null;
    };

    Template.tenantsBlock.addedDate = function()
    {
        tenantsDep.depend();
        return _Utils.timeToDateString(tenants[selectedTenant].created);
    };

    Template.tenantsBlock.events({
        'click .Name': function(e, template)
        {
            tenantsDep.depend();
            var _this = e.currentTarget;
            var tenantName = _this.innerHTML;

            $(_this).addClass('Selected');
            $(_this).siblings().removeClass('Selected');

            for(var i = 0; i < tenants.length; i++)
            {
                if(tenants[i].name == tenantName)
                    tenants[i].selected = "Selected";
                else
                    tenants[i].selected = '';
            }
        }
    })


})();

^This seemed to be what they were getting at in the meteor documentation (http://docs.meteor.com/#deps_dependency) for dependency.changed() and dependency.depend(), but all this does is give me an infinite loop.

So can I modify the way I declare deps to get this to make data reactive? Is there a better way to do this all together?

UPDATE:

Although I was skeptical to do so, I've been inclined to try to use Session.set/Session.get in a localized way. So, the next time I have to do this, I'll just do

Session.set('tenantsBlock' {tenants: [], selectedTenant: 0});

and then just access this variable from within helpers and event maps related to Template.tenantsBlock. That way they all have real time access to the data and they all get re-run when the data changes. Here's what I converted this script into (sorry these are both so large):

(function()
{
Template.tenantsBlock.created = Template.tenantsBlock.destroyed =function()
{
    _Utils.setSession('tenantsBlock', {
        tenants: [],
        selectedTenant: 0
    })
};

Template.tenantsBlock.tenantsList = function()
{
    var localContext = Session.get('tenantsBlock');
    localContext.tenants = [];
    var property = $properties.findOne({userId: Meteor.userId(), propertyId: Session.get('property')});
    var tenancies = _Utils.resolveTenancies(property, true, null, true);

    for(var i = 0; i < tenancies.length; i++)
    {
        if(tenancies[i].tenancyId == Session.get('tenancy'))
        {
            localContext.tenants = localContext.tenants.concat(tenancies[i].otherTenants, tenancies[i].primaryTenant);
            break;
        }
    }

    localContext.tenants[localContext.selectedTenant].selected = 'Selected';
    Session.set('tenantsBlock', localContext);

    return localContext.tenants;
};

Template.tenantsBlock.onlyOneTenant = function()
{
    var localContext = Session.get('tenantsBlock');
    return localContext.tenants.length > 1 ? '' : 'OneChild';
};

Template.tenantsBlock.phoneNumber = function()
{
    var localContext = Session.get('tenantsBlock');
    for(var i = 0; i < localContext.tenants[localContext.selectedTenant].details.length; i++)
        if(_Utils.getDynamicContactIconClass(localContext.tenants[localContext.selectedTenant].details[i].key) == 'Phone')
            return localContext.tenants[localContext.selectedTenant].details[i].value;

    return null;
};

Template.tenantsBlock.emailAddress = function()
{
    var localContext = Session.get('tenantsBlock');
    var selectedTenantDetails = localContext.tenants[localContext.selectedTenant].details;

    for(var i = 0; i < selectedTenantDetails.length; i++)
        if(_Utils.getDynamicContactIconClass(selectedTenantDetails[i].key) == 'Mail')
            return selectedTenantDetails[i].value;

    return null;
};

Template.tenantsBlock.addedDate = function()
{
    var localContext = Session.get('tenantsBlock');
    return _Utils.timeToDateString(localContext.tenants[localContext.selectedTenant].created);
};

Template.tenantsBlock.events({
    'click .Name': function(e, template)
    {
        var localContext = Session.get('tenantsBlock');
        var _this = e.currentTarget;
        var tenantName = _this.innerHTML;

        for(var i = 0; i < localContext.tenants.length; i++)
        {
            if(localContext.tenants[i].name == tenantName)
            {
                localContext.tenants[i].selected = 'Selected';
                localContext.selectedTenant = i;
            }
            else
            {
                localContext.tenants[i].selected = '';
            }
        }

        Session.set('tenantsBlock', localContext);
    }
})

})();
like image 938
Dom Ramirez Avatar asked Sep 18 '13 18:09

Dom Ramirez


1 Answers

You'll have to overcome the old-school way of doing it :) Meteor is a lot simpler than you think. A good rule of thumb is that if you're using jQuery to manipulate any DOM elements, you're probably doing it wrong. Additionally, if you're accessing any data without using the collection API, you'd better have good reason to do so.

In your case, you don't need to code up any manual dependencies at all. Manual dependencies are rarely needed in most Meteor applications.

The first thing you need to do is put all your tenants inside a Meteor.Collection, which will make them easier to work with.

Tenants = new Meteor.Collection("tenants");

Your tenantsBlock template should look something like this (modulo some different html elements):

<template name="tenantsBlock">
    <ol>    
    {{#each tenants}}
        <li class="name {{selected}}">
            <span>Primary Tenant: {{primaryTenant}}</span>
            <span>Other Tenants: {{otherTenants}}</span>
            <span>Phone Number: {{phoneNumber}}</span>
            <span>Email Address: {{emailAddress}}</span>
            <span>Added Date: {{addedDate}}</span>
        </li>    
    {{/each}}
    </ol>
</template>

Each document in Tenants should look something like the following:

{
    primaryTenant: "Joe Blow",
    otherTenants: "Mickey Mouse, Minnie Mouse",
    phoneNumber: "555-234-5623",
    emailAddress: "[email protected]",
    addedDate: "2005-10-30T10:45Z"
}

Then, all the code you would need is just for the selection/deselection, and you can delete everything else:

Template.tenantsBlock.tenants = function() {
    return Tenants.find();
};

Template.tenantsBlock.selected = function() {
    return Session.equals("selectedTenant", this._id);
};

Template.tenantsBlock.events({
    'click .name': function(e) {   
        Session.set("selectedTenant", this._id);
    }
});

Once again, I reiterate that you should never be doing DOM manipulations with Javascript when using Meteor. You just update your data and your templates will reactively update if everything is done correctly. Declare how you want your data to look, then change the data and watch the magic.

like image 138
Andrew Mao Avatar answered Oct 18 '22 09:10

Andrew Mao