mapexplorer-rails 1.0.0.pre.alpha → 1.0.0.pre.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mapexplorer/rails/version.rb +1 -1
- data/vendor/assets/babel-polyfill/README.md +0 -0
- data/vendor/assets/babel-polyfill/bower.json +22 -0
- data/vendor/assets/babel-polyfill/browser-polyfill.js +5 -0
- data/vendor/assets/d3/LICENSE +27 -0
- data/vendor/assets/d3/README.md +45 -0
- data/vendor/assets/d3/bower.json +7 -0
- data/vendor/assets/d3/d3.js +16383 -0
- data/vendor/assets/d3/d3.min.js +8 -0
- data/vendor/assets/jsrender/MIT-LICENSE.txt +20 -0
- data/vendor/assets/jsrender/bower.json +43 -0
- data/vendor/assets/jsrender/index.js +8 -0
- data/vendor/assets/jsrender/jsrender-node.js +2298 -0
- data/vendor/assets/jsrender/jsrender.js +2350 -0
- data/vendor/assets/jsrender/jsrender.min.js +4 -0
- data/vendor/assets/jsrender/jsrender.min.js.map +1 -0
- data/vendor/assets/jsrender/tmplify/index.js +64 -0
- data/vendor/assets/mapexplorer-core/LICENSE +21 -0
- data/vendor/assets/mapexplorer-core/README.md +235 -0
- data/vendor/assets/mapexplorer-core/assets/stylesheets/map.scss +38 -0
- data/vendor/assets/mapexplorer-core/assets/stylesheets/mapexplorer-core.scss +3 -0
- data/vendor/assets/mapexplorer-core/assets/stylesheets/merkle-path.scss +10 -0
- data/vendor/assets/mapexplorer-core/assets/stylesheets/variables.scss +14 -0
- data/vendor/assets/mapexplorer-core/bower.json +32 -0
- data/vendor/assets/mapexplorer-core/dist/mapexplorer-core.js +3872 -0
- data/vendor/assets/mapexplorer-core/dist/mapexplorer-core.js.map +1 -0
- data/vendor/assets/mapexplorer-core/dist/mapexplorer-core.min.js +35 -0
- data/vendor/assets/mapexplorer-core/karma.conf.js +117 -0
- data/vendor/assets/mapexplorer-core/package.json +85 -0
- data/vendor/assets/mapexplorer-core/rollup.bower.config.js +41 -0
- data/vendor/assets/mapexplorer-core/rollup.es.config.js +16 -0
- data/vendor/assets/mapexplorer-core/rollup.umd.config.js +25 -0
- data/vendor/assets/stratumn-sdk/LICENSE +21 -0
- data/vendor/assets/stratumn-sdk/README.md +267 -0
- data/vendor/assets/stratumn-sdk/bower.json +27 -0
- data/vendor/assets/stratumn-sdk/dist/stratumn-sdk.js +813 -0
- data/vendor/assets/stratumn-sdk/dist/stratumn-sdk.js.map +1 -0
- data/vendor/assets/stratumn-sdk/dist/stratumn-sdk.min.js +2 -0
- data/vendor/assets/stratumn-sdk/dist/stratumn-sdk.min.js.map +1 -0
- data/vendor/assets/stratumn-sdk/examples/browser/index.html +34 -0
- data/vendor/assets/stratumn-sdk/package.json +67 -0
- data/vendor/assets/stratumn-sdk/rollup.base.config.js +13 -0
- data/vendor/assets/stratumn-sdk/rollup.bower.config.js +18 -0
- data/vendor/assets/stratumn-sdk/rollup.bower.min.config.js +7 -0
- data/vendor/assets/stratumn-sdk/rollup.es.config.js +9 -0
- data/vendor/assets/stratumn-sdk/rollup.umd.config.js +10 -0
- metadata +46 -1
@@ -0,0 +1,2298 @@
|
|
1
|
+
/*! JsRender v0.9.83 (Beta): http://jsviews.com/#jsrender */
|
2
|
+
/*! **VERSION FOR NODE.JS** (For WEB see http://jsviews.com/download/jsrender.js) */
|
3
|
+
/*
|
4
|
+
* Best-of-breed templating in browser or on Node.js.
|
5
|
+
* Does not require jQuery, or HTML DOM
|
6
|
+
* Integrates with JsViews (http://jsviews.com/#jsviews)
|
7
|
+
*
|
8
|
+
* Copyright 2016, Boris Moore
|
9
|
+
* Released under the MIT License.
|
10
|
+
*/
|
11
|
+
|
12
|
+
//jshint -W018, -W041
|
13
|
+
|
14
|
+
(function(global) {
|
15
|
+
"use strict";
|
16
|
+
if (typeof exports !== 'object' ) {
|
17
|
+
throw "Outside Node.js use //jsviews.com/download/jsrender.js";
|
18
|
+
}
|
19
|
+
|
20
|
+
//========================== Top-level vars ==========================
|
21
|
+
|
22
|
+
var versionNumber = "v0.9.83",
|
23
|
+
|
24
|
+
// global var is the this object, which is window when running in the usual browser environment
|
25
|
+
|
26
|
+
$, jsvStoreName, rTag, rTmplString, topView, $views,
|
27
|
+
|
28
|
+
//TODO tmplFnsCache = {},
|
29
|
+
$isFunction, $isArray, $templates, $converters, $helpers, $tags, $sub, $subSettings, $subSettingsAdvanced, $viewsSettings, delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar, setting, baseOnError,
|
30
|
+
|
31
|
+
rPath = /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
|
32
|
+
// not object helper view viewProperty pathTokens leafToken
|
33
|
+
|
34
|
+
rParams = /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,
|
35
|
+
// lftPrn0 lftPrn bound path operator err eq path2 prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space
|
36
|
+
// (left paren? followed by (path? followed by operator) or (path followed by left paren?)) or comma or apos or quot or right paren or space
|
37
|
+
|
38
|
+
isRenderCall,
|
39
|
+
rNewLine = /[ \t]*(\r\n|\n|\r)/g,
|
40
|
+
rUnescapeQuotes = /\\(['"])/g,
|
41
|
+
rEscapeQuotes = /['"\\]/g, // Escape quotes and \ character
|
42
|
+
rBuildHash = /(?:\x08|^)(onerror:)?(?:(~?)(([\w$_\.]+):)?([^\x08]+))\x08(,)?([^\x08]+)/gi,
|
43
|
+
rTestElseIf = /^if\s/,
|
44
|
+
rFirstElem = /<(\w+)[>\s]/,
|
45
|
+
rAttrEncode = /[\x00`><"'&=]/g, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
|
46
|
+
rIsHtml = /[\x00`><\"'&=]/,
|
47
|
+
rHasHandlers = /^on[A-Z]|^convert(Back)?$/,
|
48
|
+
rWrappedInViewMarker = /^\#\d+_`[\s\S]*\/\d+_`$/,
|
49
|
+
rHtmlEncode = rAttrEncode,
|
50
|
+
viewId = 0,
|
51
|
+
charEntities = {
|
52
|
+
"&": "&",
|
53
|
+
"<": "<",
|
54
|
+
">": ">",
|
55
|
+
"\x00": "�",
|
56
|
+
"'": "'",
|
57
|
+
'"': """,
|
58
|
+
"`": "`",
|
59
|
+
"=": "="
|
60
|
+
},
|
61
|
+
HTML = "html",
|
62
|
+
OBJECT = "object",
|
63
|
+
tmplAttr = "data-jsv-tmpl",
|
64
|
+
jsvTmpl = "jsvTmpl",
|
65
|
+
indexStr = "For #index in nested block use #getIndex().",
|
66
|
+
$render = {},
|
67
|
+
|
68
|
+
jsvStores = {
|
69
|
+
template: {
|
70
|
+
compile: compileTmpl
|
71
|
+
},
|
72
|
+
tag: {
|
73
|
+
compile: compileTag
|
74
|
+
},
|
75
|
+
viewModel: {
|
76
|
+
compile: compileViewModel
|
77
|
+
},
|
78
|
+
helper: {},
|
79
|
+
converter: {}
|
80
|
+
};
|
81
|
+
|
82
|
+
// views object ($.views if jQuery is loaded, jsrender.views if no jQuery, e.g. in Node.js)
|
83
|
+
$views = {
|
84
|
+
jsviews: versionNumber,
|
85
|
+
sub: {
|
86
|
+
// subscription, e.g. JsViews integration
|
87
|
+
View: View,
|
88
|
+
Err: JsViewsError,
|
89
|
+
tmplFn: tmplFn,
|
90
|
+
parse: parseParams,
|
91
|
+
extend: $extend,
|
92
|
+
extendCtx: extendCtx,
|
93
|
+
syntaxErr: syntaxError,
|
94
|
+
onStore: {},
|
95
|
+
addSetting: addSetting,
|
96
|
+
settings: {
|
97
|
+
allowCode: false
|
98
|
+
},
|
99
|
+
advSet: noop, // Update advanced settings
|
100
|
+
_ths: tagHandlersFromProps,
|
101
|
+
_tg: function() {}, // Constructor for tagDef
|
102
|
+
_cnvt: convertVal,
|
103
|
+
_tag: renderTag,
|
104
|
+
_er: error,
|
105
|
+
_err: onRenderError,
|
106
|
+
_html: htmlEncode,
|
107
|
+
_cp: retVal, // Get compiled contextual parameters (or properties) ~foo=expr. In JsRender, simply returns val.
|
108
|
+
_sq: function(token) {
|
109
|
+
if (token === "constructor") {
|
110
|
+
syntaxError("");
|
111
|
+
}
|
112
|
+
return token;
|
113
|
+
}
|
114
|
+
},
|
115
|
+
settings: {
|
116
|
+
delimiters: $viewsDelimiters,
|
117
|
+
advanced: function(value) {
|
118
|
+
return value
|
119
|
+
? (
|
120
|
+
$extend($subSettingsAdvanced, value),
|
121
|
+
$sub.advSet(),
|
122
|
+
$viewsSettings
|
123
|
+
)
|
124
|
+
: $subSettingsAdvanced;
|
125
|
+
}
|
126
|
+
},
|
127
|
+
getCtx: retVal, // Get ctx.foo value. In JsRender, simply returns val.
|
128
|
+
map: dataMap // If jsObservable loaded first, use that definition of dataMap
|
129
|
+
};
|
130
|
+
|
131
|
+
function getDerivedMethod(baseMethod, method) {
|
132
|
+
return function() {
|
133
|
+
var ret,
|
134
|
+
tag = this,
|
135
|
+
prevBase = tag.base;
|
136
|
+
|
137
|
+
tag.base = baseMethod; // Within method call, calling this.base will call the base method
|
138
|
+
ret = method.apply(tag, arguments); // Call the method
|
139
|
+
tag.base = prevBase; // Replace this.base to be the base method of the previous call, for chained calls
|
140
|
+
return ret;
|
141
|
+
};
|
142
|
+
}
|
143
|
+
|
144
|
+
function getMethod(baseMethod, method) {
|
145
|
+
// For derived methods (or handlers declared declaratively as in {{:foo onChange=~fooChanged}} replace by a derived method, to allow using this.base(...)
|
146
|
+
// or this.baseApply(arguments) to call the base implementation. (Equivalent to this._super(...) and this._superApply(arguments) in jQuery UI)
|
147
|
+
if ($isFunction(method)) {
|
148
|
+
method = getDerivedMethod(
|
149
|
+
!baseMethod
|
150
|
+
? noop // no base method implementation, so use noop as base method
|
151
|
+
: baseMethod._d
|
152
|
+
? baseMethod // baseMethod is a derived method, so us it
|
153
|
+
: getDerivedMethod(noop, baseMethod), // baseMethod is not derived so make its base method be the noop method
|
154
|
+
method
|
155
|
+
);
|
156
|
+
method._d = 1; // Add flag that this is a derived method
|
157
|
+
}
|
158
|
+
return method;
|
159
|
+
}
|
160
|
+
|
161
|
+
function tagHandlersFromProps(tag, tagCtx) {
|
162
|
+
for (var prop in tagCtx.props) {
|
163
|
+
if (rHasHandlers.test(prop)) {
|
164
|
+
tag[prop] = getMethod(tag[prop], tagCtx.props[prop]);
|
165
|
+
// Copy over the onFoo props, convert and convertBack from tagCtx.props to tag (overrides values in tagDef).
|
166
|
+
// Note: unsupported scenario: if handlers are dynamically added ^onFoo=expression this will work, but dynamically removing will not work.
|
167
|
+
}
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
function retVal(val) {
|
172
|
+
return val;
|
173
|
+
}
|
174
|
+
|
175
|
+
function noop() {
|
176
|
+
return "";
|
177
|
+
}
|
178
|
+
|
179
|
+
function dbgBreak(val) {
|
180
|
+
// Usage examples: {{dbg:...}}, {{:~dbg(...)}}, {{dbg .../}}, {^{for ... onAfterLink=~dbg}} etc.
|
181
|
+
try {
|
182
|
+
console.log("JsRender dbg breakpoint: " + val);
|
183
|
+
throw "dbg breakpoint"; // To break here, stop on caught exceptions.
|
184
|
+
}
|
185
|
+
catch (e) {}
|
186
|
+
return this.base ? this.baseApply(arguments) : val;
|
187
|
+
}
|
188
|
+
|
189
|
+
function JsViewsError(message) {
|
190
|
+
// Error exception type for JsViews/JsRender
|
191
|
+
// Override of $.views.sub.Error is possible
|
192
|
+
this.name = ($.link ? "JsViews" : "JsRender") + " Error";
|
193
|
+
this.message = message || this.name;
|
194
|
+
}
|
195
|
+
|
196
|
+
function $extend(target, source) {
|
197
|
+
for (var name in source) {
|
198
|
+
target[name] = source[name];
|
199
|
+
}
|
200
|
+
return target;
|
201
|
+
}
|
202
|
+
|
203
|
+
(JsViewsError.prototype = new Error()).constructor = JsViewsError;
|
204
|
+
|
205
|
+
//========================== Top-level functions ==========================
|
206
|
+
|
207
|
+
//===================
|
208
|
+
// views.delimiters
|
209
|
+
//===================
|
210
|
+
|
211
|
+
function $viewsDelimiters(openChars, closeChars, link) {
|
212
|
+
// Set the tag opening and closing delimiters and 'link' character. Default is "{{", "}}" and "^"
|
213
|
+
// openChars, closeChars: opening and closing strings, each with two characters
|
214
|
+
if (!openChars) {
|
215
|
+
return $subSettings.delimiters;
|
216
|
+
}
|
217
|
+
if ($isArray(openChars)) {
|
218
|
+
return $viewsDelimiters.apply($views, openChars);
|
219
|
+
}
|
220
|
+
|
221
|
+
$subSettings.delimiters = [openChars, closeChars, linkChar = link ? link.charAt(0) : linkChar];
|
222
|
+
|
223
|
+
delimOpenChar0 = openChars.charAt(0); // Escape the characters - since they could be regex special characters
|
224
|
+
delimOpenChar1 = openChars.charAt(1);
|
225
|
+
delimCloseChar0 = closeChars.charAt(0);
|
226
|
+
delimCloseChar1 = closeChars.charAt(1);
|
227
|
+
openChars = "\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1; // Default is "{^{"
|
228
|
+
closeChars = "\\" + delimCloseChar0 + "\\" + delimCloseChar1; // Default is "}}"
|
229
|
+
// Build regex with new delimiters
|
230
|
+
// [tag (followed by / space or }) or cvtr+colon or html or code] followed by space+params then convertBack?
|
231
|
+
rTag = "(?:(\\w+(?=[\\/\\s\\" + delimCloseChar0 + "]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"
|
232
|
+
+ delimCloseChar0 + "]|\\" + delimCloseChar0 + "(?!\\" + delimCloseChar1 + "))*?)";
|
233
|
+
|
234
|
+
// Make rTag available to JsViews (or other components) for parsing binding expressions
|
235
|
+
$sub.rTag = "(?:" + rTag + ")";
|
236
|
+
// { ^? { tag+params slash? or closingTag or comment
|
237
|
+
rTag = new RegExp("(?:" + openChars + rTag + "(\\/)?|\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1 + "(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))" + closeChars, "g");
|
238
|
+
|
239
|
+
// Default: bind tagName cvt cln html code params slash bind2 closeBlk comment
|
240
|
+
// /(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}
|
241
|
+
|
242
|
+
$sub.rTmpl = new RegExp("<.*>|([^\\\\]|^)[{}]|" + openChars + ".*" + closeChars);
|
243
|
+
// $sub.rTmpl looks for html tags or { or } char not preceded by \\, or JsRender tags {{xxx}}. Each of these strings are considered
|
244
|
+
// NOT to be jQuery selectors
|
245
|
+
return $viewsSettings;
|
246
|
+
}
|
247
|
+
|
248
|
+
//=========
|
249
|
+
// View.get
|
250
|
+
//=========
|
251
|
+
|
252
|
+
function getView(inner, type) { //view.get(inner, type)
|
253
|
+
if (!type && inner !== true) {
|
254
|
+
// view.get(type)
|
255
|
+
type = inner;
|
256
|
+
inner = undefined;
|
257
|
+
}
|
258
|
+
|
259
|
+
var views, i, l, found,
|
260
|
+
view = this,
|
261
|
+
root = !type || type === "root";
|
262
|
+
// If type is undefined, returns root view (view under top view).
|
263
|
+
|
264
|
+
if (inner) {
|
265
|
+
// Go through views - this one, and all nested ones, depth-first - and return first one with given type.
|
266
|
+
// If type is undefined, i.e. view.get(true), return first child view.
|
267
|
+
found = type && view.type === type && view;
|
268
|
+
if (!found) {
|
269
|
+
views = view.views;
|
270
|
+
if (view._.useKey) {
|
271
|
+
for (i in views) {
|
272
|
+
if (found = type ? views[i].get(inner, type) : views[i]) {
|
273
|
+
break;
|
274
|
+
}
|
275
|
+
}
|
276
|
+
} else {
|
277
|
+
for (i = 0, l = views.length; !found && i < l; i++) {
|
278
|
+
found = type ? views[i].get(inner, type) : views[i];
|
279
|
+
}
|
280
|
+
}
|
281
|
+
}
|
282
|
+
} else if (root) {
|
283
|
+
// Find root view. (view whose parent is top view)
|
284
|
+
while (view.parent) {
|
285
|
+
found = view;
|
286
|
+
view = view.parent;
|
287
|
+
}
|
288
|
+
} else {
|
289
|
+
while (view && !found) {
|
290
|
+
// Go through views - this one, and all parent ones - and return first one with given type.
|
291
|
+
found = view.type === type ? view : undefined;
|
292
|
+
view = view.parent;
|
293
|
+
}
|
294
|
+
}
|
295
|
+
return found;
|
296
|
+
}
|
297
|
+
|
298
|
+
function getNestedIndex() {
|
299
|
+
var view = this.get("item");
|
300
|
+
return view ? view.index : undefined;
|
301
|
+
}
|
302
|
+
|
303
|
+
getNestedIndex.depends = function() {
|
304
|
+
return [this.get("item"), "index"];
|
305
|
+
};
|
306
|
+
|
307
|
+
function getIndex() {
|
308
|
+
return this.index;
|
309
|
+
}
|
310
|
+
|
311
|
+
getIndex.depends = "index";
|
312
|
+
|
313
|
+
//==========
|
314
|
+
// View.hlp
|
315
|
+
//==========
|
316
|
+
|
317
|
+
function getHelper(helper, isContextCb) {
|
318
|
+
// Helper method called as view.hlp(key) from compiled template, for helpers or template parameters ~foo
|
319
|
+
var wrapped, deps,
|
320
|
+
view = this,
|
321
|
+
res = view.ctx;
|
322
|
+
|
323
|
+
if (res) {
|
324
|
+
res = res[helper];
|
325
|
+
}
|
326
|
+
if (res === undefined) {
|
327
|
+
res = $helpers[helper];
|
328
|
+
}
|
329
|
+
if (res && res._cp) { // If this helper resource is a contextual parameter, ~foo=expr
|
330
|
+
if (isContextCb) { // In a context callback for a contextual param, return the [view, dependencies...] array - needed for observe call
|
331
|
+
deps = $sub._ceo(res[1].deps); // fn deps, with any exprObs cloned
|
332
|
+
deps.unshift(res[0]); // view
|
333
|
+
deps._cp = true;
|
334
|
+
return deps;
|
335
|
+
}
|
336
|
+
res = $views.getCtx(res); // If a contextual param, but not a context callback, return evaluated param - fn(data, view, j)
|
337
|
+
}
|
338
|
+
|
339
|
+
if (res) {
|
340
|
+
if ($isFunction(res) && !res._wrp) {
|
341
|
+
// If it is of type function, and not already wrapped, we will wrap it, so if called with no this pointer it will be called with the
|
342
|
+
// view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too.
|
343
|
+
// Note that helper functions on deeper paths will have specific this pointers, from the preceding path.
|
344
|
+
// For example, ~util.foo() will have the ~util object as 'this' pointer
|
345
|
+
wrapped = function() {
|
346
|
+
return res.apply((!this || this === global) ? view : this, arguments);
|
347
|
+
};
|
348
|
+
wrapped._wrp = view;
|
349
|
+
$extend(wrapped, res); // Attach same expandos (if any) to the wrapped function
|
350
|
+
}
|
351
|
+
}
|
352
|
+
return wrapped || res;
|
353
|
+
}
|
354
|
+
|
355
|
+
function getTemplate(tmpl) {
|
356
|
+
return tmpl && (tmpl.fn
|
357
|
+
? tmpl
|
358
|
+
: this.getRsc("templates", tmpl) || $templates(tmpl)); // not yet compiled
|
359
|
+
}
|
360
|
+
|
361
|
+
//==============
|
362
|
+
// views._cnvt
|
363
|
+
//==============
|
364
|
+
|
365
|
+
function convertVal(converter, view, tagCtx, onError) {
|
366
|
+
// self is template object or linkCtx object
|
367
|
+
var tag, value,
|
368
|
+
// if tagCtx is an integer, then it is the key for the compiled function to return the boundTag tagCtx
|
369
|
+
boundTag = typeof tagCtx === "number" && view.tmpl.bnds[tagCtx-1],
|
370
|
+
linkCtx = view.linkCtx; // For data-link="{cvt:...}"...
|
371
|
+
|
372
|
+
if (onError !== undefined) {
|
373
|
+
tagCtx = onError = {props: {}, args: [onError]};
|
374
|
+
} else if (boundTag) {
|
375
|
+
tagCtx = boundTag(view.data, view, $sub);
|
376
|
+
}
|
377
|
+
|
378
|
+
value = tagCtx.args[0];
|
379
|
+
if (converter || boundTag) {
|
380
|
+
tag = linkCtx && linkCtx.tag;
|
381
|
+
if (!tag) {
|
382
|
+
tag = $extend(new $sub._tg(), {
|
383
|
+
_: {
|
384
|
+
inline: !linkCtx,
|
385
|
+
bnd: boundTag,
|
386
|
+
unlinked: true
|
387
|
+
},
|
388
|
+
tagName: ":",
|
389
|
+
cvt: converter,
|
390
|
+
flow: true,
|
391
|
+
tagCtx: tagCtx
|
392
|
+
});
|
393
|
+
if (linkCtx) {
|
394
|
+
linkCtx.tag = tag;
|
395
|
+
tag.linkCtx = linkCtx;
|
396
|
+
}
|
397
|
+
tagCtx.ctx = extendCtx(tagCtx.ctx, (linkCtx ? linkCtx.view : view).ctx);
|
398
|
+
}
|
399
|
+
tag._er = onError && value;
|
400
|
+
tagHandlersFromProps(tag, tagCtx);
|
401
|
+
|
402
|
+
tagCtx.view = view;
|
403
|
+
|
404
|
+
tag.ctx = tagCtx.ctx || tag.ctx || {};
|
405
|
+
tagCtx.ctx = undefined;
|
406
|
+
|
407
|
+
value = tag.cvtArgs(converter !== "true" && converter)[0]; // If there is a convertBack but no convert, converter will be "true"
|
408
|
+
|
409
|
+
// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
|
410
|
+
value = boundTag && view._.onRender
|
411
|
+
? view._.onRender(value, view, tag)
|
412
|
+
: value;
|
413
|
+
}
|
414
|
+
return value != undefined ? value : "";
|
415
|
+
}
|
416
|
+
|
417
|
+
function convertArgs(converter) {
|
418
|
+
var tag = this,
|
419
|
+
tagCtx = tag.tagCtx,
|
420
|
+
view = tagCtx.view,
|
421
|
+
args = tagCtx.args;
|
422
|
+
|
423
|
+
converter = converter || tag.convert;
|
424
|
+
converter = converter && ("" + converter === converter
|
425
|
+
? (view.getRsc("converters", converter) || error("Unknown converter: '" + converter + "'"))
|
426
|
+
: converter);
|
427
|
+
|
428
|
+
args = !args.length && !tagCtx.index // On the opening tag with no args, bind to the current data context
|
429
|
+
? [view.data]
|
430
|
+
: converter
|
431
|
+
? args.slice() // If there is a converter, use a copy of the tagCtx.args array for rendering, and replace the args[0] in
|
432
|
+
// the copied array with the converted value. But we do not modify the value of tag.tagCtx.args[0] (the original args array)
|
433
|
+
: args; // If no converter, get the original tagCtx.args
|
434
|
+
|
435
|
+
if (converter) {
|
436
|
+
if (converter.depends) {
|
437
|
+
tag.depends = $sub.getDeps(tag.depends, tag, converter.depends, converter);
|
438
|
+
}
|
439
|
+
args[0] = converter.apply(tag, args);
|
440
|
+
}
|
441
|
+
return args;
|
442
|
+
}
|
443
|
+
|
444
|
+
//=============
|
445
|
+
// views._tag
|
446
|
+
//=============
|
447
|
+
|
448
|
+
function getResource(resourceType, itemName) {
|
449
|
+
var res, store,
|
450
|
+
view = this;
|
451
|
+
while ((res === undefined) && view) {
|
452
|
+
store = view.tmpl && view.tmpl[resourceType];
|
453
|
+
res = store && store[itemName];
|
454
|
+
view = view.parent;
|
455
|
+
}
|
456
|
+
return res || $views[resourceType][itemName];
|
457
|
+
}
|
458
|
+
|
459
|
+
function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) {
|
460
|
+
parentView = parentView || topView;
|
461
|
+
var tag, tag_, tagDef, template, tags, attr, parentTag, i, l, itemRet, tagCtx, tagCtxCtx,
|
462
|
+
content, callInit, mapDef, thisMap, args, props, initialTmpl, tagDataMap, contentCtx,
|
463
|
+
ret = "",
|
464
|
+
linkCtx = parentView.linkCtx || 0,
|
465
|
+
ctx = parentView.ctx,
|
466
|
+
parentTmpl = tmpl || parentView.tmpl,
|
467
|
+
// if tagCtx is an integer, then it is the key for the compiled function to return the boundTag tagCtxs
|
468
|
+
boundTag = typeof tagCtxs === "number" && parentView.tmpl.bnds[tagCtxs-1];
|
469
|
+
|
470
|
+
if (tagName._is === "tag") {
|
471
|
+
tag = tagName;
|
472
|
+
tagName = tag.tagName;
|
473
|
+
tagCtxs = tag.tagCtxs;
|
474
|
+
template = tag.template;
|
475
|
+
} else {
|
476
|
+
tagDef = parentView.getRsc("tags", tagName) || error("Unknown tag: {{" + tagName + "}} ");
|
477
|
+
template = tagDef.template;
|
478
|
+
}
|
479
|
+
|
480
|
+
if (onError !== undefined) {
|
481
|
+
ret += onError;
|
482
|
+
tagCtxs = onError = [{props: {}, args: []}];
|
483
|
+
} else if (boundTag) {
|
484
|
+
tagCtxs = boundTag(parentView.data, parentView, $sub);
|
485
|
+
}
|
486
|
+
|
487
|
+
l = tagCtxs.length;
|
488
|
+
for (i = 0; i < l; i++) {
|
489
|
+
tagCtx = tagCtxs[i];
|
490
|
+
if (!linkCtx || !linkCtx.tag || i && !linkCtx.tag._.inline || tag._er) {
|
491
|
+
// Initialize tagCtx
|
492
|
+
// For block tags, tagCtx.tmpl is an integer > 0
|
493
|
+
if (content = parentTmpl.tmpls && tagCtx.tmpl) {
|
494
|
+
content = tagCtx.content = parentTmpl.tmpls[content - 1];
|
495
|
+
}
|
496
|
+
tagCtx.index = i;
|
497
|
+
tagCtx.tmpl = content; // Set the tmpl property to the content of the block tag
|
498
|
+
tagCtx.render = renderContent;
|
499
|
+
tagCtx.view = parentView;
|
500
|
+
tagCtx.ctx = extendCtx(tagCtx.ctx, ctx); // Clone and extend parentView.ctx
|
501
|
+
}
|
502
|
+
if (tmpl = tagCtx.props.tmpl) {
|
503
|
+
// If the tmpl property is overridden, set the value (when initializing, or, in case of binding: ^tmpl=..., when updating)
|
504
|
+
tagCtx.tmpl = parentView.getTmpl(tmpl);
|
505
|
+
}
|
506
|
+
|
507
|
+
if (!tag) {
|
508
|
+
// This will only be hit for initial tagCtx (not for {{else}}) - if the tag instance does not exist yet
|
509
|
+
// Instantiate tag if it does not yet exist
|
510
|
+
// If the tag has not already been instantiated, we will create a new instance.
|
511
|
+
// ~tag will access the tag, even within the rendering of the template content of this tag.
|
512
|
+
// From child/descendant tags, can access using ~tag.parent, or ~parentTags.tagName
|
513
|
+
tag = new tagDef._ctr();
|
514
|
+
callInit = !!tag.init;
|
515
|
+
|
516
|
+
tag.parent = parentTag = ctx && ctx.tag;
|
517
|
+
tag.tagCtxs = tagCtxs;
|
518
|
+
tagDataMap = tag.dataMap;
|
519
|
+
|
520
|
+
if (linkCtx) {
|
521
|
+
tag._.inline = false;
|
522
|
+
linkCtx.tag = tag;
|
523
|
+
tag.linkCtx = linkCtx;
|
524
|
+
}
|
525
|
+
if (tag._.bnd = boundTag || linkCtx.fn) {
|
526
|
+
// Bound if {^{tag...}} or data-link="{tag...}"
|
527
|
+
tag._.arrVws = {};
|
528
|
+
} else if (tag.dataBoundOnly) {
|
529
|
+
error("{^{" + tagName + "}} tag must be data-bound");
|
530
|
+
}
|
531
|
+
//TODO better perf for childTags() - keep child tag.tags array, (and remove child, when disposed)
|
532
|
+
// tag.tags = [];
|
533
|
+
}
|
534
|
+
tagCtxs = tag.tagCtxs;
|
535
|
+
tagDataMap = tag.dataMap;
|
536
|
+
|
537
|
+
tagCtx.tag = tag;
|
538
|
+
if (tagDataMap && tagCtxs) {
|
539
|
+
tagCtx.map = tagCtxs[i].map; // Copy over the compiled map instance from the previous tagCtxs to the refreshed ones
|
540
|
+
}
|
541
|
+
if (!tag.flow) {
|
542
|
+
tagCtxCtx = tagCtx.ctx = tagCtx.ctx || {};
|
543
|
+
|
544
|
+
// tags hash: tag.ctx.tags, merged with parentView.ctx.tags,
|
545
|
+
tags = tag.parents = tagCtxCtx.parentTags = ctx && extendCtx(tagCtxCtx.parentTags, ctx.parentTags) || {};
|
546
|
+
if (parentTag) {
|
547
|
+
tags[parentTag.tagName] = parentTag;
|
548
|
+
//TODO better perf for childTags: parentTag.tags.push(tag);
|
549
|
+
}
|
550
|
+
tags[tag.tagName] = tagCtxCtx.tag = tag;
|
551
|
+
}
|
552
|
+
}
|
553
|
+
if (!(tag._er = onError)) {
|
554
|
+
tagHandlersFromProps(tag, tagCtxs[0]);
|
555
|
+
tag.rendering = {}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...)
|
556
|
+
for (i = 0; i < l; i++) {
|
557
|
+
tagCtx = tag.tagCtx = tagCtxs[i];
|
558
|
+
props = tagCtx.props;
|
559
|
+
args = tag.cvtArgs();
|
560
|
+
|
561
|
+
if (mapDef = props.dataMap || tagDataMap) {
|
562
|
+
if (args.length || props.dataMap) {
|
563
|
+
thisMap = tagCtx.map;
|
564
|
+
if (!thisMap || thisMap.src !== args[0] || isUpdate) {
|
565
|
+
if (thisMap && thisMap.src) {
|
566
|
+
thisMap.unmap(); // only called if observable map - not when only used in JsRender, e.g. by {{props}}
|
567
|
+
}
|
568
|
+
thisMap = tagCtx.map = mapDef.map(args[0], props, undefined, !tag._.bnd);
|
569
|
+
}
|
570
|
+
args = [thisMap.tgt];
|
571
|
+
}
|
572
|
+
}
|
573
|
+
tag.ctx = tagCtx.ctx;
|
574
|
+
|
575
|
+
if (!i) {
|
576
|
+
if (callInit) {
|
577
|
+
initialTmpl = tag.template;
|
578
|
+
tag.init(tagCtx, linkCtx, tag.ctx);
|
579
|
+
callInit = undefined;
|
580
|
+
}
|
581
|
+
if (linkCtx) {
|
582
|
+
// Set attr on linkCtx to ensure outputting to the correct target attribute.
|
583
|
+
// Setting either linkCtx.attr or this.attr in the init() allows per-instance choice of target attrib.
|
584
|
+
linkCtx.attr = tag.attr = linkCtx.attr || tag.attr;
|
585
|
+
}
|
586
|
+
attr = tag.attr;
|
587
|
+
tag._.noVws = attr && attr !== HTML;
|
588
|
+
}
|
589
|
+
|
590
|
+
itemRet = undefined;
|
591
|
+
if (tag.render) {
|
592
|
+
itemRet = tag.render.apply(tag, args);
|
593
|
+
if (parentView.linked && itemRet && tag.linkedElem && !rWrappedInViewMarker.test(itemRet)) {
|
594
|
+
// When a tag renders content from the render method, with data linking, and has a linkedElem binding, then we need to wrap with
|
595
|
+
// view markers, if absent, so the content is a view associated with the tag, which will correctly dispose bindings if deleted.
|
596
|
+
itemRet = renderWithViews($.templates(itemRet), args[0], undefined, undefined, parentView, undefined, undefined, tag);
|
597
|
+
}
|
598
|
+
}
|
599
|
+
if (!args.length) {
|
600
|
+
args = [parentView]; // no arguments - (e.g. {{else}}) get data context from view.
|
601
|
+
}
|
602
|
+
if (itemRet === undefined) {
|
603
|
+
contentCtx = args[0]; // Default data context for wrapped block content is the first argument. Defined tag.contentCtx function to override this.
|
604
|
+
if (tag.contentCtx) {
|
605
|
+
contentCtx = tag.contentCtx(contentCtx);
|
606
|
+
}
|
607
|
+
itemRet = tagCtx.render(contentCtx, true) || (isUpdate ? undefined : "");
|
608
|
+
}
|
609
|
+
// No return value from render, and no template/content tagCtx.render(...), so return undefined
|
610
|
+
ret = ret ? ret + (itemRet || "") : itemRet; // If no rendered content, this will be undefined
|
611
|
+
}
|
612
|
+
tag.rendering = undefined;
|
613
|
+
}
|
614
|
+
tag.tagCtx = tagCtxs[0];
|
615
|
+
tag.ctx = tag.tagCtx.ctx;
|
616
|
+
|
617
|
+
if (tag._.noVws) {
|
618
|
+
if (tag._.inline) {
|
619
|
+
// inline tag with attr set to "text" will insert HTML-encoded content - as if it was element-based innerText
|
620
|
+
ret = attr === "text"
|
621
|
+
? $converters.html(ret)
|
622
|
+
: "";
|
623
|
+
}
|
624
|
+
}
|
625
|
+
return boundTag && parentView._.onRender
|
626
|
+
// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
|
627
|
+
? parentView._.onRender(ret, parentView, tag)
|
628
|
+
: ret;
|
629
|
+
}
|
630
|
+
|
631
|
+
//=================
|
632
|
+
// View constructor
|
633
|
+
//=================
|
634
|
+
|
635
|
+
function View(context, type, parentView, data, template, key, onRender, contentTmpl) {
|
636
|
+
// Constructor for view object in view hierarchy. (Augmented by JsViews if JsViews is loaded)
|
637
|
+
var views, parentView_, tag, self_,
|
638
|
+
self = this,
|
639
|
+
isArray = type === "array";
|
640
|
+
|
641
|
+
self.content = contentTmpl;
|
642
|
+
self.views = isArray ? [] : {};
|
643
|
+
self.parent = parentView;
|
644
|
+
self.type = type || "top";
|
645
|
+
self.data = data;
|
646
|
+
self.tmpl = template;
|
647
|
+
// If the data is an array, this is an 'array view' with a views array for each child 'item view'
|
648
|
+
// If the data is not an array, this is an 'item view' with a views 'hash' object for any child nested views
|
649
|
+
// ._.useKey is non zero if is not an 'array view' (owning a data array). Use this as next key for adding to child views hash
|
650
|
+
self_ = self._ = {
|
651
|
+
key: 0,
|
652
|
+
useKey: isArray ? 0 : 1,
|
653
|
+
id: "" + viewId++,
|
654
|
+
onRender: onRender,
|
655
|
+
bnds: {}
|
656
|
+
};
|
657
|
+
self.linked = !!onRender;
|
658
|
+
if (parentView) {
|
659
|
+
views = parentView.views;
|
660
|
+
parentView_ = parentView._;
|
661
|
+
if (parentView_.useKey) {
|
662
|
+
// Parent is not an 'array view'. Add this view to its views object
|
663
|
+
// self._key = is the key in the parent view hash
|
664
|
+
views[self_.key = "_" + parentView_.useKey++] = self;
|
665
|
+
self.index = indexStr;
|
666
|
+
self.getIndex = getNestedIndex;
|
667
|
+
} else if (views.length === (self_.key = self.index = key)) { // Parent is an 'array view'. Add this view to its views array
|
668
|
+
views.push(self); // Adding to end of views array. (Using push when possible - better perf than splice)
|
669
|
+
} else {
|
670
|
+
views.splice(key, 0, self); // Inserting in views array
|
671
|
+
}
|
672
|
+
// If no context was passed in, use parent context
|
673
|
+
// If context was passed in, it should have been merged already with parent context
|
674
|
+
self.ctx = context || parentView.ctx;
|
675
|
+
} else {
|
676
|
+
self.ctx = context;
|
677
|
+
}
|
678
|
+
}
|
679
|
+
|
680
|
+
View.prototype = {
|
681
|
+
get: getView,
|
682
|
+
getIndex: getIndex,
|
683
|
+
getRsc: getResource,
|
684
|
+
getTmpl: getTemplate,
|
685
|
+
hlp: getHelper,
|
686
|
+
_is: "view"
|
687
|
+
};
|
688
|
+
|
689
|
+
//====================================================
|
690
|
+
// Registration
|
691
|
+
//====================================================
|
692
|
+
|
693
|
+
function compileChildResources(parentTmpl) {
|
694
|
+
var storeName, storeNames, resources;
|
695
|
+
for (storeName in jsvStores) {
|
696
|
+
storeNames = storeName + "s";
|
697
|
+
if (parentTmpl[storeNames]) {
|
698
|
+
resources = parentTmpl[storeNames]; // Resources not yet compiled
|
699
|
+
parentTmpl[storeNames] = {}; // Remove uncompiled resources
|
700
|
+
$views[storeNames](resources, parentTmpl); // Add back in the compiled resources
|
701
|
+
}
|
702
|
+
}
|
703
|
+
}
|
704
|
+
|
705
|
+
//===============
|
706
|
+
// compileTag
|
707
|
+
//===============
|
708
|
+
|
709
|
+
function compileTag(name, tagDef, parentTmpl) {
|
710
|
+
var tmpl, baseTag, prop,
|
711
|
+
compiledDef = new $sub._tg();
|
712
|
+
|
713
|
+
function Tag() {
|
714
|
+
var tag = this;
|
715
|
+
tag._ = {
|
716
|
+
inline: true,
|
717
|
+
unlinked: true
|
718
|
+
};
|
719
|
+
|
720
|
+
tag.tagName = name;
|
721
|
+
}
|
722
|
+
|
723
|
+
if ($isFunction(tagDef)) {
|
724
|
+
// Simple tag declared as function. No presenter instantation.
|
725
|
+
tagDef = {
|
726
|
+
depends: tagDef.depends,
|
727
|
+
render: tagDef
|
728
|
+
};
|
729
|
+
} else if ("" + tagDef === tagDef) {
|
730
|
+
tagDef = {template: tagDef};
|
731
|
+
}
|
732
|
+
if (baseTag = tagDef.baseTag) {
|
733
|
+
tagDef.flow = !!tagDef.flow; // Set flow property, so defaults to false even if baseTag has flow=true
|
734
|
+
tagDef.baseTag = baseTag = "" + baseTag === baseTag
|
735
|
+
? (parentTmpl && parentTmpl.tags[baseTag] || $tags[baseTag])
|
736
|
+
: baseTag;
|
737
|
+
|
738
|
+
compiledDef = $extend(compiledDef, baseTag);
|
739
|
+
|
740
|
+
for (prop in tagDef) {
|
741
|
+
compiledDef[prop] = getMethod(baseTag[prop], tagDef[prop]);
|
742
|
+
}
|
743
|
+
} else {
|
744
|
+
compiledDef = $extend(compiledDef, tagDef);
|
745
|
+
}
|
746
|
+
|
747
|
+
// Tag declared as object, used as the prototype for tag instantiation (control/presenter)
|
748
|
+
if ((tmpl = compiledDef.template) !== undefined) {
|
749
|
+
compiledDef.template = "" + tmpl === tmpl ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
|
750
|
+
}
|
751
|
+
if (compiledDef.init !== false) {
|
752
|
+
// Set init: false on tagDef if you want to provide just a render method, or render and template, but no constructor or prototype.
|
753
|
+
// so equivalent to setting tag to render function, except you can also provide a template.
|
754
|
+
(Tag.prototype = compiledDef).constructor = compiledDef._ctr = Tag;
|
755
|
+
}
|
756
|
+
|
757
|
+
if (parentTmpl) {
|
758
|
+
compiledDef._parentTmpl = parentTmpl;
|
759
|
+
}
|
760
|
+
return compiledDef;
|
761
|
+
}
|
762
|
+
|
763
|
+
function baseApply(args) {
|
764
|
+
// In derived method (or handler declared declaratively as in {{:foo onChange=~fooChanged}} can call base method,
|
765
|
+
// using this.baseApply(arguments) (Equivalent to this._superApply(arguments) in jQuery UI)
|
766
|
+
return this.base.apply(this, args);
|
767
|
+
}
|
768
|
+
|
769
|
+
//===============
|
770
|
+
// compileTmpl
|
771
|
+
//===============
|
772
|
+
|
773
|
+
function compileTmpl(name, tmpl, parentTmpl, options) {
|
774
|
+
// tmpl is either a template object, a selector for a template script block, the name of a compiled template, or a template object
|
775
|
+
|
776
|
+
//==== nested functions ====
|
777
|
+
function lookupTemplate(value) {
|
778
|
+
// If value is of type string - treat as selector, or name of compiled template
|
779
|
+
// Return the template object, if already compiled, or the markup string
|
780
|
+
var currentName, tmpl;
|
781
|
+
if (("" + value === value) || value.nodeType > 0 && (elem = value)) {
|
782
|
+
if (!elem) {
|
783
|
+
if (/^\.\/[^\\:*?"<>]*$/.test(value)) {
|
784
|
+
// tmpl="./some/file.html"
|
785
|
+
// If the template is not named, use "./some/file.html" as name.
|
786
|
+
if (tmpl = $templates[name = name || value]) {
|
787
|
+
value = tmpl;
|
788
|
+
} else {
|
789
|
+
// NODE.JS-SPECIFIC CODE:
|
790
|
+
// Load template file from the file system
|
791
|
+
// Consider supporting/using async version with callback: nodeFs.readFile(path, {encoding: "utf8"}, callback);
|
792
|
+
try {
|
793
|
+
value = nodeFs.readFileSync(value, "utf8");
|
794
|
+
}
|
795
|
+
catch(e) {
|
796
|
+
if(e && e.code == 'ENOENT') {
|
797
|
+
error("Template '" + value + "' not found at '" + e.path + "'. Use path relative to '" + rootDirPath + "'.");
|
798
|
+
}
|
799
|
+
}
|
800
|
+
// Remove BOM if necessary
|
801
|
+
value = compileTmpl(name, value.replace(/^\uFEFF/, ''), parentTmpl, options);
|
802
|
+
|
803
|
+
if (!options) {
|
804
|
+
$templates[name] = value;
|
805
|
+
}
|
806
|
+
}
|
807
|
+
}
|
808
|
+
}
|
809
|
+
elem = undefined;
|
810
|
+
} else if (!value.fn) {
|
811
|
+
value = undefined;
|
812
|
+
// If value is not a string. HTML element, or compiled template, return undefined
|
813
|
+
}
|
814
|
+
return value;
|
815
|
+
}
|
816
|
+
|
817
|
+
var elem, compiledTmpl,
|
818
|
+
tmplOrMarkup = tmpl = tmpl || "";
|
819
|
+
|
820
|
+
//==== Compile the template ====
|
821
|
+
if (options === 0) {
|
822
|
+
options = undefined;
|
823
|
+
tmplOrMarkup = lookupTemplate(tmplOrMarkup); // Top-level compile so do a template lookup
|
824
|
+
}
|
825
|
+
|
826
|
+
// If options, then this was already compiled from a (script) element template declaration.
|
827
|
+
// If not, then if tmpl is a template object, use it for options
|
828
|
+
options = options || (tmpl.markup ? tmpl : {});
|
829
|
+
options.tmplName = name;
|
830
|
+
if (parentTmpl) {
|
831
|
+
options._parentTmpl = parentTmpl;
|
832
|
+
}
|
833
|
+
// If tmpl is not a markup string or a selector string, then it must be a template object
|
834
|
+
// In that case, get it from the markup property of the object
|
835
|
+
if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = lookupTemplate(tmpl.markup))) {
|
836
|
+
if (tmplOrMarkup.fn) {
|
837
|
+
// If the string references a compiled template object, need to recompile to merge any modified options
|
838
|
+
tmplOrMarkup = tmplOrMarkup.markup;
|
839
|
+
}
|
840
|
+
}
|
841
|
+
if (tmplOrMarkup !== undefined) {
|
842
|
+
if (tmplOrMarkup.fn || tmpl.fn) {
|
843
|
+
// tmpl is already compiled, so use it
|
844
|
+
if (tmplOrMarkup.fn) {
|
845
|
+
compiledTmpl = tmplOrMarkup;
|
846
|
+
}
|
847
|
+
} else {
|
848
|
+
// tmplOrMarkup is a markup string, not a compiled template
|
849
|
+
// Create template object
|
850
|
+
tmpl = tmplObject(tmplOrMarkup, options);
|
851
|
+
// Compile to AST and then to compiled function
|
852
|
+
tmplFn(tmplOrMarkup.replace(rEscapeQuotes, "\\$&"), tmpl);
|
853
|
+
}
|
854
|
+
if (!compiledTmpl) {
|
855
|
+
compiledTmpl = $extend(function() {
|
856
|
+
return compiledTmpl.render.apply(compiledTmpl, arguments);
|
857
|
+
}, tmpl);
|
858
|
+
|
859
|
+
compileChildResources(compiledTmpl);
|
860
|
+
}
|
861
|
+
if (name && !parentTmpl && name !== jsvTmpl) {
|
862
|
+
$render[name] = compiledTmpl;
|
863
|
+
}
|
864
|
+
return compiledTmpl;
|
865
|
+
}
|
866
|
+
}
|
867
|
+
|
868
|
+
//==== /end of function compileTmpl ====
|
869
|
+
|
870
|
+
//=================
|
871
|
+
// compileViewModel
|
872
|
+
//=================
|
873
|
+
|
874
|
+
function getDefaultVal(defaultVal, data) {
|
875
|
+
return $.isFunction(defaultVal)
|
876
|
+
? defaultVal.call(data)
|
877
|
+
: defaultVal;
|
878
|
+
}
|
879
|
+
|
880
|
+
function unmapArray(modelArr) {
|
881
|
+
var i, arr = [],
|
882
|
+
l = modelArr.length;
|
883
|
+
for (i=0; i<l; i++) {
|
884
|
+
arr.push(modelArr[i].unmap());
|
885
|
+
}
|
886
|
+
return arr;
|
887
|
+
}
|
888
|
+
|
889
|
+
function compileViewModel(name, type) {
|
890
|
+
var i, constructor,
|
891
|
+
viewModels = this,
|
892
|
+
getters = type.getters,
|
893
|
+
extend = type.extend,
|
894
|
+
id = type.id,
|
895
|
+
proto = $.extend({
|
896
|
+
_is: name || "unnamed",
|
897
|
+
unmap: unmap,
|
898
|
+
merge: merge
|
899
|
+
}, extend),
|
900
|
+
args = "",
|
901
|
+
body = "",
|
902
|
+
l = getters ? getters.length : 0,
|
903
|
+
$observable = $.observable,
|
904
|
+
getterNames = {};
|
905
|
+
|
906
|
+
function GetNew(args) {
|
907
|
+
constructor.apply(this, args);
|
908
|
+
}
|
909
|
+
|
910
|
+
function vm() {
|
911
|
+
return new GetNew(arguments);
|
912
|
+
}
|
913
|
+
|
914
|
+
function iterate(data, action) {
|
915
|
+
var j, getterType, defaultVal, prop, ob,
|
916
|
+
m = getters.length;
|
917
|
+
for (j=0; j<m; j++) {
|
918
|
+
prop = getters[j];
|
919
|
+
getterType = undefined;
|
920
|
+
if (prop + "" !== prop) {
|
921
|
+
getterType = prop;
|
922
|
+
prop = getterType.getter;
|
923
|
+
}
|
924
|
+
if ((ob = data[prop]) === undefined && getterType && (defaultVal = getterType.defaultVal) !== undefined) {
|
925
|
+
ob = getDefaultVal(defaultVal, data);
|
926
|
+
}
|
927
|
+
action(ob, getterType && viewModels[getterType.type], prop);
|
928
|
+
}
|
929
|
+
}
|
930
|
+
|
931
|
+
function map(data) {
|
932
|
+
data = data + "" === data
|
933
|
+
? JSON.parse(data) // Accept JSON string
|
934
|
+
: data; // or object/array
|
935
|
+
var i, j, l, m, prop,
|
936
|
+
ob = data,
|
937
|
+
arr = [];
|
938
|
+
|
939
|
+
if ($isArray(data)) {
|
940
|
+
data = data || [];
|
941
|
+
l = data.length;
|
942
|
+
for (i=0; i<l; i++) {
|
943
|
+
arr.push(this.map(data[i]));
|
944
|
+
}
|
945
|
+
arr._is = name;
|
946
|
+
arr.unmap = unmap;
|
947
|
+
arr.merge = merge;
|
948
|
+
return arr;
|
949
|
+
}
|
950
|
+
|
951
|
+
if (data) {
|
952
|
+
iterate(data, function(ob, viewModel) {
|
953
|
+
if (viewModel) { // Iterate to build getters arg array (value, or mapped value)
|
954
|
+
ob = viewModel.map(ob);
|
955
|
+
}
|
956
|
+
arr.push(ob);
|
957
|
+
});
|
958
|
+
|
959
|
+
ob = this.apply(this, arr); // Insantiate this View Model, passing getters args array to constructor
|
960
|
+
for (prop in data) { // Copy over any other properties. that are not get/set properties
|
961
|
+
if (!getterNames[prop]) {
|
962
|
+
ob[prop] = data[prop];
|
963
|
+
}
|
964
|
+
}
|
965
|
+
}
|
966
|
+
return ob;
|
967
|
+
}
|
968
|
+
|
969
|
+
function merge(data) {
|
970
|
+
data = data + "" === data
|
971
|
+
? JSON.parse(data) // Accept JSON string
|
972
|
+
: data; // or object/array
|
973
|
+
var i, j, l, m, prop, mod, found, assigned, ob, newModArr,
|
974
|
+
model = this;
|
975
|
+
|
976
|
+
if ($isArray(model)) {
|
977
|
+
assigned = {};
|
978
|
+
newModArr = [];
|
979
|
+
l = data.length;
|
980
|
+
m = model.length;
|
981
|
+
for (i=0; i<l; i++) {
|
982
|
+
ob = data[i];
|
983
|
+
found = false;
|
984
|
+
for (j=0; j<m && !found; j++) {
|
985
|
+
if (assigned[j]) {
|
986
|
+
continue;
|
987
|
+
}
|
988
|
+
mod = model[j];
|
989
|
+
|
990
|
+
if (id) {
|
991
|
+
assigned[j] = found = id + "" === id
|
992
|
+
? (ob[id] && (getterNames[id] ? mod[id]() : mod[id]) === ob[id])
|
993
|
+
: id(mod, ob);
|
994
|
+
}
|
995
|
+
}
|
996
|
+
if (found) {
|
997
|
+
mod.merge(ob);
|
998
|
+
newModArr.push(mod);
|
999
|
+
} else {
|
1000
|
+
newModArr.push(vm.map(ob));
|
1001
|
+
}
|
1002
|
+
}
|
1003
|
+
if ($observable) {
|
1004
|
+
$observable(model).refresh(newModArr, true);
|
1005
|
+
} else {
|
1006
|
+
model.splice.apply(model, [0, model.length].concat(newModArr));
|
1007
|
+
}
|
1008
|
+
return;
|
1009
|
+
}
|
1010
|
+
iterate(data, function(ob, viewModel, getter) {
|
1011
|
+
if (viewModel) {
|
1012
|
+
model[getter]().merge(ob); // Update typed property
|
1013
|
+
} else {
|
1014
|
+
model[getter](ob); // Update non-typed property
|
1015
|
+
}
|
1016
|
+
});
|
1017
|
+
for (prop in data) {
|
1018
|
+
if (!getterNames[prop]) {
|
1019
|
+
model[prop] = data[prop];
|
1020
|
+
}
|
1021
|
+
}
|
1022
|
+
}
|
1023
|
+
|
1024
|
+
function unmap() {
|
1025
|
+
var ob, prop, i, l, getterType, arr, value,
|
1026
|
+
model = this;
|
1027
|
+
|
1028
|
+
if ($isArray(model)) {
|
1029
|
+
return unmapArray(model);
|
1030
|
+
}
|
1031
|
+
ob = {};
|
1032
|
+
l = getters.length;
|
1033
|
+
for (i=0; i<l; i++) {
|
1034
|
+
prop = getters[i];
|
1035
|
+
getterType = undefined;
|
1036
|
+
if (prop + "" !== prop) {
|
1037
|
+
getterType = prop;
|
1038
|
+
prop = getterType.getter;
|
1039
|
+
}
|
1040
|
+
value = model[prop]();
|
1041
|
+
ob[prop] = getterType && value && viewModels[getterType.type]
|
1042
|
+
? $isArray(value)
|
1043
|
+
? unmapArray(value)
|
1044
|
+
: value.unmap()
|
1045
|
+
: value;
|
1046
|
+
}
|
1047
|
+
for (prop in model) {
|
1048
|
+
if (prop !== "_is" && !getterNames[prop] && (prop.charAt(0) !== "_" || !getterNames[prop.slice(1)]) && !$.isFunction(model[prop])) {
|
1049
|
+
ob[prop] = model[prop];
|
1050
|
+
}
|
1051
|
+
}
|
1052
|
+
return ob;
|
1053
|
+
}
|
1054
|
+
|
1055
|
+
GetNew.prototype = proto;
|
1056
|
+
|
1057
|
+
for (i=0; i<l; i++) {
|
1058
|
+
(function(getter) {
|
1059
|
+
getter = getter.getter || getter;
|
1060
|
+
getterNames[getter] = i+1;
|
1061
|
+
var privField = "_" + getter;
|
1062
|
+
|
1063
|
+
args += (args ? "," : "") + getter;
|
1064
|
+
body += "this." + privField + " = " + getter + ";\n";
|
1065
|
+
proto[getter] = proto[getter] || function(val) {
|
1066
|
+
if (!arguments.length) {
|
1067
|
+
return this[privField]; // If there is no argument, use as a getter
|
1068
|
+
}
|
1069
|
+
if ($observable) {
|
1070
|
+
$observable(this).setProperty(getter, val);
|
1071
|
+
} else {
|
1072
|
+
this[privField] = val;
|
1073
|
+
}
|
1074
|
+
};
|
1075
|
+
|
1076
|
+
if ($observable) {
|
1077
|
+
proto[getter].set = proto[getter].set || function(val) {
|
1078
|
+
this[privField] = val; // Setter called by observable property change
|
1079
|
+
};
|
1080
|
+
}
|
1081
|
+
})(getters[i]);
|
1082
|
+
}
|
1083
|
+
|
1084
|
+
constructor = new Function(args, body.slice(0, -1));
|
1085
|
+
constructor.prototype = proto;
|
1086
|
+
proto.constructor = constructor;
|
1087
|
+
|
1088
|
+
vm.map = map;
|
1089
|
+
vm.getters = getters;
|
1090
|
+
vm.extend = extend;
|
1091
|
+
vm.id = id;
|
1092
|
+
return vm;
|
1093
|
+
}
|
1094
|
+
|
1095
|
+
function tmplObject(markup, options) {
|
1096
|
+
// Template object constructor
|
1097
|
+
var htmlTag,
|
1098
|
+
wrapMap = $subSettingsAdvanced._wm || {}, // Only used in JsViews. Otherwise empty: {}
|
1099
|
+
tmpl = $extend(
|
1100
|
+
{
|
1101
|
+
tmpls: [],
|
1102
|
+
links: {}, // Compiled functions for link expressions
|
1103
|
+
bnds: [],
|
1104
|
+
_is: "template",
|
1105
|
+
render: renderContent
|
1106
|
+
},
|
1107
|
+
options
|
1108
|
+
);
|
1109
|
+
|
1110
|
+
tmpl.markup = markup;
|
1111
|
+
if (!options.htmlTag) {
|
1112
|
+
// Set tmpl.tag to the top-level HTML tag used in the template, if any...
|
1113
|
+
htmlTag = rFirstElem.exec(markup);
|
1114
|
+
tmpl.htmlTag = htmlTag ? htmlTag[1].toLowerCase() : "";
|
1115
|
+
}
|
1116
|
+
htmlTag = wrapMap[tmpl.htmlTag];
|
1117
|
+
if (htmlTag && htmlTag !== wrapMap.div) {
|
1118
|
+
// When using JsViews, we trim templates which are inserted into HTML contexts where text nodes are not rendered (i.e. not 'Phrasing Content').
|
1119
|
+
// Currently not trimmed for <li> tag. (Not worth adding perf cost)
|
1120
|
+
tmpl.markup = $.trim(tmpl.markup);
|
1121
|
+
}
|
1122
|
+
|
1123
|
+
return tmpl;
|
1124
|
+
}
|
1125
|
+
|
1126
|
+
//==============
|
1127
|
+
// registerStore
|
1128
|
+
//==============
|
1129
|
+
|
1130
|
+
function registerStore(storeName, storeSettings) {
|
1131
|
+
|
1132
|
+
function theStore(name, item, parentTmpl) {
|
1133
|
+
// The store is also the function used to add items to the store. e.g. $.templates, or $.views.tags
|
1134
|
+
|
1135
|
+
// For store of name 'thing', Call as:
|
1136
|
+
// $.views.things(items[, parentTmpl]),
|
1137
|
+
// or $.views.things(name, item[, parentTmpl])
|
1138
|
+
|
1139
|
+
var onStore, compile, itemName, thisStore;
|
1140
|
+
if (name && typeof name === OBJECT && !name.nodeType && !name.markup && !name.getTgt && !(storeName === "viewModel" && name.getters || name.extend)) {
|
1141
|
+
// Call to $.views.things(items[, parentTmpl]),
|
1142
|
+
|
1143
|
+
// Adding items to the store
|
1144
|
+
// If name is a hash, then item is parentTmpl. Iterate over hash and call store for key.
|
1145
|
+
for (itemName in name) {
|
1146
|
+
theStore(itemName, name[itemName], item);
|
1147
|
+
}
|
1148
|
+
return item || $views;
|
1149
|
+
}
|
1150
|
+
// Adding a single unnamed item to the store
|
1151
|
+
if (item === undefined) {
|
1152
|
+
item = name;
|
1153
|
+
name = undefined;
|
1154
|
+
}
|
1155
|
+
if (name && "" + name !== name) { // name must be a string
|
1156
|
+
parentTmpl = item;
|
1157
|
+
item = name;
|
1158
|
+
name = undefined;
|
1159
|
+
}
|
1160
|
+
thisStore = parentTmpl
|
1161
|
+
? storeName === "viewModel"
|
1162
|
+
? parentTmpl
|
1163
|
+
: (parentTmpl[storeNames] = parentTmpl[storeNames] || {})
|
1164
|
+
: theStore;
|
1165
|
+
compile = storeSettings.compile;
|
1166
|
+
if (item === null) {
|
1167
|
+
// If item is null, delete this entry
|
1168
|
+
if (name) {
|
1169
|
+
delete thisStore[name];
|
1170
|
+
}
|
1171
|
+
} else {
|
1172
|
+
item = compile ? compile.call(thisStore, name, item, parentTmpl, 0) : item;
|
1173
|
+
if (name) {
|
1174
|
+
thisStore[name] = item;
|
1175
|
+
}
|
1176
|
+
}
|
1177
|
+
if (compile && item) {
|
1178
|
+
item._is = storeName; // Only do this for compiled objects (tags, templates...)
|
1179
|
+
}
|
1180
|
+
if (item && (onStore = $sub.onStore[storeName])) {
|
1181
|
+
// e.g. JsViews integration
|
1182
|
+
onStore(name, item, compile);
|
1183
|
+
}
|
1184
|
+
return item;
|
1185
|
+
}
|
1186
|
+
|
1187
|
+
var storeNames = storeName + "s";
|
1188
|
+
|
1189
|
+
$views[storeNames] = theStore;
|
1190
|
+
}
|
1191
|
+
|
1192
|
+
function addSetting(st) {
|
1193
|
+
$viewsSettings[st] = function(value) {
|
1194
|
+
return arguments.length
|
1195
|
+
? ($subSettings[st] = value, $viewsSettings)
|
1196
|
+
: $subSettings[st];
|
1197
|
+
};
|
1198
|
+
}
|
1199
|
+
|
1200
|
+
//=========
|
1201
|
+
// dataMap
|
1202
|
+
//=========
|
1203
|
+
|
1204
|
+
function dataMap(mapDef) {
|
1205
|
+
function Map(source, options) {
|
1206
|
+
this.tgt = mapDef.getTgt(source, options);
|
1207
|
+
}
|
1208
|
+
|
1209
|
+
if ($isFunction(mapDef)) {
|
1210
|
+
// Simple map declared as function
|
1211
|
+
mapDef = {
|
1212
|
+
getTgt: mapDef
|
1213
|
+
};
|
1214
|
+
}
|
1215
|
+
|
1216
|
+
if (mapDef.baseMap) {
|
1217
|
+
mapDef = $extend($extend({}, mapDef.baseMap), mapDef);
|
1218
|
+
}
|
1219
|
+
|
1220
|
+
mapDef.map = function(source, options) {
|
1221
|
+
return new Map(source, options);
|
1222
|
+
};
|
1223
|
+
return mapDef;
|
1224
|
+
}
|
1225
|
+
|
1226
|
+
//==============
|
1227
|
+
// renderContent
|
1228
|
+
//==============
|
1229
|
+
|
1230
|
+
function renderContent(data, context, noIteration, parentView, key, onRender) {
|
1231
|
+
var i, l, tag, tmpl, tagCtx, isTopRenderCall, prevData, prevIndex,
|
1232
|
+
view = parentView,
|
1233
|
+
result = "";
|
1234
|
+
|
1235
|
+
if (context === true) {
|
1236
|
+
noIteration = context; // passing boolean as second param - noIteration
|
1237
|
+
context = undefined;
|
1238
|
+
} else if (typeof context !== OBJECT) {
|
1239
|
+
context = undefined; // context must be a boolean (noIteration) or a plain object
|
1240
|
+
}
|
1241
|
+
|
1242
|
+
if (tag = this.tag) {
|
1243
|
+
// This is a call from renderTag or tagCtx.render(...)
|
1244
|
+
tagCtx = this;
|
1245
|
+
view = view || tagCtx.view;
|
1246
|
+
tmpl = view.getTmpl(tag.template || tagCtx.tmpl);
|
1247
|
+
if (!arguments.length) {
|
1248
|
+
data = view;
|
1249
|
+
}
|
1250
|
+
} else {
|
1251
|
+
// This is a template.render(...) call
|
1252
|
+
tmpl = this;
|
1253
|
+
}
|
1254
|
+
|
1255
|
+
if (tmpl) {
|
1256
|
+
if (!parentView && data && data._is === "view") {
|
1257
|
+
view = data; // When passing in a view to render or link (and not passing in a parent view) use the passed-in view as parentView
|
1258
|
+
}
|
1259
|
+
|
1260
|
+
if (view) {
|
1261
|
+
if (data === view) {
|
1262
|
+
// Inherit the data from the parent view.
|
1263
|
+
// This may be the contents of an {{if}} block
|
1264
|
+
data = view.data;
|
1265
|
+
}
|
1266
|
+
}
|
1267
|
+
|
1268
|
+
isTopRenderCall = !view;
|
1269
|
+
isRenderCall = isRenderCall || isTopRenderCall;
|
1270
|
+
if (!view) {
|
1271
|
+
(context = context || {}).root = data; // Provide ~root as shortcut to top-level data.
|
1272
|
+
}
|
1273
|
+
if (!isRenderCall || $subSettingsAdvanced.useViews || tmpl.useViews || view && view !== topView) {
|
1274
|
+
result = renderWithViews(tmpl, data, context, noIteration, view, key, onRender, tag);
|
1275
|
+
} else {
|
1276
|
+
if (view) { // In a block
|
1277
|
+
prevData = view.data;
|
1278
|
+
prevIndex = view.index;
|
1279
|
+
view.index = indexStr;
|
1280
|
+
} else {
|
1281
|
+
view = topView;
|
1282
|
+
view.data = data;
|
1283
|
+
view.ctx = context;
|
1284
|
+
}
|
1285
|
+
if ($isArray(data) && !noIteration) {
|
1286
|
+
// Create a view for the array, whose child views correspond to each data item. (Note: if key and parentView are passed in
|
1287
|
+
// along with parent view, treat as insert -e.g. from view.addViews - so parentView is already the view item for array)
|
1288
|
+
for (i = 0, l = data.length; i < l; i++) {
|
1289
|
+
view.index = i;
|
1290
|
+
view.data = data[i];
|
1291
|
+
result += tmpl.fn(data[i], view, $sub);
|
1292
|
+
}
|
1293
|
+
} else {
|
1294
|
+
view.data = data;
|
1295
|
+
result += tmpl.fn(data, view, $sub);
|
1296
|
+
}
|
1297
|
+
view.data = prevData;
|
1298
|
+
view.index = prevIndex;
|
1299
|
+
}
|
1300
|
+
if (isTopRenderCall) {
|
1301
|
+
isRenderCall = undefined;
|
1302
|
+
}
|
1303
|
+
}
|
1304
|
+
return result;
|
1305
|
+
}
|
1306
|
+
|
1307
|
+
function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, tag) {
|
1308
|
+
function setItemVar(item) {
|
1309
|
+
// When itemVar is specified, set modified ctx with user-named ~item
|
1310
|
+
newCtx = $extend({}, context);
|
1311
|
+
newCtx[itemVar] = item;
|
1312
|
+
}
|
1313
|
+
|
1314
|
+
// Render template against data as a tree of subviews (nested rendered template instances), or as a string (top-level template).
|
1315
|
+
// If the data is the parent view, treat as noIteration, re-render with the same data context.
|
1316
|
+
var i, l, newView, childView, itemResult, swapContent, contentTmpl, outerOnRender, tmplName, itemVar, newCtx, tagCtx,
|
1317
|
+
result = "";
|
1318
|
+
|
1319
|
+
if (tag) {
|
1320
|
+
// This is a call from renderTag or tagCtx.render(...)
|
1321
|
+
tmplName = tag.tagName;
|
1322
|
+
tagCtx = tag.tagCtx;
|
1323
|
+
context = context ? extendCtx(context, tag.ctx) : tag.ctx;
|
1324
|
+
|
1325
|
+
if (tmpl === view.content) { // {{xxx tmpl=#content}}
|
1326
|
+
contentTmpl = tmpl !== view.ctx._wrp // We are rendering the #content
|
1327
|
+
? view.ctx._wrp // #content was the tagCtx.props.tmpl wrapper of the block content - so within this view, #content will now be the view.ctx._wrp block content
|
1328
|
+
: undefined; // #content was the view.ctx._wrp block content - so within this view, there is no longer any #content to wrap.
|
1329
|
+
} else if (tmpl !== tagCtx.content) {
|
1330
|
+
if (tmpl === tag.template) { // Rendering {{tag}} tag.template, replacing block content.
|
1331
|
+
contentTmpl = tagCtx.tmpl; // Set #content to block content (or wrapped block content if tagCtx.props.tmpl is set)
|
1332
|
+
context._wrp = tagCtx.content; // Pass wrapped block content to nested views
|
1333
|
+
} else { // Rendering tagCtx.props.tmpl wrapper
|
1334
|
+
contentTmpl = tagCtx.content || view.content; // Set #content to wrapped block content
|
1335
|
+
}
|
1336
|
+
} else {
|
1337
|
+
contentTmpl = view.content; // Nested views inherit same wrapped #content property
|
1338
|
+
}
|
1339
|
+
|
1340
|
+
if (tagCtx.props.link === false) {
|
1341
|
+
// link=false setting on block tag
|
1342
|
+
// We will override inherited value of link by the explicit setting link=false taken from props
|
1343
|
+
// The child views of an unlinked view are also unlinked. So setting child back to true will not have any effect.
|
1344
|
+
context = context || {};
|
1345
|
+
context.link = false;
|
1346
|
+
}
|
1347
|
+
|
1348
|
+
if (itemVar = tagCtx.props.itemVar) {
|
1349
|
+
if (itemVar.charAt(0) !== "~") {
|
1350
|
+
syntaxError("Use itemVar='~myItem'");
|
1351
|
+
}
|
1352
|
+
itemVar = itemVar.slice(1);
|
1353
|
+
}
|
1354
|
+
}
|
1355
|
+
|
1356
|
+
if (view) {
|
1357
|
+
onRender = onRender || view._.onRender;
|
1358
|
+
context = extendCtx(context, view.ctx);
|
1359
|
+
}
|
1360
|
+
|
1361
|
+
if (key === true) {
|
1362
|
+
swapContent = true;
|
1363
|
+
key = 0;
|
1364
|
+
}
|
1365
|
+
|
1366
|
+
// If link===false, do not call onRender, so no data-linking marker nodes
|
1367
|
+
if (onRender && (context && context.link === false || tag && tag._.noVws)) {
|
1368
|
+
onRender = undefined;
|
1369
|
+
}
|
1370
|
+
outerOnRender = onRender;
|
1371
|
+
if (onRender === true) {
|
1372
|
+
// Used by view.refresh(). Don't create a new wrapper view.
|
1373
|
+
outerOnRender = undefined;
|
1374
|
+
onRender = view._.onRender;
|
1375
|
+
}
|
1376
|
+
// Set additional context on views created here, (as modified context inherited from the parent, and to be inherited by child views)
|
1377
|
+
context = tmpl.helpers
|
1378
|
+
? extendCtx(tmpl.helpers, context)
|
1379
|
+
: context;
|
1380
|
+
|
1381
|
+
newCtx = context;
|
1382
|
+
if ($isArray(data) && !noIteration) {
|
1383
|
+
// Create a view for the array, whose child views correspond to each data item. (Note: if key and view are passed in
|
1384
|
+
// along with parent view, treat as insert -e.g. from view.addViews - so view is already the view item for array)
|
1385
|
+
newView = swapContent
|
1386
|
+
? view
|
1387
|
+
: (key !== undefined && view)
|
1388
|
+
|| new View(context, "array", view, data, tmpl, key, onRender, contentTmpl);
|
1389
|
+
if (view && view._.useKey) {
|
1390
|
+
// Parent is not an 'array view'
|
1391
|
+
newView._.bnd = !tag || tag._.bnd && tag; // For array views that are data bound for collection change events, set the
|
1392
|
+
// view._.bnd property to true for top-level link() or data-link="{for}", or to the tag instance for a data-bound tag, e.g. {^{for ...}}
|
1393
|
+
}
|
1394
|
+
if (itemVar) {
|
1395
|
+
newView.it = itemVar;
|
1396
|
+
}
|
1397
|
+
itemVar = newView.it;
|
1398
|
+
for (i = 0, l = data.length; i < l; i++) {
|
1399
|
+
// Create a view for each data item.
|
1400
|
+
if (itemVar) {
|
1401
|
+
setItemVar(data[i]); // use modified ctx with user-named ~item
|
1402
|
+
}
|
1403
|
+
childView = new View(newCtx, "item", newView, data[i], tmpl, (key || 0) + i, onRender, newView.content);
|
1404
|
+
|
1405
|
+
itemResult = tmpl.fn(data[i], childView, $sub);
|
1406
|
+
result += newView._.onRender ? newView._.onRender(itemResult, childView) : itemResult;
|
1407
|
+
}
|
1408
|
+
} else {
|
1409
|
+
// Create a view for singleton data object. The type of the view will be the tag name, e.g. "if" or "myTag" except for
|
1410
|
+
// "item", "array" and "data" views. A "data" view is from programmatic render(object) against a 'singleton'.
|
1411
|
+
if (itemVar) {
|
1412
|
+
setItemVar(data);
|
1413
|
+
}
|
1414
|
+
newView = swapContent ? view : new View(newCtx, tmplName || "data", view, data, tmpl, key, onRender, contentTmpl);
|
1415
|
+
if (tag && !tag.flow) {
|
1416
|
+
newView.tag = tag;
|
1417
|
+
tag.view = newView;
|
1418
|
+
}
|
1419
|
+
result += tmpl.fn(data, newView, $sub);
|
1420
|
+
}
|
1421
|
+
return outerOnRender ? outerOnRender(result, newView) : result;
|
1422
|
+
}
|
1423
|
+
|
1424
|
+
//===========================
|
1425
|
+
// Build and compile template
|
1426
|
+
//===========================
|
1427
|
+
|
1428
|
+
// Generate a reusable function that will serve to render a template against data
|
1429
|
+
// (Compile AST then build template function)
|
1430
|
+
|
1431
|
+
function onRenderError(e, view, fallback) {
|
1432
|
+
var message = fallback !== undefined
|
1433
|
+
? $isFunction(fallback)
|
1434
|
+
? fallback.call(view.data, e, view)
|
1435
|
+
: fallback || ""
|
1436
|
+
: "{Error: " + e.message + "}";
|
1437
|
+
|
1438
|
+
if ($subSettings.onError && (fallback = $subSettings.onError.call(view.data, e, fallback && message, view)) !== undefined) {
|
1439
|
+
message = fallback; // There is a settings.debugMode(handler) onError override. Call it, and use return value (if any) to replace message
|
1440
|
+
}
|
1441
|
+
|
1442
|
+
return view && !view.linkCtx ? $converters.html(message) : message;
|
1443
|
+
}
|
1444
|
+
|
1445
|
+
function error(message) {
|
1446
|
+
throw new $sub.Err(message);
|
1447
|
+
}
|
1448
|
+
|
1449
|
+
function syntaxError(message) {
|
1450
|
+
error("Syntax error\n" + message);
|
1451
|
+
}
|
1452
|
+
|
1453
|
+
function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) {
|
1454
|
+
// Compile markup to AST (abtract syntax tree) then build the template function code from the AST nodes
|
1455
|
+
// Used for compiling templates, and also by JsViews to build functions for data link expressions
|
1456
|
+
|
1457
|
+
//==== nested functions ====
|
1458
|
+
function pushprecedingContent(shift) {
|
1459
|
+
shift -= loc;
|
1460
|
+
if (shift) {
|
1461
|
+
content.push(markup.substr(loc, shift).replace(rNewLine, "\\n"));
|
1462
|
+
}
|
1463
|
+
}
|
1464
|
+
|
1465
|
+
function blockTagCheck(tagName, block) {
|
1466
|
+
if (tagName) {
|
1467
|
+
tagName += '}}';
|
1468
|
+
// '{{include}} block has {{/for}} with no open {{for}}'
|
1469
|
+
syntaxError((
|
1470
|
+
block
|
1471
|
+
? '{{' + block + '}} block has {{/' + tagName + ' without {{' + tagName
|
1472
|
+
: 'Unmatched or missing {{/' + tagName) + ', in template:\n' + markup);
|
1473
|
+
}
|
1474
|
+
}
|
1475
|
+
|
1476
|
+
function parseTag(all, bind, tagName, converter, colon, html, codeTag, params, slash, bind2, closeBlock, index) {
|
1477
|
+
/*
|
1478
|
+
|
1479
|
+
bind tagName cvt cln html code params slash bind2 closeBlk comment
|
1480
|
+
/(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}/g
|
1481
|
+
|
1482
|
+
(?:
|
1483
|
+
{(\^)?{ bind
|
1484
|
+
(?:
|
1485
|
+
(\w+ tagName
|
1486
|
+
(?=[\/\s}])
|
1487
|
+
)
|
1488
|
+
|
|
1489
|
+
(\w+)?(:) converter colon
|
1490
|
+
|
|
1491
|
+
(>) html
|
1492
|
+
|
|
1493
|
+
(\*) codeTag
|
1494
|
+
)
|
1495
|
+
\s*
|
1496
|
+
( params
|
1497
|
+
(?:[^}]|}(?!}))*?
|
1498
|
+
)
|
1499
|
+
(\/)? slash
|
1500
|
+
|
|
1501
|
+
{(\^)?{ bind2
|
1502
|
+
(?:
|
1503
|
+
(?:\/(\w+))\s* closeBlock
|
1504
|
+
|
|
1505
|
+
!--[\s\S]*?-- comment
|
1506
|
+
)
|
1507
|
+
)
|
1508
|
+
}}/g
|
1509
|
+
|
1510
|
+
*/
|
1511
|
+
if (codeTag && bind || slash && !tagName || params && params.slice(-1) === ":" || bind2) {
|
1512
|
+
syntaxError(all);
|
1513
|
+
}
|
1514
|
+
|
1515
|
+
// Build abstract syntax tree (AST): [tagName, converter, params, content, hash, bindings, contentMarkup]
|
1516
|
+
if (html) {
|
1517
|
+
colon = ":";
|
1518
|
+
converter = HTML;
|
1519
|
+
}
|
1520
|
+
slash = slash || isLinkExpr && !hasElse;
|
1521
|
+
|
1522
|
+
var pathBindings = (bind || isLinkExpr) && [[]],
|
1523
|
+
props = "",
|
1524
|
+
args = "",
|
1525
|
+
ctxProps = "",
|
1526
|
+
paramsArgs = "",
|
1527
|
+
paramsProps = "",
|
1528
|
+
paramsCtxProps = "",
|
1529
|
+
onError = "",
|
1530
|
+
useTrigger = "",
|
1531
|
+
// Block tag if not self-closing and not {{:}} or {{>}} (special case) and not a data-link expression
|
1532
|
+
block = !slash && !colon;
|
1533
|
+
|
1534
|
+
//==== nested helper function ====
|
1535
|
+
tagName = tagName || (params = params || "#data", colon); // {{:}} is equivalent to {{:#data}}
|
1536
|
+
pushprecedingContent(index);
|
1537
|
+
loc = index + all.length; // location marker - parsed up to here
|
1538
|
+
if (codeTag) {
|
1539
|
+
if (allowCode) {
|
1540
|
+
content.push(["*", "\n" + params.replace(/^:/, "ret+= ").replace(rUnescapeQuotes, "$1") + ";\n"]);
|
1541
|
+
}
|
1542
|
+
} else if (tagName) {
|
1543
|
+
if (tagName === "else") {
|
1544
|
+
if (rTestElseIf.test(params)) {
|
1545
|
+
syntaxError('for "{{else if expr}}" use "{{else expr}}"');
|
1546
|
+
}
|
1547
|
+
pathBindings = current[7] && [[]];
|
1548
|
+
current[8] = markup.substring(current[8], index); // contentMarkup for block tag
|
1549
|
+
current = stack.pop();
|
1550
|
+
content = current[2];
|
1551
|
+
block = true;
|
1552
|
+
}
|
1553
|
+
if (params) {
|
1554
|
+
// remove newlines from the params string, to avoid compiled code errors for unterminated strings
|
1555
|
+
parseParams(params.replace(rNewLine, " "), pathBindings, tmpl)
|
1556
|
+
.replace(rBuildHash, function(all, onerror, isCtx, key, keyToken, keyValue, arg, param) {
|
1557
|
+
key = "'" + keyToken + "':";
|
1558
|
+
if (arg) {
|
1559
|
+
args += keyValue + ",";
|
1560
|
+
paramsArgs += "'" + param + "',";
|
1561
|
+
} else if (isCtx) {
|
1562
|
+
ctxProps += key + 'j._cp(' + keyValue + ',"' + param + '",view),';
|
1563
|
+
// Compiled code for evaluating tagCtx on a tag will have: ctx:{'foo':j._cp(compiledExpr, "expr", view)}
|
1564
|
+
paramsCtxProps += key + "'" + param + "',";
|
1565
|
+
} else if (onerror) {
|
1566
|
+
onError += keyValue;
|
1567
|
+
} else {
|
1568
|
+
if (keyToken === "trigger") {
|
1569
|
+
useTrigger += keyValue;
|
1570
|
+
}
|
1571
|
+
props += key + keyValue + ",";
|
1572
|
+
paramsProps += key + "'" + param + "',";
|
1573
|
+
hasHandlers = hasHandlers || rHasHandlers.test(keyToken);
|
1574
|
+
}
|
1575
|
+
return "";
|
1576
|
+
}).slice(0, -1);
|
1577
|
+
}
|
1578
|
+
|
1579
|
+
if (pathBindings && pathBindings[0]) {
|
1580
|
+
pathBindings.pop(); // Remove the bindings that was prepared for next arg. (There is always an extra one ready).
|
1581
|
+
}
|
1582
|
+
|
1583
|
+
newNode = [
|
1584
|
+
tagName,
|
1585
|
+
converter || !!convertBack || hasHandlers || "",
|
1586
|
+
block && [],
|
1587
|
+
parsedParam(paramsArgs || (tagName === ":" ? "'#data'," : ""), paramsProps, paramsCtxProps), // {{:}} equivalent to {{:#data}}
|
1588
|
+
parsedParam(args || (tagName === ":" ? "data," : ""), props, ctxProps),
|
1589
|
+
onError,
|
1590
|
+
useTrigger,
|
1591
|
+
pathBindings || 0
|
1592
|
+
];
|
1593
|
+
content.push(newNode);
|
1594
|
+
if (block) {
|
1595
|
+
stack.push(current);
|
1596
|
+
current = newNode;
|
1597
|
+
current[8] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag
|
1598
|
+
}
|
1599
|
+
} else if (closeBlock) {
|
1600
|
+
blockTagCheck(closeBlock !== current[0] && current[0] !== "else" && closeBlock, current[0]);
|
1601
|
+
current[8] = markup.substring(current[8], index); // contentMarkup for block tag
|
1602
|
+
current = stack.pop();
|
1603
|
+
}
|
1604
|
+
blockTagCheck(!current && closeBlock);
|
1605
|
+
content = current[2];
|
1606
|
+
}
|
1607
|
+
//==== /end of nested functions ====
|
1608
|
+
|
1609
|
+
var i, result, newNode, hasHandlers, bindings,
|
1610
|
+
allowCode = $subSettings.allowCode || tmpl && tmpl.allowCode
|
1611
|
+
|| $viewsSettings.allowCode === true, // include direct setting of settings.allowCode true for backward compat only
|
1612
|
+
astTop = [],
|
1613
|
+
loc = 0,
|
1614
|
+
stack = [],
|
1615
|
+
content = astTop,
|
1616
|
+
current = [,,astTop];
|
1617
|
+
|
1618
|
+
if (allowCode && tmpl._is) {
|
1619
|
+
tmpl.allowCode = allowCode;
|
1620
|
+
}
|
1621
|
+
|
1622
|
+
//TODO result = tmplFnsCache[markup]; // Only cache if template is not named and markup length < ...,
|
1623
|
+
//and there are no bindings or subtemplates?? Consider standard optimization for data-link="a.b.c"
|
1624
|
+
// if (result) {
|
1625
|
+
// tmpl.fn = result;
|
1626
|
+
// } else {
|
1627
|
+
|
1628
|
+
// result = markup;
|
1629
|
+
if (isLinkExpr) {
|
1630
|
+
if (convertBack !== undefined) {
|
1631
|
+
markup = markup.slice(0, -convertBack.length - 2) + delimCloseChar0;
|
1632
|
+
}
|
1633
|
+
markup = delimOpenChar0 + markup + delimCloseChar1;
|
1634
|
+
}
|
1635
|
+
|
1636
|
+
blockTagCheck(stack[0] && stack[0][2].pop()[0]);
|
1637
|
+
// Build the AST (abstract syntax tree) under astTop
|
1638
|
+
markup.replace(rTag, parseTag);
|
1639
|
+
|
1640
|
+
pushprecedingContent(markup.length);
|
1641
|
+
|
1642
|
+
if (loc = astTop[astTop.length - 1]) {
|
1643
|
+
blockTagCheck("" + loc !== loc && (+loc[8] === loc[8]) && loc[0]);
|
1644
|
+
}
|
1645
|
+
// result = tmplFnsCache[markup] = buildCode(astTop, tmpl);
|
1646
|
+
// }
|
1647
|
+
|
1648
|
+
if (isLinkExpr) {
|
1649
|
+
result = buildCode(astTop, markup, isLinkExpr);
|
1650
|
+
bindings = [];
|
1651
|
+
i = astTop.length;
|
1652
|
+
while (i--) {
|
1653
|
+
bindings.unshift(astTop[i][7]); // With data-link expressions, pathBindings array for tagCtx[i] is astTop[i][7]
|
1654
|
+
}
|
1655
|
+
setPaths(result, bindings);
|
1656
|
+
} else {
|
1657
|
+
result = buildCode(astTop, tmpl);
|
1658
|
+
}
|
1659
|
+
return result;
|
1660
|
+
}
|
1661
|
+
|
1662
|
+
function setPaths(fn, pathsArr) {
|
1663
|
+
var key, paths,
|
1664
|
+
i = 0,
|
1665
|
+
l = pathsArr.length;
|
1666
|
+
fn.deps = [];
|
1667
|
+
fn.paths = []; // The array of path binding (array/dictionary)s for each tag/else block's args and props
|
1668
|
+
for (; i < l; i++) {
|
1669
|
+
fn.paths.push(paths = pathsArr[i]);
|
1670
|
+
for (key in paths) {
|
1671
|
+
if (key !== "_jsvto" && paths.hasOwnProperty(key) && paths[key].length && !paths[key].skp) {
|
1672
|
+
fn.deps = fn.deps.concat(paths[key]); // deps is the concatenation of the paths arrays for the different bindings
|
1673
|
+
}
|
1674
|
+
}
|
1675
|
+
}
|
1676
|
+
}
|
1677
|
+
|
1678
|
+
function parsedParam(args, props, ctx) {
|
1679
|
+
return [args.slice(0, -1), props.slice(0, -1), ctx.slice(0, -1)];
|
1680
|
+
}
|
1681
|
+
|
1682
|
+
function paramStructure(parts, type) {
|
1683
|
+
return '\n\t'
|
1684
|
+
+ (type
|
1685
|
+
? type + ':{'
|
1686
|
+
: '')
|
1687
|
+
+ 'args:[' + parts[0] + ']'
|
1688
|
+
+ (parts[1] || !type
|
1689
|
+
? ',\n\tprops:{' + parts[1] + '}'
|
1690
|
+
: "")
|
1691
|
+
+ (parts[2] ? ',\n\tctx:{' + parts[2] + '}' : "");
|
1692
|
+
}
|
1693
|
+
|
1694
|
+
function parseParams(params, pathBindings, tmpl) {
|
1695
|
+
|
1696
|
+
function parseTokens(all, lftPrn0, lftPrn, bound, path, operator, err, eq, path2, prn, comma, lftPrn2, apos, quot, rtPrn, rtPrnDot, prn2, space, index, full) {
|
1697
|
+
// /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,
|
1698
|
+
// lftPrn0 lftPrn bound path operator err eq path2 prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space
|
1699
|
+
// (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
|
1700
|
+
function parsePath(allPath, not, object, helper, view, viewProperty, pathTokens, leafToken) {
|
1701
|
+
//rPath = /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
|
1702
|
+
// not object helper view viewProperty pathTokens leafToken
|
1703
|
+
var subPath = object === ".";
|
1704
|
+
if (object) {
|
1705
|
+
path = path.slice(not.length);
|
1706
|
+
if (/^\.?constructor$/.test(leafToken||path)) {
|
1707
|
+
syntaxError(allPath);
|
1708
|
+
}
|
1709
|
+
if (!subPath) {
|
1710
|
+
allPath = (helper
|
1711
|
+
? 'view.hlp("' + helper + '")'
|
1712
|
+
: view
|
1713
|
+
? "view"
|
1714
|
+
: "data")
|
1715
|
+
+ (leafToken
|
1716
|
+
? (viewProperty
|
1717
|
+
? "." + viewProperty
|
1718
|
+
: helper
|
1719
|
+
? ""
|
1720
|
+
: (view ? "" : "." + object)
|
1721
|
+
) + (pathTokens || "")
|
1722
|
+
: (leafToken = helper ? "" : view ? viewProperty || "" : object, ""));
|
1723
|
+
|
1724
|
+
allPath = allPath + (leafToken ? "." + leafToken : "");
|
1725
|
+
|
1726
|
+
allPath = not + (allPath.slice(0, 9) === "view.data"
|
1727
|
+
? allPath.slice(5) // convert #view.data... to data...
|
1728
|
+
: allPath);
|
1729
|
+
}
|
1730
|
+
if (bindings) {
|
1731
|
+
binds = named === "linkTo" ? (bindto = pathBindings._jsvto = pathBindings._jsvto || []) : bndCtx.bd;
|
1732
|
+
if (theOb = subPath && binds[binds.length-1]) {
|
1733
|
+
if (theOb._jsv) {
|
1734
|
+
while (theOb.sb) {
|
1735
|
+
theOb = theOb.sb;
|
1736
|
+
}
|
1737
|
+
if (theOb.bnd) {
|
1738
|
+
path = "^" + path.slice(1);
|
1739
|
+
}
|
1740
|
+
theOb.sb = path;
|
1741
|
+
theOb.bnd = theOb.bnd || path.charAt(0) === "^";
|
1742
|
+
}
|
1743
|
+
} else {
|
1744
|
+
binds.push(path);
|
1745
|
+
}
|
1746
|
+
pathStart[parenDepth] = index + (subPath ? 1 : 0);
|
1747
|
+
}
|
1748
|
+
}
|
1749
|
+
return allPath;
|
1750
|
+
}
|
1751
|
+
|
1752
|
+
//bound = bindings && bound;
|
1753
|
+
if (bound && !eq) {
|
1754
|
+
path = bound + path; // e.g. some.fn(...)^some.path - so here path is "^some.path"
|
1755
|
+
}
|
1756
|
+
operator = operator || "";
|
1757
|
+
lftPrn = lftPrn || lftPrn0 || lftPrn2;
|
1758
|
+
path = path || path2;
|
1759
|
+
// Could do this - but not worth perf cost?? :-
|
1760
|
+
// if (!path.lastIndexOf("#data.", 0)) { path = path.slice(6); } // If path starts with "#data.", remove that.
|
1761
|
+
prn = prn || prn2 || "";
|
1762
|
+
|
1763
|
+
var expr, exprFn, binds, theOb, newOb,
|
1764
|
+
rtSq = ")";
|
1765
|
+
|
1766
|
+
if (prn === "[") {
|
1767
|
+
prn ="[j._sq(";
|
1768
|
+
rtSq = ")]";
|
1769
|
+
}
|
1770
|
+
|
1771
|
+
if (err && !aposed && !quoted) {
|
1772
|
+
syntaxError(params);
|
1773
|
+
} else {
|
1774
|
+
if (bindings && rtPrnDot && !aposed && !quoted) {
|
1775
|
+
// This is a binding to a path in which an object is returned by a helper/data function/expression, e.g. foo()^x.y or (a?b:c)^x.y
|
1776
|
+
// We create a compiled function to get the object instance (which will be called when the dependent data of the subexpression changes, to return the new object, and trigger re-binding of the subsequent path)
|
1777
|
+
if (!named || boundName || bindto) {
|
1778
|
+
expr = pathStart[parenDepth - 1];
|
1779
|
+
if (full.length - 1 > index - (expr || 0)) { // We need to compile a subexpression
|
1780
|
+
expr = full.slice(expr, index + all.length);
|
1781
|
+
if (exprFn !== true) { // If not reentrant call during compilation
|
1782
|
+
binds = bindto || bndStack[parenDepth-1].bd;
|
1783
|
+
// Insert exprOb object, to be used during binding to return the computed object
|
1784
|
+
theOb = binds[binds.length-1];
|
1785
|
+
if (theOb && theOb.prm) {
|
1786
|
+
while (theOb.sb && theOb.sb.prm) {
|
1787
|
+
theOb = theOb.sb;
|
1788
|
+
}
|
1789
|
+
newOb = theOb.sb = {path: theOb.sb, bnd: theOb.bnd};
|
1790
|
+
} else {
|
1791
|
+
binds.push(newOb = {path: binds.pop()}); // Insert exprOb object, to be used during binding to return the computed object
|
1792
|
+
} // (e.g. "some.object()" in "some.object().a.b" - to be used as context for binding the following tokens "a.b")
|
1793
|
+
}
|
1794
|
+
rtPrnDot = delimOpenChar1 + ":" + expr // The parameter or function subexpression
|
1795
|
+
+ " onerror=''" // set onerror='' in order to wrap generated code with a try catch - returning '' as object instance if there is an error/missing parent
|
1796
|
+
+ delimCloseChar0;
|
1797
|
+
exprFn = tmplLinks[rtPrnDot];
|
1798
|
+
if (!exprFn) {
|
1799
|
+
tmplLinks[rtPrnDot] = true; // Flag that this exprFn (for rtPrnDot) is being compiled
|
1800
|
+
tmplLinks[rtPrnDot] = exprFn = tmplFn(rtPrnDot, tmpl, true); // Compile the expression (or use cached copy already in tmpl.links)
|
1801
|
+
}
|
1802
|
+
if (exprFn !== true && newOb) {
|
1803
|
+
// If not reentrant call during compilation
|
1804
|
+
newOb._jsv = exprFn;
|
1805
|
+
newOb.prm = bndCtx.bd;
|
1806
|
+
newOb.bnd = newOb.bnd || newOb.path && newOb.path.indexOf("^") >= 0;
|
1807
|
+
}
|
1808
|
+
}
|
1809
|
+
}
|
1810
|
+
}
|
1811
|
+
return (aposed
|
1812
|
+
// within single-quoted string
|
1813
|
+
? (aposed = !apos, (aposed ? all : lftPrn2 + '"'))
|
1814
|
+
: quoted
|
1815
|
+
// within double-quoted string
|
1816
|
+
? (quoted = !quot, (quoted ? all : lftPrn2 + '"'))
|
1817
|
+
:
|
1818
|
+
(
|
1819
|
+
(lftPrn
|
1820
|
+
? (pathStart[parenDepth] = index++, bndCtx = bndStack[++parenDepth] = {bd: []}, lftPrn)
|
1821
|
+
: "")
|
1822
|
+
+ (space
|
1823
|
+
? (parenDepth
|
1824
|
+
? ""
|
1825
|
+
// New arg or prop - so insert backspace \b (\x08) as separator for named params, used subsequently by rBuildHash, and prepare new bindings array
|
1826
|
+
: (paramIndex = full.slice(paramIndex, index), named
|
1827
|
+
? (named = boundName = bindto = false, "\b")
|
1828
|
+
: "\b,") + paramIndex + (paramIndex = index + all.length, bindings && pathBindings.push(bndCtx.bd = []), "\b")
|
1829
|
+
)
|
1830
|
+
: eq
|
1831
|
+
// named param. Remove bindings for arg and create instead bindings array for prop
|
1832
|
+
? (parenDepth && syntaxError(params), bindings && pathBindings.pop(), named = path, boundName = bound, paramIndex = index + all.length,
|
1833
|
+
bindings && ((bindings = bndCtx.bd = pathBindings[named] = []), bindings.skp = !bound), path + ':')
|
1834
|
+
: path
|
1835
|
+
// path
|
1836
|
+
? (path.split("^").join(".").replace(rPath, parsePath)
|
1837
|
+
+ (prn
|
1838
|
+
// some.fncall(
|
1839
|
+
? (bndCtx = bndStack[++parenDepth] = {bd: []}, fnCall[parenDepth] = rtSq, prn)
|
1840
|
+
: operator)
|
1841
|
+
)
|
1842
|
+
: operator
|
1843
|
+
// operator
|
1844
|
+
? operator
|
1845
|
+
: rtPrn
|
1846
|
+
// function
|
1847
|
+
? ((rtPrn = fnCall[parenDepth] || rtPrn, fnCall[parenDepth] = false, bndCtx = bndStack[--parenDepth], rtPrn)
|
1848
|
+
+ (prn // rtPrn and prn, e.g )( in (a)() or a()(), or )[ in a()[]
|
1849
|
+
? (bndCtx = bndStack[++parenDepth], fnCall[parenDepth] = rtSq, prn)
|
1850
|
+
: "")
|
1851
|
+
)
|
1852
|
+
: comma
|
1853
|
+
? (fnCall[parenDepth] || syntaxError(params), ",") // We don't allow top-level literal arrays or objects
|
1854
|
+
: lftPrn0
|
1855
|
+
? ""
|
1856
|
+
: (aposed = apos, quoted = quot, '"')
|
1857
|
+
))
|
1858
|
+
);
|
1859
|
+
}
|
1860
|
+
}
|
1861
|
+
|
1862
|
+
var named, bindto, boundName,
|
1863
|
+
quoted, // boolean for string content in double quotes
|
1864
|
+
aposed, // or in single quotes
|
1865
|
+
bindings = pathBindings && pathBindings[0], // bindings array for the first arg
|
1866
|
+
bndCtx = {bd: bindings},
|
1867
|
+
bndStack = {0: bndCtx},
|
1868
|
+
paramIndex = 0, // list,
|
1869
|
+
tmplLinks = (tmpl ? tmpl.links : bindings && (bindings.links = bindings.links || {})) || topView.tmpl.links,
|
1870
|
+
// The following are used for tracking path parsing including nested paths, such as "a.b(c^d + (e))^f", and chained computed paths such as
|
1871
|
+
// "a.b().c^d().e.f().g" - which has four chained paths, "a.b()", "^c.d()", ".e.f()" and ".g"
|
1872
|
+
parenDepth = 0,
|
1873
|
+
fnCall = {}, // We are in a function call
|
1874
|
+
pathStart = {}, // tracks the start of the current path such as c^d() in the above example
|
1875
|
+
result = (params + (tmpl ? " " : "")).replace(rParams, parseTokens);
|
1876
|
+
|
1877
|
+
return !parenDepth && result || syntaxError(params); // Syntax error if unbalanced parens in params expression
|
1878
|
+
}
|
1879
|
+
|
1880
|
+
function buildCode(ast, tmpl, isLinkExpr) {
|
1881
|
+
// Build the template function code from the AST nodes, and set as property on the passed-in template object
|
1882
|
+
// Used for compiling templates, and also by JsViews to build functions for data link expressions
|
1883
|
+
var i, node, tagName, converter, tagCtx, hasTag, hasEncoder, getsVal, hasCnvt, useCnvt, tmplBindings, pathBindings, params, boundOnErrStart, boundOnErrEnd,
|
1884
|
+
tagRender, nestedTmpls, tmplName, nestedTmpl, tagAndElses, content, markup, nextIsElse, oldCode, isElse, isGetVal, tagCtxFn, onError, tagStart, trigger,
|
1885
|
+
tmplBindingKey = 0,
|
1886
|
+
useViews = $subSettingsAdvanced.useViews || tmpl.useViews || tmpl.tags || tmpl.templates || tmpl.helpers || tmpl.converters,
|
1887
|
+
code = "",
|
1888
|
+
tmplOptions = {},
|
1889
|
+
l = ast.length;
|
1890
|
+
|
1891
|
+
if ("" + tmpl === tmpl) {
|
1892
|
+
tmplName = isLinkExpr ? 'data-link="' + tmpl.replace(rNewLine, " ").slice(1, -1) + '"' : tmpl;
|
1893
|
+
tmpl = 0;
|
1894
|
+
} else {
|
1895
|
+
tmplName = tmpl.tmplName || "unnamed";
|
1896
|
+
if (tmpl.allowCode) {
|
1897
|
+
tmplOptions.allowCode = true;
|
1898
|
+
}
|
1899
|
+
if (tmpl.debug) {
|
1900
|
+
tmplOptions.debug = true;
|
1901
|
+
}
|
1902
|
+
tmplBindings = tmpl.bnds;
|
1903
|
+
nestedTmpls = tmpl.tmpls;
|
1904
|
+
}
|
1905
|
+
for (i = 0; i < l; i++) {
|
1906
|
+
// AST nodes: [0: tagName, 1: converter, 2: content, 3: params, 4: code, 5: onError, 6: trigger, 7:pathBindings, 8: contentMarkup]
|
1907
|
+
node = ast[i];
|
1908
|
+
|
1909
|
+
// Add newline for each callout to t() c() etc. and each markup string
|
1910
|
+
if ("" + node === node) {
|
1911
|
+
// a markup string to be inserted
|
1912
|
+
code += '\n+"' + node + '"';
|
1913
|
+
} else {
|
1914
|
+
// a compiled tag expression to be inserted
|
1915
|
+
tagName = node[0];
|
1916
|
+
if (tagName === "*") {
|
1917
|
+
// Code tag: {{* }}
|
1918
|
+
code += ";\n" + node[1] + "\nret=ret";
|
1919
|
+
} else {
|
1920
|
+
converter = node[1];
|
1921
|
+
content = !isLinkExpr && node[2];
|
1922
|
+
tagCtx = paramStructure(node[3], 'params') + '},' + paramStructure(params = node[4]);
|
1923
|
+
// NODE.JS-SPECIFIC CODE:
|
1924
|
+
var prm = params[1];
|
1925
|
+
if (prm && /^'tmpl':"\.\/[^\:*?"<>]*"$/.test(prm)) {
|
1926
|
+
tmpl.refs = tmpl.refs || {}; // Used by browserify to chain require() dependencies for tmpl="./some.file.html"
|
1927
|
+
tmpl.refs[prm.slice(8,-1)] = 1;
|
1928
|
+
} // END NODE.JS-SPECIFIC CODE
|
1929
|
+
onError = node[5];
|
1930
|
+
trigger = node[6];
|
1931
|
+
markup = node[8] && node[8].replace(rUnescapeQuotes, "$1");
|
1932
|
+
if (isElse = tagName === "else") {
|
1933
|
+
if (pathBindings) {
|
1934
|
+
pathBindings.push(node[7]);
|
1935
|
+
}
|
1936
|
+
} else {
|
1937
|
+
tmplBindingKey = 0;
|
1938
|
+
if (tmplBindings && (pathBindings = node[7])) { // Array of paths, or false if not data-bound
|
1939
|
+
pathBindings = [pathBindings];
|
1940
|
+
tmplBindingKey = tmplBindings.push(1); // Add placeholder in tmplBindings for compiled function
|
1941
|
+
}
|
1942
|
+
}
|
1943
|
+
useViews = useViews || params[1] || params[2] || pathBindings || /view.(?!index)/.test(params[0]);
|
1944
|
+
// useViews is for perf optimization. For render() we only use views if necessary - for the more advanced scenarios.
|
1945
|
+
// We use views if there are props, contextual properties or args with #... (other than #index) - but you can force
|
1946
|
+
// using the full view infrastructure, (and pay a perf price) by opting in: Set useViews: true on the template, manually...
|
1947
|
+
if (isGetVal = tagName === ":") {
|
1948
|
+
if (converter) {
|
1949
|
+
tagName = converter === HTML ? ">" : converter + tagName;
|
1950
|
+
}
|
1951
|
+
} else {
|
1952
|
+
if (content) { // TODO optimize - if content.length === 0 or if there is a tmpl="..." specified - set content to null / don't run this compilation code - since content won't get used!!
|
1953
|
+
// Create template object for nested template
|
1954
|
+
nestedTmpl = tmplObject(markup, tmplOptions);
|
1955
|
+
nestedTmpl.tmplName = tmplName + "/" + tagName;
|
1956
|
+
// Compile to AST and then to compiled function
|
1957
|
+
nestedTmpl.useViews = nestedTmpl.useViews || useViews;
|
1958
|
+
buildCode(content, nestedTmpl);
|
1959
|
+
useViews = nestedTmpl.useViews;
|
1960
|
+
nestedTmpls.push(nestedTmpl);
|
1961
|
+
}
|
1962
|
+
|
1963
|
+
if (!isElse) {
|
1964
|
+
// This is not an else tag.
|
1965
|
+
tagAndElses = tagName;
|
1966
|
+
useViews = useViews || tagName && (!$tags[tagName] || !$tags[tagName].flow);
|
1967
|
+
// Switch to a new code string for this bound tag (and its elses, if it has any) - for returning the tagCtxs array
|
1968
|
+
oldCode = code;
|
1969
|
+
code = "";
|
1970
|
+
}
|
1971
|
+
nextIsElse = ast[i + 1];
|
1972
|
+
nextIsElse = nextIsElse && nextIsElse[0] === "else";
|
1973
|
+
}
|
1974
|
+
tagStart = onError ? ";\ntry{\nret+=" : "\n+";
|
1975
|
+
boundOnErrStart = "";
|
1976
|
+
boundOnErrEnd = "";
|
1977
|
+
|
1978
|
+
if (isGetVal && (pathBindings || trigger || converter && converter !== HTML)) {
|
1979
|
+
// For convertVal we need a compiled function to return the new tagCtx(s)
|
1980
|
+
tagCtxFn = new Function("data,view,j,u", " // " + tmplName + " " + tmplBindingKey + " " + tagName
|
1981
|
+
+ "\nreturn {" + tagCtx + "};");
|
1982
|
+
tagCtxFn._er = onError;
|
1983
|
+
tagCtxFn._tag = tagName;
|
1984
|
+
|
1985
|
+
if (isLinkExpr) {
|
1986
|
+
return tagCtxFn;
|
1987
|
+
}
|
1988
|
+
|
1989
|
+
setPaths(tagCtxFn, pathBindings);
|
1990
|
+
tagRender = 'c("' + converter + '",view,';
|
1991
|
+
useCnvt = true;
|
1992
|
+
boundOnErrStart = tagRender + tmplBindingKey + ",";
|
1993
|
+
boundOnErrEnd = ")";
|
1994
|
+
}
|
1995
|
+
code += (isGetVal
|
1996
|
+
? (isLinkExpr ? (onError ? "try{\n" : "") + "return " : tagStart) + (useCnvt // Call _cnvt if there is a converter: {{cnvt: ... }} or {^{cnvt: ... }}
|
1997
|
+
? (useCnvt = undefined, useViews = hasCnvt = true, tagRender + (pathBindings
|
1998
|
+
? ((tmplBindings[tmplBindingKey - 1] = tagCtxFn), tmplBindingKey) // Store the compiled tagCtxFn in tmpl.bnds, and pass the key to convertVal()
|
1999
|
+
: "{" + tagCtx + "}") + ")")
|
2000
|
+
: tagName === ">"
|
2001
|
+
? (hasEncoder = true, "h(" + params[0] + ")")
|
2002
|
+
: (getsVal = true, "((v=" + params[0] + ')!=null?v:' + (isLinkExpr ? 'null)' : '"")'))
|
2003
|
+
// Non strict equality so data-link="title{:expr}" with expr=null/undefined removes title attribute
|
2004
|
+
)
|
2005
|
+
: (hasTag = true, "\n{view:view,tmpl:" // Add this tagCtx to the compiled code for the tagCtxs to be passed to renderTag()
|
2006
|
+
+ (content ? nestedTmpls.length : "0") + "," // For block tags, pass in the key (nestedTmpls.length) to the nested content template
|
2007
|
+
+ tagCtx + "},"));
|
2008
|
+
|
2009
|
+
if (tagAndElses && !nextIsElse) {
|
2010
|
+
// This is a data-link expression or an inline tag without any elses, or the last {{else}} of an inline tag
|
2011
|
+
// We complete the code for returning the tagCtxs array
|
2012
|
+
code = "[" + code.slice(0, -1) + "]";
|
2013
|
+
tagRender = 't("' + tagAndElses + '",view,this,';
|
2014
|
+
if (isLinkExpr || pathBindings) {
|
2015
|
+
// This is a bound tag (data-link expression or inline bound tag {^{tag ...}}) so we store a compiled tagCtxs function in tmp.bnds
|
2016
|
+
code = new Function("data,view,j,u", " // " + tmplName + " " + tmplBindingKey + " " + tagAndElses + "\nreturn " + code + ";");
|
2017
|
+
code._er = onError;
|
2018
|
+
code._tag = tagAndElses;
|
2019
|
+
if (pathBindings) {
|
2020
|
+
setPaths(tmplBindings[tmplBindingKey - 1] = code, pathBindings);
|
2021
|
+
}
|
2022
|
+
if (isLinkExpr) {
|
2023
|
+
return code; // For a data-link expression we return the compiled tagCtxs function
|
2024
|
+
}
|
2025
|
+
boundOnErrStart = tagRender + tmplBindingKey + ",undefined,";
|
2026
|
+
boundOnErrEnd = ")";
|
2027
|
+
}
|
2028
|
+
|
2029
|
+
// This is the last {{else}} for an inline tag.
|
2030
|
+
// For a bound tag, pass the tagCtxs fn lookup key to renderTag.
|
2031
|
+
// For an unbound tag, include the code directly for evaluating tagCtxs array
|
2032
|
+
code = oldCode + tagStart + tagRender + (tmplBindingKey || code) + ")";
|
2033
|
+
pathBindings = 0;
|
2034
|
+
tagAndElses = 0;
|
2035
|
+
}
|
2036
|
+
if (onError) {
|
2037
|
+
useViews = true;
|
2038
|
+
code += ';\n}catch(e){ret' + (isLinkExpr ? "urn " : "+=") + boundOnErrStart + 'j._err(e,view,' + onError + ')' + boundOnErrEnd + ';}' + (isLinkExpr ? "" : 'ret=ret');
|
2039
|
+
}
|
2040
|
+
}
|
2041
|
+
}
|
2042
|
+
}
|
2043
|
+
// Include only the var references that are needed in the code
|
2044
|
+
code = "// " + tmplName
|
2045
|
+
|
2046
|
+
+ "\nvar v"
|
2047
|
+
+ (hasTag ? ",t=j._tag" : "") // has tag
|
2048
|
+
+ (hasCnvt ? ",c=j._cnvt" : "") // converter
|
2049
|
+
+ (hasEncoder ? ",h=j._html" : "") // html converter
|
2050
|
+
+ (isLinkExpr ? ";\n" : ',ret=""\n')
|
2051
|
+
+ (tmplOptions.debug ? "debugger;" : "")
|
2052
|
+
+ code
|
2053
|
+
+ (isLinkExpr ? "\n" : ";\nreturn ret;");
|
2054
|
+
|
2055
|
+
if ($subSettings.debugMode !== false) {
|
2056
|
+
code = "try {\n" + code + "\n}catch(e){\nreturn j._err(e, view);\n}";
|
2057
|
+
}
|
2058
|
+
|
2059
|
+
try {
|
2060
|
+
code = new Function("data,view,j,u", code);
|
2061
|
+
} catch (e) {
|
2062
|
+
syntaxError("Compiled template code:\n\n" + code + '\n: "' + e.message + '"');
|
2063
|
+
}
|
2064
|
+
if (tmpl) {
|
2065
|
+
tmpl.fn = code;
|
2066
|
+
tmpl.useViews = !!useViews;
|
2067
|
+
}
|
2068
|
+
return code;
|
2069
|
+
}
|
2070
|
+
|
2071
|
+
//==========
|
2072
|
+
// Utilities
|
2073
|
+
//==========
|
2074
|
+
|
2075
|
+
// Merge objects, in particular contexts which inherit from parent contexts
|
2076
|
+
function extendCtx(context, parentContext) {
|
2077
|
+
// Return copy of parentContext, unless context is defined and is different, in which case return a new merged context
|
2078
|
+
// If neither context nor parentContext are defined, return undefined
|
2079
|
+
return context && context !== parentContext
|
2080
|
+
? (parentContext
|
2081
|
+
? $extend($extend({}, parentContext), context)
|
2082
|
+
: context)
|
2083
|
+
: parentContext && $extend({}, parentContext);
|
2084
|
+
}
|
2085
|
+
|
2086
|
+
// Get character entity for HTML and Attribute encoding
|
2087
|
+
function getCharEntity(ch) {
|
2088
|
+
return charEntities[ch] || (charEntities[ch] = "&#" + ch.charCodeAt(0) + ";");
|
2089
|
+
}
|
2090
|
+
|
2091
|
+
function getTargetProps(source) {
|
2092
|
+
// this pointer is theMap - which has tagCtx.props too
|
2093
|
+
// arguments: tagCtx.args.
|
2094
|
+
var key, prop,
|
2095
|
+
props = [];
|
2096
|
+
|
2097
|
+
if (typeof source === OBJECT) {
|
2098
|
+
for (key in source) {
|
2099
|
+
prop = source[key];
|
2100
|
+
if (source.hasOwnProperty(key) && !$isFunction(prop)) {
|
2101
|
+
props.push({key: key, prop: prop});
|
2102
|
+
}
|
2103
|
+
}
|
2104
|
+
}
|
2105
|
+
return props;
|
2106
|
+
}
|
2107
|
+
|
2108
|
+
function $fnRender(data, context, noIteration) {
|
2109
|
+
var tmplElem = this.jquery && (this[0] || error('Unknown template')), // Targeted element not found for jQuery template selector such as "#myTmpl"
|
2110
|
+
tmpl = tmplElem.getAttribute(tmplAttr);
|
2111
|
+
|
2112
|
+
return renderContent.call(tmpl ? $.data(tmplElem)[jsvTmpl] : $templates(tmplElem), data, context, noIteration);
|
2113
|
+
}
|
2114
|
+
|
2115
|
+
//========================== Register converters ==========================
|
2116
|
+
|
2117
|
+
function htmlEncode(text) {
|
2118
|
+
// HTML encode: Replace < > & ' and " by corresponding entities.
|
2119
|
+
return text != undefined ? rIsHtml.test(text) && ("" + text).replace(rHtmlEncode, getCharEntity) || text : "";
|
2120
|
+
}
|
2121
|
+
|
2122
|
+
//========================== Initialize ==========================
|
2123
|
+
|
2124
|
+
$sub = $views.sub;
|
2125
|
+
$viewsSettings = $views.settings;
|
2126
|
+
|
2127
|
+
{
|
2128
|
+
// JsRender not already loaded, or loaded without jQuery, and we are now moving from jsrender namespace to jQuery namepace
|
2129
|
+
for (jsvStoreName in jsvStores) {
|
2130
|
+
registerStore(jsvStoreName, jsvStores[jsvStoreName]);
|
2131
|
+
}
|
2132
|
+
|
2133
|
+
$converters = $views.converters;
|
2134
|
+
$helpers = $views.helpers;
|
2135
|
+
$tags = $views.tags;
|
2136
|
+
|
2137
|
+
$sub._tg.prototype = {
|
2138
|
+
baseApply: baseApply,
|
2139
|
+
cvtArgs: convertArgs
|
2140
|
+
};
|
2141
|
+
|
2142
|
+
topView = $sub.topView = new View();
|
2143
|
+
|
2144
|
+
{
|
2145
|
+
$ = {};
|
2146
|
+
|
2147
|
+
$.isFunction = function(ob) {
|
2148
|
+
return typeof ob === "function";
|
2149
|
+
};
|
2150
|
+
|
2151
|
+
$.isArray = Array.isArray || function(obj) {
|
2152
|
+
return ({}.toString).call(obj) === "[object Array]";
|
2153
|
+
};
|
2154
|
+
|
2155
|
+
$.jsrender = versionNumber;
|
2156
|
+
}
|
2157
|
+
$subSettings = $sub.settings;
|
2158
|
+
$subSettings.allowCode = false;
|
2159
|
+
$isFunction = $.isFunction;
|
2160
|
+
$.render = $render;
|
2161
|
+
$.views = $views;
|
2162
|
+
$.templates = $templates = $views.templates;
|
2163
|
+
|
2164
|
+
$.compile = function(markup, options) { // For integration with Hapi (and possibly other platforms) provide standard API/signature for template compilation
|
2165
|
+
options = options || {};
|
2166
|
+
options.markup = markup;
|
2167
|
+
return $templates(options);
|
2168
|
+
};
|
2169
|
+
|
2170
|
+
for (setting in $subSettings) {
|
2171
|
+
addSetting(setting);
|
2172
|
+
}
|
2173
|
+
|
2174
|
+
($viewsSettings.debugMode = function(debugMode) {
|
2175
|
+
return debugMode === undefined
|
2176
|
+
? $subSettings.debugMode
|
2177
|
+
: (
|
2178
|
+
$subSettings.debugMode = debugMode,
|
2179
|
+
$subSettings.onError = debugMode + "" === debugMode
|
2180
|
+
? new Function("", "return '" + debugMode + "';" )
|
2181
|
+
: $isFunction(debugMode)
|
2182
|
+
? debugMode
|
2183
|
+
: undefined,
|
2184
|
+
$viewsSettings);
|
2185
|
+
})(false); // jshint ignore:line
|
2186
|
+
|
2187
|
+
$subSettingsAdvanced = $subSettings.advanced = {
|
2188
|
+
useViews: false,
|
2189
|
+
_jsv: false // For global access to JsViews store
|
2190
|
+
};
|
2191
|
+
|
2192
|
+
//========================== Register tags ==========================
|
2193
|
+
|
2194
|
+
$tags({
|
2195
|
+
"if": {
|
2196
|
+
render: function(val) {
|
2197
|
+
// This function is called once for {{if}} and once for each {{else}}.
|
2198
|
+
// We will use the tag.rendering object for carrying rendering state across the calls.
|
2199
|
+
// If not done (a previous block has not been rendered), look at expression for this block and render the block if expression is truthy
|
2200
|
+
// Otherwise return ""
|
2201
|
+
var self = this,
|
2202
|
+
tagCtx = self.tagCtx,
|
2203
|
+
ret = (self.rendering.done || !val && (arguments.length || !tagCtx.index))
|
2204
|
+
? ""
|
2205
|
+
: (self.rendering.done = true, self.selected = tagCtx.index,
|
2206
|
+
// Test is satisfied, so render content on current context. We call tagCtx.render() rather than return undefined
|
2207
|
+
// (which would also render the tmpl/content on the current context but would iterate if it is an array)
|
2208
|
+
tagCtx.render(tagCtx.view, true)); // no arg, so renders against parentView.data
|
2209
|
+
return ret;
|
2210
|
+
},
|
2211
|
+
flow: true
|
2212
|
+
},
|
2213
|
+
"for": {
|
2214
|
+
render: function(val) {
|
2215
|
+
// This function is called once for {{for}} and once for each {{else}}.
|
2216
|
+
// We will use the tag.rendering object for carrying rendering state across the calls.
|
2217
|
+
var finalElse = !arguments.length,
|
2218
|
+
value,
|
2219
|
+
self = this,
|
2220
|
+
tagCtx = self.tagCtx,
|
2221
|
+
result = "",
|
2222
|
+
done = 0;
|
2223
|
+
|
2224
|
+
if (!self.rendering.done) {
|
2225
|
+
value = finalElse ? tagCtx.view.data : val; // For the final else, defaults to current data without iteration.
|
2226
|
+
if (value !== undefined) {
|
2227
|
+
result += tagCtx.render(value, finalElse); // Iterates except on final else, if data is an array. (Use {{include}} to compose templates without array iteration)
|
2228
|
+
done += $isArray(value) ? value.length : 1;
|
2229
|
+
}
|
2230
|
+
if (self.rendering.done = done) {
|
2231
|
+
self.selected = tagCtx.index;
|
2232
|
+
}
|
2233
|
+
// If nothing was rendered we will look at the next {{else}}. Otherwise, we are done.
|
2234
|
+
}
|
2235
|
+
return result;
|
2236
|
+
},
|
2237
|
+
flow: true
|
2238
|
+
},
|
2239
|
+
props: {
|
2240
|
+
baseTag: "for",
|
2241
|
+
dataMap: dataMap(getTargetProps),
|
2242
|
+
flow: true
|
2243
|
+
},
|
2244
|
+
include: {
|
2245
|
+
flow: true
|
2246
|
+
},
|
2247
|
+
"*": {
|
2248
|
+
// {{* code... }} - Ignored if template.allowCode and $.views.settings.allowCode are false. Otherwise include code in compiled template
|
2249
|
+
render: retVal,
|
2250
|
+
flow: true
|
2251
|
+
},
|
2252
|
+
":*": {
|
2253
|
+
// {{:* returnedExpression }} - Ignored if template.allowCode and $.views.settings.allowCode are false. Otherwise include code in compiled template
|
2254
|
+
render: retVal,
|
2255
|
+
flow: true
|
2256
|
+
},
|
2257
|
+
dbg: $helpers.dbg = $converters.dbg = dbgBreak // Register {{dbg/}}, {{dbg:...}} and ~dbg() to throw and catch, as breakpoints for debugging.
|
2258
|
+
});
|
2259
|
+
|
2260
|
+
$converters({
|
2261
|
+
html: htmlEncode,
|
2262
|
+
attr: htmlEncode, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
|
2263
|
+
url: function(text) {
|
2264
|
+
// URL encoding helper.
|
2265
|
+
return text != undefined ? encodeURI("" + text) : text === null ? text : ""; // null returns null, e.g. to remove attribute. undefined returns ""
|
2266
|
+
}
|
2267
|
+
});
|
2268
|
+
}
|
2269
|
+
//========================== Define default delimiters ==========================
|
2270
|
+
$subSettings = $sub.settings;
|
2271
|
+
$isArray = ($||jsr).isArray;
|
2272
|
+
$viewsSettings.delimiters("{{", "}}", "^");
|
2273
|
+
|
2274
|
+
// NODE.JS-SPECIFIC CODE:
|
2275
|
+
var nodeFs = require('fs'),
|
2276
|
+
nodePath = require('path'),
|
2277
|
+
nodePathSep = nodePath.sep,
|
2278
|
+
rootDirPath = nodePath.resolve("./"),
|
2279
|
+
rootDirPathLen = rootDirPath.length + 1;
|
2280
|
+
|
2281
|
+
// Support for rendering templates from file system in Node.js Node, and for Express template engine integration,
|
2282
|
+
// using app.engine('html', jsrender.__express);
|
2283
|
+
$.renderFile = $.__express = function(filepath, data, callback) {
|
2284
|
+
filepath = './' + nodeFs.realpathSync(filepath).slice(rootDirPathLen).split(nodePathSep).join('/'); // Normalize to ./some/file.html
|
2285
|
+
var html = $templates(filepath).render(data);
|
2286
|
+
if (callback) {
|
2287
|
+
callback(null, html);
|
2288
|
+
}
|
2289
|
+
return html;
|
2290
|
+
};
|
2291
|
+
|
2292
|
+
$views.tags("clientTemplate", function(path) { // Custom tag to render a template in a script block, so it can be used as a client template without making an HTTP request
|
2293
|
+
return '<script id="' + path + '" type="text/x-jsrender">' + $templates(path).markup + '</script>';
|
2294
|
+
});
|
2295
|
+
|
2296
|
+
module.exports = $;
|
2297
|
+
// END NODE.JS-SPECIFIC CODE
|
2298
|
+
}(this));
|