Skip to content

Instantly share code, notes, and snippets.

@Rich-Harris
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rich-Harris/9628823 to your computer and use it in GitHub Desktop.
Save Rich-Harris/9628823 to your computer and use it in GitHub Desktop.
Evaluating expressions within events

In answer to this tweet:

Within Ractive templates, you can use normal JavaScript expressions (with a handful of exceptions, e.g. no new operators or assignments, since expressions should be side-effect free). These expressions aren't straightforwardly evaled - instead, they're parsed into an abstract syntax tree, at which point we extract the references and turn it back into a string which is later used to generate a function:

Ractive.parse('{{ a+b+1 ? "a" : "b" }}');

// results in
{
  t: 2,
  x: {
    r: ['a', 'b'],
    s: '${0}+${1}+1?"a":"b"'
  }
}

Those references might mean different things at different times:

<!-- here, `a` might mean `foo.a` or `a` - mutatis mutandis for `b` -->
{{#foo}}
  {{ a+b+1 ? "a" : "b" }}
{{/foo}}

<!-- but here, it could mean `bar.a` -->
{{#bar}}
  {{ a+b+1 ? "a" : "b" }}
{{/bar}}

In other words, a reference must be resolved to a keypath, taking account of its context. It's those keypaths that are used to set up the reactive data-binding. This can't happen at parse time, because it's dependent on the data the template is rendered with - so it's impossible to evaluate the expression outside the context of a specific point within the template.

Suppose we wanted to use the evaluate an expression anyway, and that we're in a position to say that we don't care about context (i.e. a and b are top-level properties), and that we wanted to use the value of that expression in an event handler.

We could do this:

ractive.on( 'foo', function ( event ) {
  var a, b, value;
  
  a = this.get( 'a' );
  b = this.get( 'b' );
  value = a+b+1 ? "a" : "b";
  
  doSomethingWith( value );
});

That's a perfectly valid approach, and one that is easy to understand. But if we wanted or needed to use the expression syntax instead, we could take advantage of the fact that you can pass arguments to event handlers:

<button on-click='foo:{{ a+b+1 ? "a" : "b" }}'>click me!</button>
ractive.on( 'foo', function ( event, value ) {
  doSomethingWith( value );
});
@mkrn
Copy link

mkrn commented Mar 18, 2014

This is great advise, and I understand the scope limitation now.

What we are trying to do is to describe entire setup, including events in Json, so functions won't quite work.

There are not that many possible types of actions, - set, fetch data, navigate..
Set is the most important but lot of the times it needs to use a different variable(s) with (get) and do something with it (increment, concat, etc). I guess for all of these assuming top-level scope is fine! So it would work if there would be a way to do something like:


ractive.on('event', function(event, value) {
var parsed = Ractive.parse('{{ a+b+1 ? "a" : "b" }}');
var val = ractive.evaluateAtTopLevel(parsed);
ractive.set('keypath', val);
});

Do you think creating a new instance for this is too wasteful? If there's already a method that's doing it on initial render, maybe it could be exposed to API.

Thanks again

@Rich-Harris
Copy link
Author

@mkrn Sorry, I forgot you were going to reply to this gist and only just saw it!

As it happens, you have excellent timing. A new feature that will be merged soon is computed properties, which behave just like regular properties except that they are derived from other values. There's a discussion on GitHub and on the Ractive mailing list.

So basically, you could do

ractive = new Ractive({
  el: 'body',
  template: myTemplate,
  data: {
    a: 1,
    b: 2
  },
  computed: {
    prop: '${a} + ${b} + 1 ? "a" : "b"'
  }
});

ractive.on( 'event', function () {
  var val = ractive.get( 'prop' );
  doSomethingWith(prop);
});

In your example you're setting a keypath to the result of the expression - you wouldn't actually need to do that with computed properties since you could just use {{prop}} (or whatever it was called) in your template, and it would update reactively.

Hope this helps

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment