1 // Copyright 2006 Google Inc.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12 // implied. See the License for the specific language governing
13 // permissions and limitations under the License.
15 * Author: Steffen Meschkat <mesch@google.com>
17 * @fileoverview This class is used to evaluate expressions in a local
18 * context. Used by JstProcessor.
23 * Names of special variables defined by the jstemplate evaluation
24 * context. These can be used in js expression in jstemplate
27 var VAR_index
= '$index';
28 var VAR_count
= '$count';
29 var VAR_this
= '$this';
30 var VAR_context
= '$context';
35 * The name of the global variable which holds the value to be returned if
36 * context evaluation results in an error.
37 * Use JsEvalContext.setGlobal(GLOB_default, value) to set this.
39 var GLOB_default
= '$default';
43 * Un-inlined literals, to avoid object creation in IE6. TODO(mesch):
44 * So far, these are only used here, but we could use them thoughout
45 * the code and thus move them to constants.js.
48 var REGEXP_semicolon
= /\s*;\s*/;
53 * @param {Object|null=} opt_data
54 * @param {Object=} opt_parent
57 function JsEvalContext(opt_data
, opt_parent
) {
58 this.constructor_
.apply(this, arguments
);
62 * Context for processing a jstemplate. The context contains a context
63 * object, whose properties can be referred to in jstemplate
64 * expressions, and it holds the locally defined variables.
66 * @param {Object|null} opt_data The context object. Null if no context.
68 * @param {Object} opt_parent The parent context, from which local
69 * variables are inherited. Normally the context object of the parent
70 * context is the object whose property the parent object is. Null for the
71 * context of the root object.
74 JsEvalContext
.prototype.constructor_ = function(opt_data
, opt_parent
) {
79 * The context for variable definitions in which the jstemplate
80 * expressions are evaluated. Other than for the local context,
81 * which replaces the parent context, variable definitions of the
82 * parent are inherited. The special variable $this points to data_.
84 * If this instance is recycled from the cache, then the property is
85 * already initialized.
92 // If there is a parent node, inherit local variables from the
94 copyProperties(me
.vars_
, opt_parent
.vars_
);
96 // If a root node, inherit global symbols. Since every parent
97 // chain has a root with no parent, global variables will be
98 // present in the case above too. This means that globals can be
99 // overridden by locals, as it should be.
100 copyProperties(me
.vars_
, JsEvalContext
.globals_
);
104 * The current context object is assigned to the special variable
105 * $this so it is possible to use it in expressions.
108 me
.vars_
[VAR_this
] = opt_data
;
111 * The entire context structure is exposed as a variable so it can be
112 * passed to javascript invocations through jseval.
114 me
.vars_
[VAR_context
] = me
;
117 * The local context of the input data in which the jstemplate
118 * expressions are evaluated. Notice that this is usually an Object,
119 * but it can also be a scalar value (and then still the expression
120 * $this can be used to refer to it). Notice this can even be value,
121 * undefined or null. Hence, we have to protect jsexec() from using
122 * undefined or null, yet we want $this to reflect the true value of
123 * the current context. Thus we assign the original value to $this,
124 * above, but for the expression context we replace null and
125 * undefined by the empty string.
129 me
.data_
= getDefaultObject(opt_data
, STRING_empty
);
132 // If this is a top-level context, create a variable reference to the data
133 // to allow for accessing top-level properties of the original context
134 // data from child contexts.
135 me
.vars_
[VAR_top
] = me
.data_
;
141 * A map of globally defined symbols. Every instance of JsExprContext
142 * inherits them in its vars_.
145 JsEvalContext
.globals_
= {}
149 * Sets a global symbol. It will be available like a variable in every
150 * JsEvalContext instance. This is intended mainly to register
151 * immutable global objects, such as functions, at load time, and not
152 * to add global data at runtime. I.e. the same objections as to
153 * global variables in general apply also here. (Hence the name
154 * "global", and not "global var".)
155 * @param {string} name
156 * @param {Object|null} value
158 JsEvalContext
.setGlobal = function(name
, value
) {
159 JsEvalContext
.globals_
[name
] = value
;
164 * Set the default value to be returned if context evaluation results in an
165 * error. (This can occur if a non-existent value was requested).
167 JsEvalContext
.setGlobal(GLOB_default
, null);
171 * A cache to reuse JsEvalContext instances. (IE6 perf)
173 * @type Array.<JsEvalContext>
175 JsEvalContext
.recycledInstances_
= [];
179 * A factory to create a JsEvalContext instance, possibly reusing
180 * one from recycledInstances_. (IE6 perf)
182 * @param {Object} opt_data
183 * @param {JsEvalContext} opt_parent
184 * @return {JsEvalContext}
186 JsEvalContext
.create = function(opt_data
, opt_parent
) {
187 if (jsLength(JsEvalContext
.recycledInstances_
) > 0) {
188 var instance
= JsEvalContext
.recycledInstances_
.pop();
189 JsEvalContext
.call(instance
, opt_data
, opt_parent
);
192 return new JsEvalContext(opt_data
, opt_parent
);
198 * Recycle a used JsEvalContext instance, so we can avoid creating one
199 * the next time we need one. (IE6 perf)
201 * @param {JsEvalContext} instance
203 JsEvalContext
.recycle = function(instance
) {
204 for (var i
in instance
.vars_
) {
205 // NOTE(mesch): We avoid object creation here. (IE6 perf)
206 delete instance
.vars_
[i
];
208 instance
.data_
= null;
209 JsEvalContext
.recycledInstances_
.push(instance
);
214 * Executes a function created using jsEvalToFunction() in the context
215 * of vars, data, and template.
217 * @param {Function} exprFunction A javascript function created from
218 * a jstemplate attribute value.
220 * @param {Element} template DOM node of the template.
222 * @return {Object|null} The value of the expression from which
223 * exprFunction was created in the current js expression context and
224 * the context of template.
226 JsEvalContext
.prototype.jsexec = function(exprFunction
, template
) {
228 return exprFunction
.call(template
, this.vars_
, this.data_
);
230 log('jsexec EXCEPTION: ' + e
+ ' at ' + template
+
231 ' with ' + exprFunction
);
232 return JsEvalContext
.globals_
[GLOB_default
];
238 * Clones the current context for a new context object. The cloned
239 * context has the data object as its context object and the current
240 * context as its parent context. It also sets the $index variable to
241 * the given value. This value usually is the position of the data
242 * object in a list for which a template is instantiated multiply.
244 * @param {Object} data The new context object.
246 * @param {number|string} index Position of the new context when multiply
247 * instantiated. (See implementation of jstSelect().)
249 * @param {number} count The total number of contexts that were multiply
250 * instantiated. (See implementation of jstSelect().)
252 * @return {JsEvalContext}
254 JsEvalContext
.prototype.clone = function(data
, index
, count
) {
255 var ret
= JsEvalContext
.create(data
, this);
256 ret
.setVariable(VAR_index
, index
);
257 ret
.setVariable(VAR_count
, count
);
263 * Binds a local variable to the given value. If set from jstemplate
264 * jsvalue expressions, variable names must start with $, but in the
265 * API they only have to be valid javascript identifier.
267 * @param {string} name
270 JsEvalContext
.prototype.setVariable = function(name
, value
) {
271 this.vars_
[name
] = value
;
276 * Returns the value bound to the local variable of the given name, or
277 * undefined if it wasn't set. There is no way to distinguish a
278 * variable that wasn't set from a variable that was set to
279 * undefined. Used mostly for testing.
281 * @param {string} name
285 JsEvalContext
.prototype.getVariable = function(name
) {
286 return this.vars_
[name
];
291 * Evaluates a string expression within the scope of this context
292 * and returns the result.
294 * @param {string} expr A javascript expression
295 * @param {Element} opt_template An optional node to serve as "this"
297 * @return {Object?} value
299 JsEvalContext
.prototype.evalExpression = function(expr
, opt_template
) {
300 var exprFunction
= jsEvalToFunction(expr
);
301 return this.jsexec(exprFunction
, opt_template
);
306 * Uninlined string literals for jsEvalToFunction() (IE6 perf).
310 var STRING_with
= 'with (a_) with (b_) return ';
314 * Cache for jsEvalToFunction results.
317 JsEvalContext
.evalToFunctionCache_
= {};
321 * Evaluates the given expression as the body of a function that takes
322 * vars and data as arguments. Since the resulting function depends
323 * only on expr, we cache the result so we save some Function
324 * invocations, and some object creations in IE6.
326 * @param {string} expr A javascript expression.
328 * @return {Function} A function that returns the value of expr in the
329 * context of vars and data.
331 function jsEvalToFunction(expr
) {
332 if (!JsEvalContext
.evalToFunctionCache_
[expr
]) {
334 // NOTE(mesch): The Function constructor is faster than eval().
335 JsEvalContext
.evalToFunctionCache_
[expr
] =
336 new Function(STRING_a
, STRING_b
, STRING_with
+ expr
);
338 log('jsEvalToFunction (' + expr
+ ') EXCEPTION ' + e
);
341 return JsEvalContext
.evalToFunctionCache_
[expr
];
346 * Evaluates the given expression to itself. This is meant to pass
347 * through string attribute values.
349 * @param {string} expr
353 function jsEvalToSelf(expr
) {
359 * Parses the value of the jsvalues attribute in jstemplates: splits
360 * it up into a map of labels and expressions, and creates functions
361 * from the expressions that are suitable for execution by
362 * JsEvalContext.jsexec(). All that is returned as a flattened array
363 * of pairs of a String and a Function.
365 * @param {string} expr
369 function jsEvalToValues(expr
) {
370 // TODO(mesch): It is insufficient to split the values by simply
371 // finding semi-colons, as the semi-colon may be part of a string
372 // constant or escaped.
374 var values
= expr
.split(REGEXP_semicolon
);
375 for (var i
= 0, I
= jsLength(values
); i
< I
; ++i
) {
376 var colon
= values
[i
].indexOf(CHAR_colon
);
380 var label
= stringTrim(values
[i
].substr(0, colon
));
381 var value
= jsEvalToFunction(values
[i
].substr(colon
+ 1));
382 ret
.push(label
, value
);
389 * Parses the value of the jseval attribute of jstemplates: splits it
390 * up into a list of expressions, and creates functions from the
391 * expressions that are suitable for execution by
392 * JsEvalContext.jsexec(). All that is returned as an Array of
395 * @param {string} expr
397 * @return {Array.<Function>}
399 function jsEvalToExpressions(expr
) {
401 var values
= expr
.split(REGEXP_semicolon
);
402 for (var i
= 0, I
= jsLength(values
); i
< I
; ++i
) {
404 var value
= jsEvalToFunction(values
[i
]);