Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #8166 (closed enhancement: fixed)

Opened 2 years ago

Last modified 1 year ago

[PATCH][TEST] Prototype: much more power for Template + String#evaluate factory

Reported by: tdd Assigned to: sam
Priority: normal Milestone: 1.x
Component: Prototype Version: edge
Severity: normal Keywords: 1.5.2 discuss
Cc:

Description

This patch supersedes #8164, which itself replaced #8060 and #8078. It goes way beyond the limited scope of these, providing support for just about everything, at a weight gain 5 bytes below 1KB.

It is also extensively tested (29 assertions in 7 tests, covering all possible combinations, including nightmarish ones).

With this patch, you can:

  • Use dot-notation and []-based indexing.
  • Use method names as regular properties: these methods are going to be called with no argument. You can keep chaining components after a method call: it needs not be the last part of the expression.
  • Provide custom replacement callbacks in the options (2nd) parameter, which are components of the expression starting with a specific string (defaults to '@', can be customized through the callbackPrefix option). These callbacks will be called with two arguments: first the object that is their current context, second the original object passed to evaluate.
  • Simplify one-shot templating by using 'templateText'.evaluate(obj) instead of new Template('templateText').evaluate(obj). The factory method accepts the same optional parameter for options as the Template constructor does.

Note that...

# This patch preserves complete backward compatibility. # You can use empty brackets to represent the empty string as a property name. Brackets are, essentially, there for weird property names (e.g. those containing brackets, which can then be escaped with \, or those with dots). There is no need for quotes inside the brackets, and they are indeed going to be used literally if you put any.

The unit tests provide examples aplenty, but here is a monster test with combined features, straight out of them:

var source = '#{name} is #{age} years old, ' +
  'managed by #{manager.name}, #{manager.age}.\n' +
  'Yes, #{manager.@cb2}.\n' +
  '#{name} works on #{getJob}.\n' +
  'Colleagues include #{colleagues[0].name} ' +
  '(working on #{colleagues[0].getJob}) ' +
  'and #{colleagues[1].name},\n' +
  'but "#{colleagues[0].@cb2}" is false.';
var subject = { name: 'Stephan', age: 22, manager: { name: 'John', age: 29 },
  colleagues: [
    { name: 'Mark', getJob: function() { return 'SOAP'; } },
    { name: 'Indy' } ],
  getJob: function() { return 'Prototype stuff'; }
};
var opts = {
  cb2: function(local, global) {
    return local.name + ' supervises ' + global.name;
  }
};
new Template(source, opts).evaluate(subject)

This will evaluate to:

Stephan is 22 years old, managed by John, 29.
Yes, John supervises Stephan.
Stephan works on Prototype stuff.
Colleagues include Mark (working on SOAP) and Indy,
but "Mark supervises Stephan" is false.

Note that you could very well use templates like:

'#{colleagues[0].getJob.toLowerCase}'
// => 'soap'
'#{colleagues[0].getJob.length}'
// => '4'

(Where you get multiple functions, or a function and a property, etc.)

You could also use square brackets for weird property names:

var subject = { '': 'empty', '.NET': 'C#' };
'#{[]}'.evaluate(subject)
// => 'empty'
'#{[.NET]}'.evaluate(subject)
// => 'C#'

Isn't it sweeeeeet?

Attachments

template.diff (7.7 kB) - added by tdd on 04/24/07 20:39:37.
Full patch with tests
template.2.diff (5.6 kB) - added by tdd on 04/24/07 21:46:55.
Lighter version based on toTemplateReplacements method
template.3.diff (5.6 kB) - added by tdd on 07/23/07 14:47:58.
Renamed String#evaluate to String#interpolate

Change History

04/24/07 20:39:37 changed by tdd

  • attachment template.diff added.

Full patch with tests

04/24/07 21:46:31 changed by tdd

  • keywords changed from 1.5.2 ready to 1.5.2 discuss.

Sam requested we strip built-in method support and custom callbacks...

On the other hand, we allow for a toTemplateReplacements() method to be defined on the passed object, that produces an alternate representation. This lets methods be called as you want and their results stored in the proper properties, and also covers the custom callback thing.

04/24/07 21:46:55 changed by tdd

  • attachment template.2.diff added.

Lighter version based on toTemplateReplacements method

04/25/07 01:22:13 changed by bokuhog

toTemplateReplacements is nice for allowing more options in using functions but it is annoying to have to list all the properties again to be able to use a one function.

Can will still allow function names with no args to be put into the template string (i.e. #{getJob}) and have toTemplateReplacements for functions that we need to pass args to.

04/25/07 06:30:52 changed by tdd

Bokuhog: ultimately that's a "convince Sam" issue. If you can come up with significant use cases where no-arg methods need be called straightforwardly, put them here. The rationale behind stripping them so far is that interpolated strings should not use a different syntax then actual JS exprs. So if there were methods in there, they would need parentheses; which means they should be able to have args; which ups the ante way too much.

Note that toTemplateReplacements can be simply implemented with something like the following, which does not require manual prop listing.:

MyClass.prototype = {
  ...
  toTemplateReplacements: function() {
    return Object.extend(Object.clone(this), { job: this.getJob() });
  }
  ...
}

07/23/07 04:48:58 changed by Tobie

Had'nt we agreed on String#interpolate ?

07/23/07 06:44:59 changed by tdd

IIRC, yes.

07/23/07 14:47:58 changed by tdd

  • attachment template.3.diff added.

Renamed String#evaluate to String#interpolate

07/24/07 17:24:28 changed by sam

  • status changed from new to closed.
  • resolution set to fixed.

(In [7221]) prototype: Template enhancements. Closes #8166.