Isomorphic way to access new template state?

I just read the changes about blaze http://meteorcapture.com/blaze-updates/ and something really bother me.

Template.hello.onCreated(function() {  
  this.state.get('counter');
});

Template.hello.helpers({  
  counter: function() {
    Template.instance.state.get('counter');
  }
});

Template.hello.events({  
  'click button': function(event, template) {
    template.state.get('counter');
  }
});

Do you see it? 3 Template.hello functions and 3 different way to access state().

this.state.get('counter');
Template.instance.state.get('counter');
template.state.get('counter');

What I love in Meteor is its easiness, isomorphic api (one way to do one thing). And having to remember 3 different way to access the same object depending of where I am, is definitely not easy nor isomorphic (ok we can troll about this term but you see what I mean).

3 Likes

I agree local reactive state is fine, but it would be better to access to it in a consistent way

I guess you can use Template.instance().state everywhere.

Oh yes, I didnā€™t said it but the local reactive state is really a cool idea!

If Template.instance().state works everywhere, I donā€™t see why creating different way to do the same thing, itā€™s disturbing for new people who try to learn

Template.instance() is the newest API for this, and the most consistent. Unfortunately, itā€™s quite verbose, but we are working on some ways to make it more concise. (You can hack it into being more concise by setting Ti = Template.instance and calling it with Ti() :smile: )

Our favorite option right now is to make ā€œthisā€ inside all template callbacks, events, and helpers be the template instance, and introduce a better way of calling the data context.

9 Likes

I like the idea of ā€˜thisā€™ being the template instance within events and helpers.

2 Likes

Good point. I think the more explicit, the better.
Another option could be passing template as argument to every callback function. But ā€œthisā€ namespace also makes sense.

Hereā€™s something I was messing around with. Created a simple package called ā€˜stateā€™.

State = {
  set: function(name, value) {
    return Template.instance().state.set(name, value);
  },
  get: function(name) {
    return Template.instance().state.get(name);
  }
};

Now we can do this:

Template.hello.onCreated(function() {  
  // required until Sashko's PR is merged.
  this.state = new ReactiveDict();
  // counter starts at 0
  State.set('counter', 0);
});

Template.hello.helpers({  
  counter: function() {
    return State.get('counter');
  }
});

Template.hello.events({  
  'click button': function(event, template) {
    // increment the counter when button is clicked
    State.set('counter', State.get('counter') + 1);
  }
});
1 Like

Very nice! I love that the new template state API can be something that people build on.

@dburles do you have any opinions on auto-migration of state across hot code push? I was going to just use an _id field in the template arguments/data context if one is present.

It would be amazing if itā€™s possible to migrate without referencing an _id field and itā€™s something I have been pondering, but havenā€™t really dug into. @sashko have you looked at anything beyond the _id field solution?

Well there needs to be some sort of unique string that identifies the template, otherwise you canā€™t match up the data before and after the migration. I think there needs to be a sane default that works most of the time, and then a good power user tool that lets you set a custom identifier. I was thinking using the _id of the data context as the default (another alternative would be using the path from the DOM or something), and then having an API for migrating manually.

What would your ideal migration API look like?

In a lot of cases there wonā€™t be an _id available to reference, so some kind of identifier would be required, but Iā€™m not sure exactly how we apply that state once the DOM is re-drawn.

Hello guys!
I known about template-scoped vars from here:


Loved the idea.

But Template.instance().state and template.state calls are so weird (they are not consistent in helpers and event handlers)! And it downed upon me to create ReactiveDict in data fn of Iron-router!!

We can do this:

data: ->
  State: new ReactiveDict()
  products: Products.find()
  ...

And now we can use this @State in

  1. Template helpers as
Template.my_template.events
  'click .action-btn': -> @State.set('some_state', 'value')`
  1. Template events:
Template.my_template.helpers
  some_state: -> @State.get('some_state')`

It seems so awesome to me!!! Jist had to share this to somebody. Registered here :smile:

Added: Found some bug. data function of router is reactive, so it reruns every change in any collection, it returns. So it flushes our State. Workaround is to add function

  State: do ->
    state = null
    -> state ?= new ReactiveDict()

or in js:

  State: (function() {
      var state = null;
      return function() {
        return state != null ? state : state = new ReactiveDict();
      };
    })();

to router controller.

This is actually the future of template state as we see it, but you wonā€™t need to use iron router to get it.

Cool. +1 for:

make ā€œthisā€ inside all template callbacks, events, and helpers be the template instance

We just implemented small hack that exactly matches your last idea.

    Template::methods = (methods) ->
      helpers = {}
      for name, method of methods
        do (method) ->
          helpers[name] = ->
            tpl = Template.instance()
            original = tpl.context  # Save context if somebody already used it.
            tpl.context = @
            result = method.call tpl
            tpl.context = original  # Restore original context
            return result
      @helpers helpers

And now we write:

Template.myTemplate.onCreated ->
  @myReactiveVar = new ReactiveVar ''

Template.myTemplate.methods
  myVar: -> 
    @myReactiveVar.get()  
    # `this` coresponds to `Template.instance()`
  fullName: -> 
    "#{@context.firstName} #{@context.lastName}"  
    # `this.context` coresponds to current data context.