6056 |
efrain |
1 |
/*! JsRender v1.0.7: http://jsviews.com/#jsrender */
|
|
|
2 |
/*! **VERSION FOR WEB** (For NODE.JS see http://jsviews.com/download/jsrender-node.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 2020, Boris Moore
|
|
|
9 |
* Released under the MIT License.
|
|
|
10 |
*/
|
|
|
11 |
|
|
|
12 |
//jshint -W018, -W041, -W120
|
|
|
13 |
|
|
|
14 |
(function(factory, global) {
|
|
|
15 |
// global var is the this object, which is window when running in the usual browser environment
|
|
|
16 |
var $ = global.jQuery;
|
|
|
17 |
|
|
|
18 |
if (typeof exports === "object") { // CommonJS e.g. Browserify
|
|
|
19 |
module.exports = $
|
|
|
20 |
? factory(global, $)
|
|
|
21 |
: function($) { // If no global jQuery, take optional jQuery passed as parameter: require('jsrender')(jQuery)
|
|
|
22 |
if ($ && !$.fn) {
|
|
|
23 |
throw "Provide jQuery or null";
|
|
|
24 |
}
|
|
|
25 |
return factory(global, $);
|
|
|
26 |
};
|
|
|
27 |
} else if (typeof define === "function" && define.amd) { // AMD script loader, e.g. RequireJS
|
|
|
28 |
define(function() {
|
|
|
29 |
return factory(global);
|
|
|
30 |
});
|
|
|
31 |
} else { // Browser using plain <script> tag
|
|
|
32 |
factory(global, false);
|
|
|
33 |
}
|
|
|
34 |
} (
|
|
|
35 |
|
|
|
36 |
// factory (for jsrender.js)
|
|
|
37 |
function(global, $) {
|
|
|
38 |
"use strict";
|
|
|
39 |
|
|
|
40 |
//========================== Top-level vars ==========================
|
|
|
41 |
|
|
|
42 |
// global var is the this object, which is window when running in the usual browser environment
|
|
|
43 |
var setGlobals = $ === false; // Only set globals if script block in browser (not AMD and not CommonJS)
|
|
|
44 |
|
|
|
45 |
$ = $ && $.fn ? $ : global.jQuery; // $ is jQuery passed in by CommonJS loader (Browserify), or global jQuery.
|
|
|
46 |
|
|
|
47 |
var versionNumber = "v1.0.7",
|
|
|
48 |
jsvStoreName, rTag, rTmplString, topView, $views, $expando,
|
|
|
49 |
_ocp = "_ocp", // Observable contextual parameter
|
|
|
50 |
|
|
|
51 |
$isFunction, $isArray, $templates, $converters, $helpers, $tags, $sub, $subSettings, $subSettingsAdvanced, $viewsSettings,
|
|
|
52 |
delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar, setting, baseOnError,
|
|
|
53 |
|
|
|
54 |
isRenderCall,
|
|
|
55 |
rNewLine = /[ \t]*(\r\n|\n|\r)/g,
|
|
|
56 |
rUnescapeQuotes = /\\(['"\\])/g, // Unescape quotes and trim
|
|
|
57 |
rEscapeQuotes = /['"\\]/g, // Escape quotes and \ character
|
|
|
58 |
rBuildHash = /(?:\x08|^)(onerror:)?(?:(~?)(([\w$.]+):)?([^\x08]+))\x08(,)?([^\x08]+)/gi,
|
|
|
59 |
rTestElseIf = /^if\s/,
|
|
|
60 |
rFirstElem = /<(\w+)[>\s]/,
|
|
|
61 |
rAttrEncode = /[\x00`><"'&=]/g, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
|
|
|
62 |
rIsHtml = /[\x00`><\"'&=]/,
|
|
|
63 |
rHasHandlers = /^on[A-Z]|^convert(Back)?$/,
|
|
|
64 |
rWrappedInViewMarker = /^\#\d+_`[\s\S]*\/\d+_`$/,
|
|
|
65 |
rHtmlEncode = rAttrEncode,
|
|
|
66 |
rDataEncode = /[&<>]/g,
|
|
|
67 |
rDataUnencode = /&(amp|gt|lt);/g,
|
|
|
68 |
rBracketQuote = /\[['"]?|['"]?\]/g,
|
|
|
69 |
viewId = 0,
|
|
|
70 |
charEntities = {
|
|
|
71 |
"&": "&",
|
|
|
72 |
"<": "<",
|
|
|
73 |
">": ">",
|
|
|
74 |
"\x00": "�",
|
|
|
75 |
"'": "'",
|
|
|
76 |
'"': """,
|
|
|
77 |
"`": "`",
|
|
|
78 |
"=": "="
|
|
|
79 |
},
|
|
|
80 |
charsFromEntities = {
|
|
|
81 |
amp: "&",
|
|
|
82 |
gt: ">",
|
|
|
83 |
lt: "<"
|
|
|
84 |
},
|
|
|
85 |
HTML = "html",
|
|
|
86 |
OBJECT = "object",
|
|
|
87 |
tmplAttr = "data-jsv-tmpl",
|
|
|
88 |
jsvTmpl = "jsvTmpl",
|
|
|
89 |
indexStr = "For #index in nested block use #getIndex().",
|
|
|
90 |
cpFnStore = {}, // Compiled furnctions for computed values in template expressions (properties, methods, helpers)
|
|
|
91 |
$render = {},
|
|
|
92 |
|
|
|
93 |
jsr = global.jsrender,
|
|
|
94 |
jsrToJq = jsr && $ && !$.render, // JsRender already loaded, without jQuery. but we will re-load it now to attach to jQuery
|
|
|
95 |
|
|
|
96 |
jsvStores = {
|
|
|
97 |
template: {
|
|
|
98 |
compile: compileTmpl
|
|
|
99 |
},
|
|
|
100 |
tag: {
|
|
|
101 |
compile: compileTag
|
|
|
102 |
},
|
|
|
103 |
viewModel: {
|
|
|
104 |
compile: compileViewModel
|
|
|
105 |
},
|
|
|
106 |
helper: {},
|
|
|
107 |
converter: {}
|
|
|
108 |
};
|
|
|
109 |
|
|
|
110 |
// views object ($.views if jQuery is loaded, jsrender.views if no jQuery, e.g. in Node.js)
|
|
|
111 |
$views = {
|
|
|
112 |
jsviews: versionNumber,
|
|
|
113 |
sub: {
|
|
|
114 |
// subscription, e.g. JsViews integration
|
|
|
115 |
rPath: /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
|
|
|
116 |
// not object helper view viewProperty pathTokens leafToken
|
|
|
117 |
|
|
|
118 |
rPrm: /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(~?[\w$.^]+)?\s*((\+\+|--)|\+|-|~(?![\w$])|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?(@)?[#~]?[\w$.^]+)([([])?)|(,\s*)|(?:(\()\s*)?\\?(?:(')|("))|(?:\s*(([)\]])(?=[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,
|
|
|
119 |
// lftPrn0 lftPrn bound path operator err eq path2 late prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space
|
|
|
120 |
|
|
|
121 |
View: View,
|
|
|
122 |
Err: JsViewsError,
|
|
|
123 |
tmplFn: tmplFn,
|
|
|
124 |
parse: parseParams,
|
|
|
125 |
extend: $extend,
|
|
|
126 |
extendCtx: extendCtx,
|
|
|
127 |
syntaxErr: syntaxError,
|
|
|
128 |
onStore: {
|
|
|
129 |
template: function(name, item) {
|
|
|
130 |
if (item === null) {
|
|
|
131 |
delete $render[name];
|
|
|
132 |
} else if (name) {
|
|
|
133 |
$render[name] = item;
|
|
|
134 |
}
|
|
|
135 |
}
|
|
|
136 |
},
|
|
|
137 |
addSetting: addSetting,
|
|
|
138 |
settings: {
|
|
|
139 |
allowCode: false
|
|
|
140 |
},
|
|
|
141 |
advSet: noop, // Update advanced settings
|
|
|
142 |
_thp: tagHandlersFromProps,
|
|
|
143 |
_gm: getMethod,
|
|
|
144 |
_tg: function() {}, // Constructor for tagDef
|
|
|
145 |
_cnvt: convertVal,
|
|
|
146 |
_tag: renderTag,
|
|
|
147 |
_er: error,
|
|
|
148 |
_err: onRenderError,
|
|
|
149 |
_cp: retVal, // Get observable contextual parameters (or properties) ~foo=expr. In JsRender, simply returns val.
|
|
|
150 |
_sq: function(token) {
|
|
|
151 |
if (token === "constructor") {
|
|
|
152 |
syntaxError("");
|
|
|
153 |
}
|
|
|
154 |
return token;
|
|
|
155 |
}
|
|
|
156 |
},
|
|
|
157 |
settings: {
|
|
|
158 |
delimiters: $viewsDelimiters,
|
|
|
159 |
advanced: function(value) {
|
|
|
160 |
return value
|
|
|
161 |
? (
|
|
|
162 |
$extend($subSettingsAdvanced, value),
|
|
|
163 |
$sub.advSet(),
|
|
|
164 |
$viewsSettings
|
|
|
165 |
)
|
|
|
166 |
: $subSettingsAdvanced;
|
|
|
167 |
}
|
|
|
168 |
},
|
|
|
169 |
map: dataMap // If jsObservable loaded first, use that definition of dataMap
|
|
|
170 |
};
|
|
|
171 |
|
|
|
172 |
function getDerivedMethod(baseMethod, method) {
|
|
|
173 |
return function() {
|
|
|
174 |
var ret,
|
|
|
175 |
tag = this,
|
|
|
176 |
prevBase = tag.base;
|
|
|
177 |
|
|
|
178 |
tag.base = baseMethod; // Within method call, calling this.base will call the base method
|
|
|
179 |
ret = method.apply(tag, arguments); // Call the method
|
|
|
180 |
tag.base = prevBase; // Replace this.base to be the base method of the previous call, for chained calls
|
|
|
181 |
return ret;
|
|
|
182 |
};
|
|
|
183 |
}
|
|
|
184 |
|
|
|
185 |
function getMethod(baseMethod, method) {
|
|
|
186 |
// For derived methods (or handlers declared declaratively as in {{:foo onChange=~fooChanged}} replace by a derived method, to allow using this.base(...)
|
|
|
187 |
// or this.baseApply(arguments) to call the base implementation. (Equivalent to this._super(...) and this._superApply(arguments) in jQuery UI)
|
|
|
188 |
if ($isFunction(method)) {
|
|
|
189 |
method = getDerivedMethod(
|
|
|
190 |
!baseMethod
|
|
|
191 |
? noop // no base method implementation, so use noop as base method
|
|
|
192 |
: baseMethod._d
|
|
|
193 |
? baseMethod // baseMethod is a derived method, so use it
|
|
|
194 |
: getDerivedMethod(noop, baseMethod), // baseMethod is not derived so make its base method be the noop method
|
|
|
195 |
method
|
|
|
196 |
);
|
|
|
197 |
method._d = (baseMethod && baseMethod._d || 0) + 1; // Add flag for derived method (incremented for derived of derived...)
|
|
|
198 |
}
|
|
|
199 |
return method;
|
|
|
200 |
}
|
|
|
201 |
|
|
|
202 |
function tagHandlersFromProps(tag, tagCtx) {
|
|
|
203 |
var prop,
|
|
|
204 |
props = tagCtx.props;
|
|
|
205 |
for (prop in props) {
|
|
|
206 |
if (rHasHandlers.test(prop) && !(tag[prop] && tag[prop].fix)) { // Don't override handlers with fix expando (used in datepicker and spinner)
|
|
|
207 |
tag[prop] = prop !== "convert" ? getMethod(tag.constructor.prototype[prop], props[prop]) : props[prop];
|
|
|
208 |
// Copy over the onFoo props, convert and convertBack from tagCtx.props to tag (overrides values in tagDef).
|
|
|
209 |
// Note: unsupported scenario: if handlers are dynamically added ^onFoo=expression this will work, but dynamically removing will not work.
|
|
|
210 |
}
|
|
|
211 |
}
|
|
|
212 |
}
|
|
|
213 |
|
|
|
214 |
function retVal(val) {
|
|
|
215 |
return val;
|
|
|
216 |
}
|
|
|
217 |
|
|
|
218 |
function noop() {
|
|
|
219 |
return "";
|
|
|
220 |
}
|
|
|
221 |
|
|
|
222 |
function dbgBreak(val) {
|
|
|
223 |
// Usage examples: {{dbg:...}}, {{:~dbg(...)}}, {{dbg .../}}, {^{for ... onAfterLink=~dbg}} etc.
|
|
|
224 |
try {
|
|
|
225 |
console.log("JsRender dbg breakpoint: " + val);
|
|
|
226 |
throw "dbg breakpoint"; // To break here, stop on caught exceptions.
|
|
|
227 |
}
|
|
|
228 |
catch (e) {}
|
|
|
229 |
return this.base ? this.baseApply(arguments) : val;
|
|
|
230 |
}
|
|
|
231 |
|
|
|
232 |
function JsViewsError(message) {
|
|
|
233 |
// Error exception type for JsViews/JsRender
|
|
|
234 |
// Override of $.views.sub.Error is possible
|
|
|
235 |
this.name = ($.link ? "JsViews" : "JsRender") + " Error";
|
|
|
236 |
this.message = message || this.name;
|
|
|
237 |
}
|
|
|
238 |
|
|
|
239 |
function $extend(target, source) {
|
|
|
240 |
if (target) {
|
|
|
241 |
for (var name in source) {
|
|
|
242 |
target[name] = source[name];
|
|
|
243 |
}
|
|
|
244 |
return target;
|
|
|
245 |
}
|
|
|
246 |
}
|
|
|
247 |
|
|
|
248 |
(JsViewsError.prototype = new Error()).constructor = JsViewsError;
|
|
|
249 |
|
|
|
250 |
//========================== Top-level functions ==========================
|
|
|
251 |
|
|
|
252 |
//===================
|
|
|
253 |
// views.delimiters
|
|
|
254 |
//===================
|
|
|
255 |
|
|
|
256 |
/**
|
|
|
257 |
* Set the tag opening and closing delimiters and 'link' character. Default is "{{", "}}" and "^"
|
|
|
258 |
* openChars, closeChars: opening and closing strings, each with two characters
|
|
|
259 |
* $.views.settings.delimiters(...)
|
|
|
260 |
*
|
|
|
261 |
* @param {string} openChars
|
|
|
262 |
* @param {string} [closeChars]
|
|
|
263 |
* @param {string} [link]
|
|
|
264 |
* @returns {Settings}
|
|
|
265 |
*
|
|
|
266 |
* Get delimiters
|
|
|
267 |
* delimsArray = $.views.settings.delimiters()
|
|
|
268 |
*
|
|
|
269 |
* @returns {string[]}
|
|
|
270 |
*/
|
|
|
271 |
function $viewsDelimiters(openChars, closeChars, link) {
|
|
|
272 |
if (!openChars) {
|
|
|
273 |
return $subSettings.delimiters;
|
|
|
274 |
}
|
|
|
275 |
if ($isArray(openChars)) {
|
|
|
276 |
return $viewsDelimiters.apply($views, openChars);
|
|
|
277 |
}
|
|
|
278 |
linkChar = link ? link[0] : linkChar;
|
|
|
279 |
if (!/^(\W|_){5}$/.test(openChars + closeChars + linkChar)) {
|
|
|
280 |
error("Invalid delimiters"); // Must be non-word characters, and openChars and closeChars must each be length 2
|
|
|
281 |
}
|
|
|
282 |
delimOpenChar0 = openChars[0];
|
|
|
283 |
delimOpenChar1 = openChars[1];
|
|
|
284 |
delimCloseChar0 = closeChars[0];
|
|
|
285 |
delimCloseChar1 = closeChars[1];
|
|
|
286 |
|
|
|
287 |
$subSettings.delimiters = [delimOpenChar0 + delimOpenChar1, delimCloseChar0 + delimCloseChar1, linkChar];
|
|
|
288 |
|
|
|
289 |
// Escape the characters - since they could be regex special characters
|
|
|
290 |
openChars = "\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1; // Default is "{^{"
|
|
|
291 |
closeChars = "\\" + delimCloseChar0 + "\\" + delimCloseChar1; // Default is "}}"
|
|
|
292 |
// Build regex with new delimiters
|
|
|
293 |
// [tag (followed by / space or }) or cvtr+colon or html or code] followed by space+params then convertBack?
|
|
|
294 |
rTag = "(?:(\\w+(?=[\\/\\s\\" + delimCloseChar0 + "]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"
|
|
|
295 |
+ delimCloseChar0 + "]|\\" + delimCloseChar0 + "(?!\\" + delimCloseChar1 + "))*?)";
|
|
|
296 |
|
|
|
297 |
// Make rTag available to JsViews (or other components) for parsing binding expressions
|
|
|
298 |
$sub.rTag = "(?:" + rTag + ")";
|
|
|
299 |
// { ^? { tag+params slash? or closingTag or comment
|
|
|
300 |
rTag = new RegExp("(?:" + openChars + rTag + "(\\/)?|\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1 + "(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))" + closeChars, "g");
|
|
|
301 |
|
|
|
302 |
// Default: bind tagName cvt cln html code params slash bind2 closeBlk comment
|
|
|
303 |
// /(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}
|
|
|
304 |
|
|
|
305 |
$sub.rTmpl = new RegExp("^\\s|\\s$|<.*>|([^\\\\]|^)[{}]|" + openChars + ".*" + closeChars);
|
|
|
306 |
// $sub.rTmpl looks for initial or final white space, html tags or { or } char not preceded by \\, or JsRender tags {{xxx}}.
|
|
|
307 |
// Each of these strings are considered NOT to be jQuery selectors
|
|
|
308 |
return $viewsSettings;
|
|
|
309 |
}
|
|
|
310 |
|
|
|
311 |
//=========
|
|
|
312 |
// View.get
|
|
|
313 |
//=========
|
|
|
314 |
|
|
|
315 |
function getView(inner, type) { //view.get(inner, type)
|
|
|
316 |
if (!type && inner !== true) {
|
|
|
317 |
// view.get(type)
|
|
|
318 |
type = inner;
|
|
|
319 |
inner = undefined;
|
|
|
320 |
}
|
|
|
321 |
|
|
|
322 |
var views, i, l, found,
|
|
|
323 |
view = this,
|
|
|
324 |
root = type === "root";
|
|
|
325 |
// view.get("root") returns view.root, view.get() returns view.parent, view.get(true) returns view.views[0].
|
|
|
326 |
|
|
|
327 |
if (inner) {
|
|
|
328 |
// Go through views - this one, and all nested ones, depth-first - and return first one with given type.
|
|
|
329 |
// If type is undefined, i.e. view.get(true), return first child view.
|
|
|
330 |
found = type && view.type === type && view;
|
|
|
331 |
if (!found) {
|
|
|
332 |
views = view.views;
|
|
|
333 |
if (view._.useKey) {
|
|
|
334 |
for (i in views) {
|
|
|
335 |
if (found = type ? views[i].get(inner, type) : views[i]) {
|
|
|
336 |
break;
|
|
|
337 |
}
|
|
|
338 |
}
|
|
|
339 |
} else {
|
|
|
340 |
for (i = 0, l = views.length; !found && i < l; i++) {
|
|
|
341 |
found = type ? views[i].get(inner, type) : views[i];
|
|
|
342 |
}
|
|
|
343 |
}
|
|
|
344 |
}
|
|
|
345 |
} else if (root) {
|
|
|
346 |
// Find root view. (view whose parent is top view)
|
|
|
347 |
found = view.root;
|
|
|
348 |
} else if (type) {
|
|
|
349 |
while (view && !found) {
|
|
|
350 |
// Go through views - this one, and all parent ones - and return first one with given type.
|
|
|
351 |
found = view.type === type ? view : undefined;
|
|
|
352 |
view = view.parent;
|
|
|
353 |
}
|
|
|
354 |
} else {
|
|
|
355 |
found = view.parent;
|
|
|
356 |
}
|
|
|
357 |
return found || undefined;
|
|
|
358 |
}
|
|
|
359 |
|
|
|
360 |
function getNestedIndex() {
|
|
|
361 |
var view = this.get("item");
|
|
|
362 |
return view ? view.index : undefined;
|
|
|
363 |
}
|
|
|
364 |
|
|
|
365 |
getNestedIndex.depends = function() {
|
|
|
366 |
return [this.get("item"), "index"];
|
|
|
367 |
};
|
|
|
368 |
|
|
|
369 |
function getIndex() {
|
|
|
370 |
return this.index;
|
|
|
371 |
}
|
|
|
372 |
|
|
|
373 |
getIndex.depends = "index";
|
|
|
374 |
|
|
|
375 |
//==================
|
|
|
376 |
// View.ctxPrm, etc.
|
|
|
377 |
//==================
|
|
|
378 |
|
|
|
379 |
/* Internal private: view._getOb() */
|
|
|
380 |
function getPathObject(ob, path, ltOb, fn) {
|
|
|
381 |
// Iterate through path to late paths: @a.b.c paths
|
|
|
382 |
// Return "" (or noop if leaf is a function @a.b.c(...) ) if intermediate object not yet available
|
|
|
383 |
var prevOb, tokens, l,
|
|
|
384 |
i = 0;
|
|
|
385 |
if (ltOb === 1) {
|
|
|
386 |
fn = 1;
|
|
|
387 |
ltOb = undefined;
|
|
|
388 |
}
|
|
|
389 |
// Paths like ^a^b^c or ~^a^b^c will not throw if an object in path is undefined.
|
|
|
390 |
if (path) {
|
|
|
391 |
tokens = path.split(".");
|
|
|
392 |
l = tokens.length;
|
|
|
393 |
|
|
|
394 |
for (; ob && i < l; i++) {
|
|
|
395 |
prevOb = ob;
|
|
|
396 |
ob = tokens[i] ? ob[tokens[i]] : ob;
|
|
|
397 |
}
|
|
|
398 |
}
|
|
|
399 |
if (ltOb) {
|
|
|
400 |
ltOb.lt = ltOb.lt || i<l; // If i < l there was an object in the path not yet available
|
|
|
401 |
}
|
|
|
402 |
return ob === undefined
|
|
|
403 |
? fn ? noop : ""
|
|
|
404 |
: fn ? function() {
|
|
|
405 |
return ob.apply(prevOb, arguments);
|
|
|
406 |
} : ob;
|
|
|
407 |
}
|
|
|
408 |
|
|
|
409 |
function contextParameter(key, value, get) {
|
|
|
410 |
// Helper method called as view.ctxPrm(key) for helpers or template parameters ~foo - from compiled template or from context callback
|
|
|
411 |
var wrapped, deps, res, obsCtxPrm, tagElse, callView, newRes,
|
|
|
412 |
storeView = this,
|
|
|
413 |
isUpdate = !isRenderCall && arguments.length > 1,
|
|
|
414 |
store = storeView.ctx;
|
|
|
415 |
if (key) {
|
|
|
416 |
if (!storeView._) { // tagCtx.ctxPrm() call
|
|
|
417 |
tagElse = storeView.index;
|
|
|
418 |
storeView = storeView.tag;
|
|
|
419 |
}
|
|
|
420 |
callView = storeView;
|
|
|
421 |
if (store && store.hasOwnProperty(key) || (store = $helpers).hasOwnProperty(key)) {
|
|
|
422 |
res = store[key];
|
|
|
423 |
if (key === "tag" || key === "tagCtx" || key === "root" || key === "parentTags") {
|
|
|
424 |
return res;
|
|
|
425 |
}
|
|
|
426 |
} else {
|
|
|
427 |
store = undefined;
|
|
|
428 |
}
|
|
|
429 |
if (!isRenderCall && storeView.tagCtx || storeView.linked) { // Data-linked view, or tag instance
|
|
|
430 |
if (!res || !res._cxp) {
|
|
|
431 |
// Not a contextual parameter
|
|
|
432 |
// Set storeView to tag (if this is a tag.ctxPrm() call) or to root view ("data" view of linked template)
|
|
|
433 |
storeView = storeView.tagCtx || $isFunction(res)
|
|
|
434 |
? storeView // Is a tag, not a view, or is a computed contextual parameter, so scope to the callView, no the 'scope view'
|
|
|
435 |
: (storeView = storeView.scope || storeView,
|
|
|
436 |
!storeView.isTop && storeView.ctx.tag // If this view is in a tag, set storeView to the tag
|
|
|
437 |
|| storeView);
|
|
|
438 |
if (res !== undefined && storeView.tagCtx) {
|
|
|
439 |
// If storeView is a tag, but the contextual parameter has been set at at higher level (e.g. helpers)...
|
|
|
440 |
storeView = storeView.tagCtx.view.scope; // then move storeView to the outer level (scope of tag container view)
|
|
|
441 |
}
|
|
|
442 |
store = storeView._ocps;
|
|
|
443 |
res = store && store.hasOwnProperty(key) && store[key] || res;
|
|
|
444 |
if (!(res && res._cxp) && (get || isUpdate)) {
|
|
|
445 |
// Create observable contextual parameter
|
|
|
446 |
(store || (storeView._ocps = storeView._ocps || {}))[key]
|
|
|
447 |
= res
|
|
|
448 |
= [{
|
|
|
449 |
_ocp: res, // The observable contextual parameter value
|
|
|
450 |
_vw: callView,
|
|
|
451 |
_key: key
|
|
|
452 |
}];
|
|
|
453 |
res._cxp = {
|
|
|
454 |
path: _ocp,
|
|
|
455 |
ind: 0,
|
|
|
456 |
updateValue: function(val, path) {
|
|
|
457 |
$.observable(res[0]).setProperty(_ocp, val); // Set the value (res[0]._ocp)
|
|
|
458 |
return this;
|
|
|
459 |
}
|
|
|
460 |
};
|
|
|
461 |
}
|
|
|
462 |
}
|
|
|
463 |
if (obsCtxPrm = res && res._cxp) {
|
|
|
464 |
// If this helper resource is an observable contextual parameter
|
|
|
465 |
if (arguments.length > 2) {
|
|
|
466 |
deps = res[1] ? $sub._ceo(res[1].deps) : [_ocp]; // fn deps (with any exprObs cloned using $sub._ceo)
|
|
|
467 |
deps.unshift(res[0]); // view
|
|
|
468 |
deps._cxp = obsCtxPrm;
|
|
|
469 |
// In a context callback for a contextual param, we set get = true, to get ctxPrm [view, dependencies...] array - needed for observe call
|
|
|
470 |
return deps;
|
|
|
471 |
}
|
|
|
472 |
tagElse = obsCtxPrm.tagElse;
|
|
|
473 |
newRes = res[1] // linkFn for compiled expression
|
|
|
474 |
? obsCtxPrm.tag && obsCtxPrm.tag.cvtArgs
|
|
|
475 |
? obsCtxPrm.tag.cvtArgs(tagElse, 1)[obsCtxPrm.ind] // = tag.bndArgs() - for tag contextual parameter
|
|
|
476 |
: res[1](res[0].data, res[0], $sub) // = fn(data, view, $sub) for compiled binding expression
|
|
|
477 |
: res[0]._ocp; // Observable contextual parameter (uninitialized, or initialized as static expression, so no path dependencies)
|
|
|
478 |
if (isUpdate) {
|
|
|
479 |
$sub._ucp(key, value, storeView, obsCtxPrm); // Update observable contextual parameter
|
|
|
480 |
return storeView;
|
|
|
481 |
}
|
|
|
482 |
res = newRes;
|
|
|
483 |
}
|
|
|
484 |
}
|
|
|
485 |
if (res && $isFunction(res)) {
|
|
|
486 |
// If a helper is of type function we will wrap it, so if called with no this pointer it will be called with the
|
|
|
487 |
// view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too.
|
|
|
488 |
// Note that helper functions on deeper paths will have specific this pointers, from the preceding path.
|
|
|
489 |
// For example, ~util.foo() will have the ~util object as 'this' pointer
|
|
|
490 |
wrapped = function() {
|
|
|
491 |
return res.apply((!this || this === global) ? callView : this, arguments);
|
|
|
492 |
};
|
|
|
493 |
$extend(wrapped, res); // Attach same expandos (if any) to the wrapped function
|
|
|
494 |
}
|
|
|
495 |
return wrapped || res;
|
|
|
496 |
}
|
|
|
497 |
}
|
|
|
498 |
|
|
|
499 |
/* Internal private: view._getTmpl() */
|
|
|
500 |
function getTemplate(tmpl) {
|
|
|
501 |
return tmpl && (tmpl.fn
|
|
|
502 |
? tmpl
|
|
|
503 |
: this.getRsc("templates", tmpl) || $templates(tmpl)); // not yet compiled
|
|
|
504 |
}
|
|
|
505 |
|
|
|
506 |
//==============
|
|
|
507 |
// views._cnvt
|
|
|
508 |
//==============
|
|
|
509 |
|
|
|
510 |
function convertVal(converter, view, tagCtx, onError) {
|
|
|
511 |
// Called from compiled template code for {{:}}
|
|
|
512 |
// self is template object or linkCtx object
|
|
|
513 |
var tag, linkCtx, value, argsLen, bindTo,
|
|
|
514 |
// If tagCtx is an integer, then it is the key for the compiled function to return the boundTag tagCtx
|
|
|
515 |
boundTag = typeof tagCtx === "number" && view.tmpl.bnds[tagCtx-1];
|
|
|
516 |
|
|
|
517 |
if (onError === undefined && boundTag && boundTag._lr) { // lateRender
|
|
|
518 |
onError = "";
|
|
|
519 |
}
|
|
|
520 |
if (onError !== undefined) {
|
|
|
521 |
tagCtx = onError = {props: {}, args: [onError]};
|
|
|
522 |
} else if (boundTag) {
|
|
|
523 |
tagCtx = boundTag(view.data, view, $sub);
|
|
|
524 |
}
|
|
|
525 |
boundTag = boundTag._bd && boundTag;
|
|
|
526 |
if (converter || boundTag) {
|
|
|
527 |
linkCtx = view._lc; // For data-link="{cvt:...}"... See onDataLinkedTagChange
|
|
|
528 |
tag = linkCtx && linkCtx.tag;
|
|
|
529 |
tagCtx.view = view;
|
|
|
530 |
if (!tag) {
|
|
|
531 |
tag = $extend(new $sub._tg(), {
|
|
|
532 |
_: {
|
|
|
533 |
bnd: boundTag,
|
|
|
534 |
unlinked: true,
|
|
|
535 |
lt: tagCtx.lt // If a late path @some.path has not returned @some object, mark tag as late
|
|
|
536 |
},
|
|
|
537 |
inline: !linkCtx,
|
|
|
538 |
tagName: ":",
|
|
|
539 |
convert: converter,
|
|
|
540 |
onArrayChange: true,
|
|
|
541 |
flow: true,
|
|
|
542 |
tagCtx: tagCtx,
|
|
|
543 |
tagCtxs: [tagCtx],
|
|
|
544 |
_is: "tag"
|
|
|
545 |
});
|
|
|
546 |
argsLen = tagCtx.args.length;
|
|
|
547 |
if (argsLen>1) {
|
|
|
548 |
bindTo = tag.bindTo = [];
|
|
|
549 |
while (argsLen--) {
|
|
|
550 |
bindTo.unshift(argsLen); // Bind to all the arguments - generate bindTo array: [0,1,2...]
|
|
|
551 |
}
|
|
|
552 |
}
|
|
|
553 |
if (linkCtx) {
|
|
|
554 |
linkCtx.tag = tag;
|
|
|
555 |
tag.linkCtx = linkCtx;
|
|
|
556 |
}
|
|
|
557 |
tagCtx.ctx = extendCtx(tagCtx.ctx, (linkCtx ? linkCtx.view : view).ctx);
|
|
|
558 |
tagHandlersFromProps(tag, tagCtx);
|
|
|
559 |
}
|
|
|
560 |
tag._er = onError && value;
|
|
|
561 |
tag.ctx = tagCtx.ctx || tag.ctx || {};
|
|
|
562 |
tagCtx.ctx = undefined;
|
|
|
563 |
value = tag.cvtArgs()[0]; // If there is a convertBack but no convert, converter will be "true"
|
|
|
564 |
tag._er = onError && value;
|
|
|
565 |
} else {
|
|
|
566 |
value = tagCtx.args[0];
|
|
|
567 |
}
|
|
|
568 |
|
|
|
569 |
// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
|
|
|
570 |
value = boundTag && view._.onRender
|
|
|
571 |
? view._.onRender(value, view, tag)
|
|
|
572 |
: value;
|
|
|
573 |
return value != undefined ? value : "";
|
|
|
574 |
}
|
|
|
575 |
|
|
|
576 |
function convertArgs(tagElse, bound) { // tag.cvtArgs() or tag.cvtArgs(tagElse?, true?)
|
|
|
577 |
var l, key, boundArgs, args, bindFrom, tag, converter,
|
|
|
578 |
tagCtx = this;
|
|
|
579 |
|
|
|
580 |
if (tagCtx.tagName) {
|
|
|
581 |
tag = tagCtx;
|
|
|
582 |
tagCtx = (tag.tagCtxs || [tagCtx])[tagElse||0];
|
|
|
583 |
if (!tagCtx) {
|
|
|
584 |
return;
|
|
|
585 |
}
|
|
|
586 |
} else {
|
|
|
587 |
tag = tagCtx.tag;
|
|
|
588 |
}
|
|
|
589 |
|
|
|
590 |
bindFrom = tag.bindFrom;
|
|
|
591 |
args = tagCtx.args;
|
|
|
592 |
|
|
|
593 |
if ((converter = tag.convert) && "" + converter === converter) {
|
|
|
594 |
converter = converter === "true"
|
|
|
595 |
? undefined
|
|
|
596 |
: (tagCtx.view.getRsc("converters", converter) || error("Unknown converter: '" + converter + "'"));
|
|
|
597 |
}
|
|
|
598 |
|
|
|
599 |
if (converter && !bound) { // If there is a converter, use a copy of the tagCtx.args array for rendering, and replace the args[0] in
|
|
|
600 |
args = args.slice(); // the copied array with the converted value. But we do not modify the value of tag.tagCtx.args[0] (the original args array)
|
|
|
601 |
}
|
|
|
602 |
if (bindFrom) { // Get the values of the boundArgs
|
|
|
603 |
boundArgs = [];
|
|
|
604 |
l = bindFrom.length;
|
|
|
605 |
while (l--) {
|
|
|
606 |
key = bindFrom[l];
|
|
|
607 |
boundArgs.unshift(argOrProp(tagCtx, key));
|
|
|
608 |
}
|
|
|
609 |
if (bound) {
|
|
|
610 |
args = boundArgs; // Call to bndArgs() - returns the boundArgs
|
|
|
611 |
}
|
|
|
612 |
}
|
|
|
613 |
if (converter) {
|
|
|
614 |
converter = converter.apply(tag, boundArgs || args);
|
|
|
615 |
if (converter === undefined) {
|
|
|
616 |
return args; // Returning undefined from a converter is equivalent to not having a converter.
|
|
|
617 |
}
|
|
|
618 |
bindFrom = bindFrom || [0];
|
|
|
619 |
l = bindFrom.length;
|
|
|
620 |
if (!$isArray(converter) || converter.length !== l) {
|
|
|
621 |
converter = [converter];
|
|
|
622 |
bindFrom = [0];
|
|
|
623 |
l = 1;
|
|
|
624 |
}
|
|
|
625 |
if (bound) { // Call to bndArgs() - so apply converter to all boundArgs
|
|
|
626 |
args = converter; // The array of values returned from the converter
|
|
|
627 |
} else { // Call to cvtArgs()
|
|
|
628 |
while (l--) {
|
|
|
629 |
key = bindFrom[l];
|
|
|
630 |
if (+key === key) {
|
|
|
631 |
args[key] = converter[l];
|
|
|
632 |
}
|
|
|
633 |
}
|
|
|
634 |
}
|
|
|
635 |
}
|
|
|
636 |
return args;
|
|
|
637 |
}
|
|
|
638 |
|
|
|
639 |
function argOrProp(context, key) {
|
|
|
640 |
context = context[+key === key ? "args" : "props"];
|
|
|
641 |
return context && context[key];
|
|
|
642 |
}
|
|
|
643 |
|
|
|
644 |
function convertBoundArgs(tagElse) { // tag.bndArgs()
|
|
|
645 |
return this.cvtArgs(tagElse, 1);
|
|
|
646 |
}
|
|
|
647 |
|
|
|
648 |
//=============
|
|
|
649 |
// views.tag
|
|
|
650 |
//=============
|
|
|
651 |
|
|
|
652 |
/* view.getRsc() */
|
|
|
653 |
function getResource(resourceType, itemName) {
|
|
|
654 |
var res, store,
|
|
|
655 |
view = this;
|
|
|
656 |
if ("" + itemName === itemName) {
|
|
|
657 |
while ((res === undefined) && view) {
|
|
|
658 |
store = view.tmpl && view.tmpl[resourceType];
|
|
|
659 |
res = store && store[itemName];
|
|
|
660 |
view = view.parent;
|
|
|
661 |
}
|
|
|
662 |
return res || $views[resourceType][itemName];
|
|
|
663 |
}
|
|
|
664 |
}
|
|
|
665 |
|
|
|
666 |
function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) {
|
|
|
667 |
function bindToOrBindFrom(type) {
|
|
|
668 |
var bindArray = tag[type];
|
|
|
669 |
|
|
|
670 |
if (bindArray !== undefined) {
|
|
|
671 |
bindArray = $isArray(bindArray) ? bindArray : [bindArray];
|
|
|
672 |
m = bindArray.length;
|
|
|
673 |
while (m--) {
|
|
|
674 |
key = bindArray[m];
|
|
|
675 |
if (!isNaN(parseInt(key))) {
|
|
|
676 |
bindArray[m] = parseInt(key); // Convert "0" to 0, etc.
|
|
|
677 |
}
|
|
|
678 |
}
|
|
|
679 |
}
|
|
|
680 |
|
|
|
681 |
return bindArray || [0];
|
|
|
682 |
}
|
|
|
683 |
|
|
|
684 |
parentView = parentView || topView;
|
|
|
685 |
var tag, tagDef, template, tags, attr, parentTag, l, m, n, itemRet, tagCtx, tagCtxCtx, ctxPrm, bindTo, bindFrom, initVal,
|
|
|
686 |
content, callInit, mapDef, thisMap, args, bdArgs, props, tagDataMap, contentCtx, key, bindFromLength, bindToLength, linkedElement, defaultCtx,
|
|
|
687 |
i = 0,
|
|
|
688 |
ret = "",
|
|
|
689 |
linkCtx = parentView._lc || false, // For data-link="{myTag...}"... See onDataLinkedTagChange
|
|
|
690 |
ctx = parentView.ctx,
|
|
|
691 |
parentTmpl = tmpl || parentView.tmpl,
|
|
|
692 |
// If tagCtxs is an integer, then it is the key for the compiled function to return the boundTag tagCtxs
|
|
|
693 |
boundTag = typeof tagCtxs === "number" && parentView.tmpl.bnds[tagCtxs-1];
|
|
|
694 |
|
|
|
695 |
if (tagName._is === "tag") {
|
|
|
696 |
tag = tagName;
|
|
|
697 |
tagName = tag.tagName;
|
|
|
698 |
tagCtxs = tag.tagCtxs;
|
|
|
699 |
template = tag.template;
|
|
|
700 |
} else {
|
|
|
701 |
tagDef = parentView.getRsc("tags", tagName) || error("Unknown tag: {{" + tagName + "}} ");
|
|
|
702 |
template = tagDef.template;
|
|
|
703 |
}
|
|
|
704 |
if (onError === undefined && boundTag && (boundTag._lr = (tagDef.lateRender && boundTag._lr!== false || boundTag._lr))) {
|
|
|
705 |
onError = ""; // If lateRender, set temporary onError, to skip initial rendering (and render just "")
|
|
|
706 |
}
|
|
|
707 |
if (onError !== undefined) {
|
|
|
708 |
ret += onError;
|
|
|
709 |
tagCtxs = onError = [{props: {}, args: [], params: {props:{}}}];
|
|
|
710 |
} else if (boundTag) {
|
|
|
711 |
tagCtxs = boundTag(parentView.data, parentView, $sub);
|
|
|
712 |
}
|
|
|
713 |
|
|
|
714 |
l = tagCtxs.length;
|
|
|
715 |
for (; i < l; i++) {
|
|
|
716 |
tagCtx = tagCtxs[i];
|
|
|
717 |
content = tagCtx.tmpl;
|
|
|
718 |
if (!linkCtx || !linkCtx.tag || i && !linkCtx.tag.inline || tag._er || content && +content===content) {
|
|
|
719 |
// Initialize tagCtx
|
|
|
720 |
// For block tags, tagCtx.tmpl is an integer > 0
|
|
|
721 |
if (content && parentTmpl.tmpls) {
|
|
|
722 |
tagCtx.tmpl = tagCtx.content = parentTmpl.tmpls[content - 1]; // Set the tmpl property to the content of the block tag
|
|
|
723 |
}
|
|
|
724 |
tagCtx.index = i;
|
|
|
725 |
tagCtx.ctxPrm = contextParameter;
|
|
|
726 |
tagCtx.render = renderContent;
|
|
|
727 |
tagCtx.cvtArgs = convertArgs;
|
|
|
728 |
tagCtx.bndArgs = convertBoundArgs;
|
|
|
729 |
tagCtx.view = parentView;
|
|
|
730 |
tagCtx.ctx = extendCtx(extendCtx(tagCtx.ctx, tagDef && tagDef.ctx), ctx); // Clone and extend parentView.ctx
|
|
|
731 |
}
|
|
|
732 |
if (tmpl = tagCtx.props.tmpl) {
|
|
|
733 |
// If the tmpl property is overridden, set the value (when initializing, or, in case of binding: ^tmpl=..., when updating)
|
|
|
734 |
tagCtx.tmpl = parentView._getTmpl(tmpl);
|
|
|
735 |
tagCtx.content = tagCtx.content || tagCtx.tmpl;
|
|
|
736 |
}
|
|
|
737 |
|
|
|
738 |
if (!tag) {
|
|
|
739 |
// This will only be hit for initial tagCtx (not for {{else}}) - if the tag instance does not exist yet
|
|
|
740 |
// If the tag has not already been instantiated, we will create a new instance.
|
|
|
741 |
// ~tag will access the tag, even within the rendering of the template content of this tag.
|
|
|
742 |
// From child/descendant tags, can access using ~tag.parent, or ~parentTags.tagName
|
|
|
743 |
tag = new tagDef._ctr();
|
|
|
744 |
callInit = !!tag.init;
|
|
|
745 |
|
|
|
746 |
tag.parent = parentTag = ctx && ctx.tag;
|
|
|
747 |
tag.tagCtxs = tagCtxs;
|
|
|
748 |
|
|
|
749 |
if (linkCtx) {
|
|
|
750 |
tag.inline = false;
|
|
|
751 |
linkCtx.tag = tag;
|
|
|
752 |
}
|
|
|
753 |
tag.linkCtx = linkCtx;
|
|
|
754 |
if (tag._.bnd = boundTag || linkCtx.fn) {
|
|
|
755 |
// Bound if {^{tag...}} or data-link="{tag...}"
|
|
|
756 |
tag._.ths = tagCtx.params.props.this; // Tag has a this=expr binding, to get javascript reference to tag instance
|
|
|
757 |
tag._.lt = tagCtxs.lt; // If a late path @some.path has not returned @some object, mark tag as late
|
|
|
758 |
tag._.arrVws = {};
|
|
|
759 |
} else if (tag.dataBoundOnly) {
|
|
|
760 |
error(tagName + " must be data-bound:\n{^{" + tagName + "}}");
|
|
|
761 |
}
|
|
|
762 |
//TODO better perf for childTags() - keep child tag.tags array, (and remove child, when disposed)
|
|
|
763 |
// tag.tags = [];
|
|
|
764 |
} else if (linkCtx && linkCtx.fn._lr) {
|
|
|
765 |
callInit = !!tag.init;
|
|
|
766 |
}
|
|
|
767 |
tagDataMap = tag.dataMap;
|
|
|
768 |
|
|
|
769 |
tagCtx.tag = tag;
|
|
|
770 |
if (tagDataMap && tagCtxs) {
|
|
|
771 |
tagCtx.map = tagCtxs[i].map; // Copy over the compiled map instance from the previous tagCtxs to the refreshed ones
|
|
|
772 |
}
|
|
|
773 |
if (!tag.flow) {
|
|
|
774 |
tagCtxCtx = tagCtx.ctx = tagCtx.ctx || {};
|
|
|
775 |
|
|
|
776 |
// tags hash: tag.ctx.tags, merged with parentView.ctx.tags,
|
|
|
777 |
tags = tag.parents = tagCtxCtx.parentTags = ctx && extendCtx(tagCtxCtx.parentTags, ctx.parentTags) || {};
|
|
|
778 |
if (parentTag) {
|
|
|
779 |
tags[parentTag.tagName] = parentTag;
|
|
|
780 |
//TODO better perf for childTags: parentTag.tags.push(tag);
|
|
|
781 |
}
|
|
|
782 |
tags[tag.tagName] = tagCtxCtx.tag = tag;
|
|
|
783 |
tagCtxCtx.tagCtx = tagCtx;
|
|
|
784 |
}
|
|
|
785 |
}
|
|
|
786 |
if (!(tag._er = onError)) {
|
|
|
787 |
tagHandlersFromProps(tag, tagCtxs[0]);
|
|
|
788 |
tag.rendering = {rndr: tag.rendering}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...)
|
|
|
789 |
for (i = 0; i < l; i++) { // Iterate tagCtx for each {{else}} block
|
|
|
790 |
tagCtx = tag.tagCtx = tagCtxs[i];
|
|
|
791 |
props = tagCtx.props;
|
|
|
792 |
tag.ctx = tagCtx.ctx;
|
|
|
793 |
|
|
|
794 |
if (!i) {
|
|
|
795 |
if (callInit) {
|
|
|
796 |
tag.init(tagCtx, linkCtx, tag.ctx);
|
|
|
797 |
callInit = undefined;
|
|
|
798 |
}
|
|
|
799 |
if (!tagCtx.args.length && tagCtx.argDefault !== false && tag.argDefault !== false) {
|
|
|
800 |
tagCtx.args = args = [tagCtx.view.data]; // Missing first arg defaults to the current data context
|
|
|
801 |
tagCtx.params.args = ["#data"];
|
|
|
802 |
}
|
|
|
803 |
|
|
|
804 |
bindTo = bindToOrBindFrom("bindTo");
|
|
|
805 |
|
|
|
806 |
if (tag.bindTo !== undefined) {
|
|
|
807 |
tag.bindTo = bindTo;
|
|
|
808 |
}
|
|
|
809 |
|
|
|
810 |
if (tag.bindFrom !== undefined) {
|
|
|
811 |
tag.bindFrom = bindToOrBindFrom("bindFrom");
|
|
|
812 |
} else if (tag.bindTo) {
|
|
|
813 |
tag.bindFrom = tag.bindTo = bindTo;
|
|
|
814 |
}
|
|
|
815 |
bindFrom = tag.bindFrom || bindTo;
|
|
|
816 |
|
|
|
817 |
bindToLength = bindTo.length;
|
|
|
818 |
bindFromLength = bindFrom.length;
|
|
|
819 |
|
|
|
820 |
if (tag._.bnd && (linkedElement = tag.linkedElement)) {
|
|
|
821 |
tag.linkedElement = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement];
|
|
|
822 |
|
|
|
823 |
if (bindToLength !== linkedElement.length) {
|
|
|
824 |
error("linkedElement not same length as bindTo");
|
|
|
825 |
}
|
|
|
826 |
}
|
|
|
827 |
if (linkedElement = tag.linkedCtxParam) {
|
|
|
828 |
tag.linkedCtxParam = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement];
|
|
|
829 |
|
|
|
830 |
if (bindFromLength !== linkedElement.length) {
|
|
|
831 |
error("linkedCtxParam not same length as bindFrom/bindTo");
|
|
|
832 |
}
|
|
|
833 |
}
|
|
|
834 |
|
|
|
835 |
if (bindFrom) {
|
|
|
836 |
tag._.fromIndex = {}; // Hash of bindFrom index which has same path value as bindTo index. fromIndex = tag._.fromIndex[toIndex]
|
|
|
837 |
tag._.toIndex = {}; // Hash of bindFrom index which has same path value as bindTo index. fromIndex = tag._.fromIndex[toIndex]
|
|
|
838 |
n = bindFromLength;
|
|
|
839 |
while (n--) {
|
|
|
840 |
key = bindFrom[n];
|
|
|
841 |
m = bindToLength;
|
|
|
842 |
while (m--) {
|
|
|
843 |
if (key === bindTo[m]) {
|
|
|
844 |
tag._.fromIndex[m] = n;
|
|
|
845 |
tag._.toIndex[n] = m;
|
|
|
846 |
}
|
|
|
847 |
}
|
|
|
848 |
}
|
|
|
849 |
}
|
|
|
850 |
|
|
|
851 |
if (linkCtx) {
|
|
|
852 |
// Set attr on linkCtx to ensure outputting to the correct target attribute.
|
|
|
853 |
// Setting either linkCtx.attr or this.attr in the init() allows per-instance choice of target attrib.
|
|
|
854 |
linkCtx.attr = tag.attr = linkCtx.attr || tag.attr || linkCtx._dfAt;
|
|
|
855 |
}
|
|
|
856 |
attr = tag.attr;
|
|
|
857 |
tag._.noVws = attr && attr !== HTML;
|
|
|
858 |
}
|
|
|
859 |
args = tag.cvtArgs(i);
|
|
|
860 |
if (tag.linkedCtxParam) {
|
|
|
861 |
bdArgs = tag.cvtArgs(i, 1);
|
|
|
862 |
m = bindFromLength;
|
|
|
863 |
defaultCtx = tag.constructor.prototype.ctx;
|
|
|
864 |
while (m--) {
|
|
|
865 |
if (ctxPrm = tag.linkedCtxParam[m]) {
|
|
|
866 |
key = bindFrom[m];
|
|
|
867 |
initVal = bdArgs[m];
|
|
|
868 |
// Create tag contextual parameter
|
|
|
869 |
tagCtx.ctx[ctxPrm] = $sub._cp(
|
|
|
870 |
defaultCtx && initVal === undefined ? defaultCtx[ctxPrm]: initVal,
|
|
|
871 |
initVal !== undefined && argOrProp(tagCtx.params, key),
|
|
|
872 |
tagCtx.view,
|
|
|
873 |
tag._.bnd && {tag: tag, cvt: tag.convert, ind: m, tagElse: i}
|
|
|
874 |
);
|
|
|
875 |
}
|
|
|
876 |
}
|
|
|
877 |
}
|
|
|
878 |
if ((mapDef = props.dataMap || tagDataMap) && (args.length || props.dataMap)) {
|
|
|
879 |
thisMap = tagCtx.map;
|
|
|
880 |
if (!thisMap || thisMap.src !== args[0] || isUpdate) {
|
|
|
881 |
if (thisMap && thisMap.src) {
|
|
|
882 |
thisMap.unmap(); // only called if observable map - not when only used in JsRender, e.g. by {{props}}
|
|
|
883 |
}
|
|
|
884 |
mapDef.map(args[0], tagCtx, thisMap, !tag._.bnd);
|
|
|
885 |
thisMap = tagCtx.map;
|
|
|
886 |
}
|
|
|
887 |
args = [thisMap.tgt];
|
|
|
888 |
}
|
|
|
889 |
|
|
|
890 |
itemRet = undefined;
|
|
|
891 |
if (tag.render) {
|
|
|
892 |
itemRet = tag.render.apply(tag, args);
|
|
|
893 |
if (parentView.linked && itemRet && !rWrappedInViewMarker.test(itemRet)) {
|
|
|
894 |
// When a tag renders content from the render method, with data linking then we need to wrap with view markers, if absent,
|
|
|
895 |
// to provide a contentView for the tag, which will correctly dispose bindings if deleted. The 'tmpl' for this view will
|
|
|
896 |
// be a dumbed-down template which will always return the itemRet string (no matter what the data is). The itemRet string
|
|
|
897 |
// is not compiled as template markup, so can include "{{" or "}}" without triggering syntax errors
|
|
|
898 |
tmpl = { // 'Dumbed-down' template which always renders 'static' itemRet string
|
|
|
899 |
links: []
|
|
|
900 |
};
|
|
|
901 |
tmpl.render = tmpl.fn = function() {
|
|
|
902 |
return itemRet;
|
|
|
903 |
};
|
|
|
904 |
itemRet = renderWithViews(tmpl, parentView.data, undefined, true, parentView, undefined, undefined, tag);
|
|
|
905 |
}
|
|
|
906 |
}
|
|
|
907 |
if (!args.length) {
|
|
|
908 |
args = [parentView]; // no arguments - (e.g. {{else}}) get data context from view.
|
|
|
909 |
}
|
|
|
910 |
if (itemRet === undefined) {
|
|
|
911 |
contentCtx = args[0]; // Default data context for wrapped block content is the first argument
|
|
|
912 |
if (tag.contentCtx) { // Set tag.contentCtx to true, to inherit parent context, or to a function to provide alternate context.
|
|
|
913 |
contentCtx = tag.contentCtx === true ? parentView : tag.contentCtx(contentCtx);
|
|
|
914 |
}
|
|
|
915 |
itemRet = tagCtx.render(contentCtx, true) || (isUpdate ? undefined : "");
|
|
|
916 |
}
|
|
|
917 |
ret = ret
|
|
|
918 |
? ret + (itemRet || "")
|
|
|
919 |
: itemRet !== undefined
|
|
|
920 |
? "" + itemRet
|
|
|
921 |
: undefined; // If no return value from render, and no template/content tagCtx.render(...), return undefined
|
|
|
922 |
}
|
|
|
923 |
tag.rendering = tag.rendering.rndr; // Remove tag.rendering object (if this is outermost render call. (In case of nested calls)
|
|
|
924 |
}
|
|
|
925 |
tag.tagCtx = tagCtxs[0];
|
|
|
926 |
tag.ctx = tag.tagCtx.ctx;
|
|
|
927 |
|
|
|
928 |
if (tag._.noVws && tag.inline) {
|
|
|
929 |
// inline tag with attr set to "text" will insert HTML-encoded content - as if it was element-based innerText
|
|
|
930 |
ret = attr === "text"
|
|
|
931 |
? $converters.html(ret)
|
|
|
932 |
: "";
|
|
|
933 |
}
|
|
|
934 |
return boundTag && parentView._.onRender
|
|
|
935 |
// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
|
|
|
936 |
? parentView._.onRender(ret, parentView, tag)
|
|
|
937 |
: ret;
|
|
|
938 |
}
|
|
|
939 |
|
|
|
940 |
//=================
|
|
|
941 |
// View constructor
|
|
|
942 |
//=================
|
|
|
943 |
|
|
|
944 |
function View(context, type, parentView, data, template, key, onRender, contentTmpl) {
|
|
|
945 |
// Constructor for view object in view hierarchy. (Augmented by JsViews if JsViews is loaded)
|
|
|
946 |
var views, parentView_, tag, self_,
|
|
|
947 |
self = this,
|
|
|
948 |
isArray = type === "array";
|
|
|
949 |
// If the data is an array, this is an 'array view' with a views array for each child 'item view'
|
|
|
950 |
// If the data is not an array, this is an 'item view' with a views 'hash' object for any child nested views
|
|
|
951 |
|
|
|
952 |
self.content = contentTmpl;
|
|
|
953 |
self.views = isArray ? [] : {};
|
|
|
954 |
self.data = data;
|
|
|
955 |
self.tmpl = template;
|
|
|
956 |
self_ = self._ = {
|
|
|
957 |
key: 0,
|
|
|
958 |
// ._.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
|
|
|
959 |
useKey: isArray ? 0 : 1,
|
|
|
960 |
id: "" + viewId++,
|
|
|
961 |
onRender: onRender,
|
|
|
962 |
bnds: {}
|
|
|
963 |
};
|
|
|
964 |
self.linked = !!onRender;
|
|
|
965 |
self.type = type || "top";
|
|
|
966 |
if (type) {
|
|
|
967 |
self.cache = {_ct: $subSettings._cchCt}; // Used for caching results of computed properties and helpers (view.getCache)
|
|
|
968 |
}
|
|
|
969 |
|
|
|
970 |
if (!parentView || parentView.type === "top") {
|
|
|
971 |
(self.ctx = context || {}).root = self.data;
|
|
|
972 |
}
|
|
|
973 |
|
|
|
974 |
if (self.parent = parentView) {
|
|
|
975 |
self.root = parentView.root || self; // view whose parent is top view
|
|
|
976 |
views = parentView.views;
|
|
|
977 |
parentView_ = parentView._;
|
|
|
978 |
self.isTop = parentView_.scp; // Is top content view of a link("#container", ...) call
|
|
|
979 |
self.scope = (!context.tag || context.tag === parentView.ctx.tag) && !self.isTop && parentView.scope || self;
|
|
|
980 |
// Scope for contextParams - closest non flow tag ancestor or root view
|
|
|
981 |
if (parentView_.useKey) {
|
|
|
982 |
// Parent is not an 'array view'. Add this view to its views object
|
|
|
983 |
// self._key = is the key in the parent view hash
|
|
|
984 |
views[self_.key = "_" + parentView_.useKey++] = self;
|
|
|
985 |
self.index = indexStr;
|
|
|
986 |
self.getIndex = getNestedIndex;
|
|
|
987 |
} else if (views.length === (self_.key = self.index = key)) { // Parent is an 'array view'. Add this view to its views array
|
|
|
988 |
views.push(self); // Adding to end of views array. (Using push when possible - better perf than splice)
|
|
|
989 |
} else {
|
|
|
990 |
views.splice(key, 0, self); // Inserting in views array
|
|
|
991 |
}
|
|
|
992 |
// If no context was passed in, use parent context
|
|
|
993 |
// If context was passed in, it should have been merged already with parent context
|
|
|
994 |
self.ctx = context || parentView.ctx;
|
|
|
995 |
} else if (type) {
|
|
|
996 |
self.root = self; // view whose parent is top view
|
|
|
997 |
}
|
|
|
998 |
}
|
|
|
999 |
|
|
|
1000 |
View.prototype = {
|
|
|
1001 |
get: getView,
|
|
|
1002 |
getIndex: getIndex,
|
|
|
1003 |
ctxPrm: contextParameter,
|
|
|
1004 |
getRsc: getResource,
|
|
|
1005 |
_getTmpl: getTemplate,
|
|
|
1006 |
_getOb: getPathObject,
|
|
|
1007 |
getCache: function(key) { // Get cached value of computed value
|
|
|
1008 |
if ($subSettings._cchCt > this.cache._ct) {
|
|
|
1009 |
this.cache = {_ct: $subSettings._cchCt};
|
|
|
1010 |
}
|
|
|
1011 |
return this.cache[key] || (this.cache[key] = cpFnStore[key](this.data, this, $sub));
|
|
|
1012 |
},
|
|
|
1013 |
_is: "view"
|
|
|
1014 |
};
|
|
|
1015 |
|
|
|
1016 |
//====================================================
|
|
|
1017 |
// Registration
|
|
|
1018 |
//====================================================
|
|
|
1019 |
|
|
|
1020 |
function compileChildResources(parentTmpl) {
|
|
|
1021 |
var storeName, storeNames, resources;
|
|
|
1022 |
for (storeName in jsvStores) {
|
|
|
1023 |
storeNames = storeName + "s";
|
|
|
1024 |
if (parentTmpl[storeNames]) {
|
|
|
1025 |
resources = parentTmpl[storeNames]; // Resources not yet compiled
|
|
|
1026 |
parentTmpl[storeNames] = {}; // Remove uncompiled resources
|
|
|
1027 |
$views[storeNames](resources, parentTmpl); // Add back in the compiled resources
|
|
|
1028 |
}
|
|
|
1029 |
}
|
|
|
1030 |
}
|
|
|
1031 |
|
|
|
1032 |
//===============
|
|
|
1033 |
// compileTag
|
|
|
1034 |
//===============
|
|
|
1035 |
|
|
|
1036 |
function compileTag(name, tagDef, parentTmpl) {
|
|
|
1037 |
var tmpl, baseTag, prop,
|
|
|
1038 |
compiledDef = new $sub._tg();
|
|
|
1039 |
|
|
|
1040 |
function Tag() {
|
|
|
1041 |
var tag = this;
|
|
|
1042 |
tag._ = {
|
|
|
1043 |
unlinked: true
|
|
|
1044 |
};
|
|
|
1045 |
tag.inline = true;
|
|
|
1046 |
tag.tagName = name;
|
|
|
1047 |
}
|
|
|
1048 |
|
|
|
1049 |
if ($isFunction(tagDef)) {
|
|
|
1050 |
// Simple tag declared as function. No presenter instantation.
|
|
|
1051 |
tagDef = {
|
|
|
1052 |
depends: tagDef.depends,
|
|
|
1053 |
render: tagDef
|
|
|
1054 |
};
|
|
|
1055 |
} else if ("" + tagDef === tagDef) {
|
|
|
1056 |
tagDef = {template: tagDef};
|
|
|
1057 |
}
|
|
|
1058 |
|
|
|
1059 |
if (baseTag = tagDef.baseTag) {
|
|
|
1060 |
tagDef.flow = !!tagDef.flow; // Set flow property, so defaults to false even if baseTag has flow=true
|
|
|
1061 |
baseTag = "" + baseTag === baseTag
|
|
|
1062 |
? (parentTmpl && parentTmpl.tags[baseTag] || $tags[baseTag])
|
|
|
1063 |
: baseTag;
|
|
|
1064 |
if (!baseTag) {
|
|
|
1065 |
error('baseTag: "' + tagDef.baseTag + '" not found');
|
|
|
1066 |
}
|
|
|
1067 |
compiledDef = $extend(compiledDef, baseTag);
|
|
|
1068 |
|
|
|
1069 |
for (prop in tagDef) {
|
|
|
1070 |
compiledDef[prop] = getMethod(baseTag[prop], tagDef[prop]);
|
|
|
1071 |
}
|
|
|
1072 |
} else {
|
|
|
1073 |
compiledDef = $extend(compiledDef, tagDef);
|
|
|
1074 |
}
|
|
|
1075 |
|
|
|
1076 |
// Tag declared as object, used as the prototype for tag instantiation (control/presenter)
|
|
|
1077 |
if ((tmpl = compiledDef.template) !== undefined) {
|
|
|
1078 |
compiledDef.template = "" + tmpl === tmpl ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
|
|
|
1079 |
}
|
|
|
1080 |
(Tag.prototype = compiledDef).constructor = compiledDef._ctr = Tag;
|
|
|
1081 |
|
|
|
1082 |
if (parentTmpl) {
|
|
|
1083 |
compiledDef._parentTmpl = parentTmpl;
|
|
|
1084 |
}
|
|
|
1085 |
return compiledDef;
|
|
|
1086 |
}
|
|
|
1087 |
|
|
|
1088 |
function baseApply(args) {
|
|
|
1089 |
// In derived method (or handler declared declaratively as in {{:foo onChange=~fooChanged}} can call base method,
|
|
|
1090 |
// using this.baseApply(arguments) (Equivalent to this._superApply(arguments) in jQuery UI)
|
|
|
1091 |
return this.base.apply(this, args);
|
|
|
1092 |
}
|
|
|
1093 |
|
|
|
1094 |
//===============
|
|
|
1095 |
// compileTmpl
|
|
|
1096 |
//===============
|
|
|
1097 |
|
|
|
1098 |
function compileTmpl(name, tmpl, parentTmpl, options) {
|
|
|
1099 |
// tmpl is either a template object, a selector for a template script block, or the name of a compiled template
|
|
|
1100 |
|
|
|
1101 |
//==== nested functions ====
|
|
|
1102 |
function lookupTemplate(value) {
|
|
|
1103 |
// If value is of type string - treat as selector, or name of compiled template
|
|
|
1104 |
// Return the template object, if already compiled, or the markup string
|
|
|
1105 |
var currentName, tmpl;
|
|
|
1106 |
if (("" + value === value) || value.nodeType > 0 && (elem = value)) {
|
|
|
1107 |
if (!elem) {
|
|
|
1108 |
if (/^\.?\/[^\\:*?"<>]*$/.test(value)) {
|
|
|
1109 |
// value="./some/file.html" (or "/some/file.html")
|
|
|
1110 |
// If the template is not named, use "./some/file.html" as name.
|
|
|
1111 |
if (tmpl = $templates[name = name || value]) {
|
|
|
1112 |
value = tmpl;
|
|
|
1113 |
} else {
|
|
|
1114 |
// BROWSER-SPECIFIC CODE (not on Node.js):
|
|
|
1115 |
// Look for server-generated script block with id "./some/file.html"
|
|
|
1116 |
elem = document.getElementById(value);
|
|
|
1117 |
}
|
|
|
1118 |
} else if ($.fn && !$sub.rTmpl.test(value)) {
|
|
|
1119 |
try {
|
|
|
1120 |
elem = $(value, document)[0]; // if jQuery is loaded, test for selector returning elements, and get first element
|
|
|
1121 |
} catch (e) {}
|
|
|
1122 |
}// END BROWSER-SPECIFIC CODE
|
|
|
1123 |
} //BROWSER-SPECIFIC CODE
|
|
|
1124 |
if (elem) {
|
|
|
1125 |
if (elem.tagName !== "SCRIPT") {
|
|
|
1126 |
error(value + ": Use script block, not " + elem.tagName);
|
|
|
1127 |
}
|
|
|
1128 |
if (options) {
|
|
|
1129 |
// We will compile a new template using the markup in the script element
|
|
|
1130 |
value = elem.innerHTML;
|
|
|
1131 |
} else {
|
|
|
1132 |
// We will cache a single copy of the compiled template, and associate it with the name
|
|
|
1133 |
// (renaming from a previous name if there was one).
|
|
|
1134 |
currentName = elem.getAttribute(tmplAttr);
|
|
|
1135 |
if (currentName) {
|
|
|
1136 |
if (currentName !== jsvTmpl) {
|
|
|
1137 |
value = $templates[currentName];
|
|
|
1138 |
delete $templates[currentName];
|
|
|
1139 |
} else if ($.fn) {
|
|
|
1140 |
value = $.data(elem)[jsvTmpl]; // Get cached compiled template
|
|
|
1141 |
}
|
|
|
1142 |
}
|
|
|
1143 |
if (!currentName || !value) { // Not yet compiled, or cached version lost
|
|
|
1144 |
name = name || ($.fn ? jsvTmpl : value);
|
|
|
1145 |
value = compileTmpl(name, elem.innerHTML, parentTmpl, options);
|
|
|
1146 |
}
|
|
|
1147 |
value.tmplName = name = name || currentName;
|
|
|
1148 |
if (name !== jsvTmpl) {
|
|
|
1149 |
$templates[name] = value;
|
|
|
1150 |
}
|
|
|
1151 |
elem.setAttribute(tmplAttr, name);
|
|
|
1152 |
if ($.fn) {
|
|
|
1153 |
$.data(elem, jsvTmpl, value);
|
|
|
1154 |
}
|
|
|
1155 |
}
|
|
|
1156 |
} // END BROWSER-SPECIFIC CODE
|
|
|
1157 |
elem = undefined;
|
|
|
1158 |
} else if (!value.fn) {
|
|
|
1159 |
value = undefined;
|
|
|
1160 |
// If value is not a string. HTML element, or compiled template, return undefined
|
|
|
1161 |
}
|
|
|
1162 |
return value;
|
|
|
1163 |
}
|
|
|
1164 |
|
|
|
1165 |
var elem, compiledTmpl,
|
|
|
1166 |
tmplOrMarkup = tmpl = tmpl || "";
|
|
|
1167 |
$sub._html = $converters.html;
|
|
|
1168 |
|
|
|
1169 |
//==== Compile the template ====
|
|
|
1170 |
if (options === 0) {
|
|
|
1171 |
options = undefined;
|
|
|
1172 |
tmplOrMarkup = lookupTemplate(tmplOrMarkup); // Top-level compile so do a template lookup
|
|
|
1173 |
}
|
|
|
1174 |
|
|
|
1175 |
// If options, then this was already compiled from a (script) element template declaration.
|
|
|
1176 |
// If not, then if tmpl is a template object, use it for options
|
|
|
1177 |
options = options || (tmpl.markup
|
|
|
1178 |
? tmpl.bnds
|
|
|
1179 |
? $extend({}, tmpl)
|
|
|
1180 |
: tmpl
|
|
|
1181 |
: {}
|
|
|
1182 |
);
|
|
|
1183 |
|
|
|
1184 |
options.tmplName = options.tmplName || name || "unnamed";
|
|
|
1185 |
if (parentTmpl) {
|
|
|
1186 |
options._parentTmpl = parentTmpl;
|
|
|
1187 |
}
|
|
|
1188 |
// If tmpl is not a markup string or a selector string, then it must be a template object
|
|
|
1189 |
// In that case, get it from the markup property of the object
|
|
|
1190 |
if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = lookupTemplate(tmpl.markup)) && tmplOrMarkup.fn) {
|
|
|
1191 |
// If the string references a compiled template object, need to recompile to merge any modified options
|
|
|
1192 |
tmplOrMarkup = tmplOrMarkup.markup;
|
|
|
1193 |
}
|
|
|
1194 |
if (tmplOrMarkup !== undefined) {
|
|
|
1195 |
if (tmplOrMarkup.render || tmpl.render) {
|
|
|
1196 |
// tmpl is already compiled, so use it
|
|
|
1197 |
if (tmplOrMarkup.tmpls) {
|
|
|
1198 |
compiledTmpl = tmplOrMarkup;
|
|
|
1199 |
}
|
|
|
1200 |
} else {
|
|
|
1201 |
// tmplOrMarkup is a markup string, not a compiled template
|
|
|
1202 |
// Create template object
|
|
|
1203 |
tmpl = tmplObject(tmplOrMarkup, options);
|
|
|
1204 |
// Compile to AST and then to compiled function
|
|
|
1205 |
tmplFn(tmplOrMarkup.replace(rEscapeQuotes, "\\$&"), tmpl);
|
|
|
1206 |
}
|
|
|
1207 |
if (!compiledTmpl) {
|
|
|
1208 |
compiledTmpl = $extend(function() {
|
|
|
1209 |
return compiledTmpl.render.apply(compiledTmpl, arguments);
|
|
|
1210 |
}, tmpl);
|
|
|
1211 |
|
|
|
1212 |
compileChildResources(compiledTmpl);
|
|
|
1213 |
}
|
|
|
1214 |
return compiledTmpl;
|
|
|
1215 |
}
|
|
|
1216 |
}
|
|
|
1217 |
|
|
|
1218 |
//==== /end of function compileTmpl ====
|
|
|
1219 |
|
|
|
1220 |
//=================
|
|
|
1221 |
// compileViewModel
|
|
|
1222 |
//=================
|
|
|
1223 |
|
|
|
1224 |
function getDefaultVal(defaultVal, data) {
|
|
|
1225 |
return $isFunction(defaultVal)
|
|
|
1226 |
? defaultVal.call(data)
|
|
|
1227 |
: defaultVal;
|
|
|
1228 |
}
|
|
|
1229 |
|
|
|
1230 |
function addParentRef(ob, ref, parent) {
|
|
|
1231 |
Object.defineProperty(ob, ref, {
|
|
|
1232 |
value: parent,
|
|
|
1233 |
configurable: true
|
|
|
1234 |
});
|
|
|
1235 |
}
|
|
|
1236 |
|
|
|
1237 |
function compileViewModel(name, type) {
|
|
|
1238 |
var i, constructor, parent,
|
|
|
1239 |
viewModels = this,
|
|
|
1240 |
getters = type.getters,
|
|
|
1241 |
extend = type.extend,
|
|
|
1242 |
id = type.id,
|
|
|
1243 |
proto = $.extend({
|
|
|
1244 |
_is: name || "unnamed",
|
|
|
1245 |
unmap: unmap,
|
|
|
1246 |
merge: merge
|
|
|
1247 |
}, extend),
|
|
|
1248 |
args = "",
|
|
|
1249 |
cnstr = "",
|
|
|
1250 |
getterCount = getters ? getters.length : 0,
|
|
|
1251 |
$observable = $.observable,
|
|
|
1252 |
getterNames = {};
|
|
|
1253 |
|
|
|
1254 |
function JsvVm(args) {
|
|
|
1255 |
constructor.apply(this, args);
|
|
|
1256 |
}
|
|
|
1257 |
|
|
|
1258 |
function vm() {
|
|
|
1259 |
return new JsvVm(arguments);
|
|
|
1260 |
}
|
|
|
1261 |
|
|
|
1262 |
function iterate(data, action) {
|
|
|
1263 |
var getterType, defaultVal, prop, ob, parentRef,
|
|
|
1264 |
j = 0;
|
|
|
1265 |
for (; j < getterCount; j++) {
|
|
|
1266 |
prop = getters[j];
|
|
|
1267 |
getterType = undefined;
|
|
|
1268 |
if (prop + "" !== prop) {
|
|
|
1269 |
getterType = prop;
|
|
|
1270 |
prop = getterType.getter;
|
|
|
1271 |
parentRef = getterType.parentRef;
|
|
|
1272 |
}
|
|
|
1273 |
if ((ob = data[prop]) === undefined && getterType && (defaultVal = getterType.defaultVal) !== undefined) {
|
|
|
1274 |
ob = getDefaultVal(defaultVal, data);
|
|
|
1275 |
}
|
|
|
1276 |
action(ob, getterType && viewModels[getterType.type], prop, parentRef);
|
|
|
1277 |
}
|
|
|
1278 |
}
|
|
|
1279 |
|
|
|
1280 |
function map(data) {
|
|
|
1281 |
data = data + "" === data
|
|
|
1282 |
? JSON.parse(data) // Accept JSON string
|
|
|
1283 |
: data; // or object/array
|
|
|
1284 |
var l, prop, childOb, parentRef,
|
|
|
1285 |
j = 0,
|
|
|
1286 |
ob = data,
|
|
|
1287 |
arr = [];
|
|
|
1288 |
|
|
|
1289 |
if ($isArray(data)) {
|
|
|
1290 |
data = data || [];
|
|
|
1291 |
l = data.length;
|
|
|
1292 |
for (; j<l; j++) {
|
|
|
1293 |
arr.push(this.map(data[j]));
|
|
|
1294 |
}
|
|
|
1295 |
arr._is = name;
|
|
|
1296 |
arr.unmap = unmap;
|
|
|
1297 |
arr.merge = merge;
|
|
|
1298 |
return arr;
|
|
|
1299 |
}
|
|
|
1300 |
|
|
|
1301 |
if (data) {
|
|
|
1302 |
iterate(data, function(ob, viewModel) {
|
|
|
1303 |
if (viewModel) { // Iterate to build getters arg array (value, or mapped value)
|
|
|
1304 |
ob = viewModel.map(ob);
|
|
|
1305 |
}
|
|
|
1306 |
arr.push(ob);
|
|
|
1307 |
});
|
|
|
1308 |
ob = this.apply(this, arr); // Instantiate this View Model, passing getters args array to constructor
|
|
|
1309 |
j = getterCount;
|
|
|
1310 |
while (j--) {
|
|
|
1311 |
childOb = arr[j];
|
|
|
1312 |
parentRef = getters[j].parentRef;
|
|
|
1313 |
if (parentRef && childOb && childOb.unmap) {
|
|
|
1314 |
if ($isArray(childOb)) {
|
|
|
1315 |
l = childOb.length;
|
|
|
1316 |
while (l--) {
|
|
|
1317 |
addParentRef(childOb[l], parentRef, ob);
|
|
|
1318 |
}
|
|
|
1319 |
} else {
|
|
|
1320 |
addParentRef(childOb, parentRef, ob);
|
|
|
1321 |
}
|
|
|
1322 |
}
|
|
|
1323 |
}
|
|
|
1324 |
for (prop in data) { // Copy over any other properties. that are not get/set properties
|
|
|
1325 |
if (prop !== $expando && !getterNames[prop]) {
|
|
|
1326 |
ob[prop] = data[prop];
|
|
|
1327 |
}
|
|
|
1328 |
}
|
|
|
1329 |
}
|
|
|
1330 |
return ob;
|
|
|
1331 |
}
|
|
|
1332 |
|
|
|
1333 |
function merge(data, parent, parentRef) {
|
|
|
1334 |
data = data + "" === data
|
|
|
1335 |
? JSON.parse(data) // Accept JSON string
|
|
|
1336 |
: data; // or object/array
|
|
|
1337 |
|
|
|
1338 |
var j, l, m, prop, mod, found, assigned, ob, newModArr, childOb,
|
|
|
1339 |
k = 0,
|
|
|
1340 |
model = this;
|
|
|
1341 |
|
|
|
1342 |
if ($isArray(model)) {
|
|
|
1343 |
assigned = {};
|
|
|
1344 |
newModArr = [];
|
|
|
1345 |
l = data.length;
|
|
|
1346 |
m = model.length;
|
|
|
1347 |
for (; k<l; k++) {
|
|
|
1348 |
ob = data[k];
|
|
|
1349 |
found = false;
|
|
|
1350 |
for (j=0; j<m && !found; j++) {
|
|
|
1351 |
if (assigned[j]) {
|
|
|
1352 |
continue;
|
|
|
1353 |
}
|
|
|
1354 |
mod = model[j];
|
|
|
1355 |
|
|
|
1356 |
if (id) {
|
|
|
1357 |
assigned[j] = found = id + "" === id
|
|
|
1358 |
? (ob[id] && (getterNames[id] ? mod[id]() : mod[id]) === ob[id])
|
|
|
1359 |
: id(mod, ob);
|
|
|
1360 |
}
|
|
|
1361 |
}
|
|
|
1362 |
if (found) {
|
|
|
1363 |
mod.merge(ob);
|
|
|
1364 |
newModArr.push(mod);
|
|
|
1365 |
} else {
|
|
|
1366 |
newModArr.push(childOb = vm.map(ob));
|
|
|
1367 |
if (parentRef) {
|
|
|
1368 |
addParentRef(childOb, parentRef, parent);
|
|
|
1369 |
}
|
|
|
1370 |
}
|
|
|
1371 |
}
|
|
|
1372 |
if ($observable) {
|
|
|
1373 |
$observable(model).refresh(newModArr, true);
|
|
|
1374 |
} else {
|
|
|
1375 |
model.splice.apply(model, [0, model.length].concat(newModArr));
|
|
|
1376 |
}
|
|
|
1377 |
return;
|
|
|
1378 |
}
|
|
|
1379 |
iterate(data, function(ob, viewModel, getter, parentRef) {
|
|
|
1380 |
if (viewModel) {
|
|
|
1381 |
model[getter]().merge(ob, model, parentRef); // Update typed property
|
|
|
1382 |
} else if (model[getter]() !== ob) {
|
|
|
1383 |
model[getter](ob); // Update non-typed property
|
|
|
1384 |
}
|
|
|
1385 |
});
|
|
|
1386 |
for (prop in data) {
|
|
|
1387 |
if (prop !== $expando && !getterNames[prop]) {
|
|
|
1388 |
model[prop] = data[prop];
|
|
|
1389 |
}
|
|
|
1390 |
}
|
|
|
1391 |
}
|
|
|
1392 |
|
|
|
1393 |
function unmap() {
|
|
|
1394 |
var ob, prop, getterType, arr, value,
|
|
|
1395 |
k = 0,
|
|
|
1396 |
model = this;
|
|
|
1397 |
|
|
|
1398 |
function unmapArray(modelArr) {
|
|
|
1399 |
var arr = [],
|
|
|
1400 |
i = 0,
|
|
|
1401 |
l = modelArr.length;
|
|
|
1402 |
for (; i<l; i++) {
|
|
|
1403 |
arr.push(modelArr[i].unmap());
|
|
|
1404 |
}
|
|
|
1405 |
return arr;
|
|
|
1406 |
}
|
|
|
1407 |
|
|
|
1408 |
if ($isArray(model)) {
|
|
|
1409 |
return unmapArray(model);
|
|
|
1410 |
}
|
|
|
1411 |
ob = {};
|
|
|
1412 |
for (; k < getterCount; k++) {
|
|
|
1413 |
prop = getters[k];
|
|
|
1414 |
getterType = undefined;
|
|
|
1415 |
if (prop + "" !== prop) {
|
|
|
1416 |
getterType = prop;
|
|
|
1417 |
prop = getterType.getter;
|
|
|
1418 |
}
|
|
|
1419 |
value = model[prop]();
|
|
|
1420 |
ob[prop] = getterType && value && viewModels[getterType.type]
|
|
|
1421 |
? $isArray(value)
|
|
|
1422 |
? unmapArray(value)
|
|
|
1423 |
: value.unmap()
|
|
|
1424 |
: value;
|
|
|
1425 |
}
|
|
|
1426 |
for (prop in model) {
|
|
|
1427 |
if (model.hasOwnProperty(prop) && (prop.charAt(0) !== "_" || !getterNames[prop.slice(1)]) && prop !== $expando && !$isFunction(model[prop])) {
|
|
|
1428 |
ob[prop] = model[prop];
|
|
|
1429 |
}
|
|
|
1430 |
}
|
|
|
1431 |
return ob;
|
|
|
1432 |
}
|
|
|
1433 |
|
|
|
1434 |
JsvVm.prototype = proto;
|
|
|
1435 |
|
|
|
1436 |
for (i=0; i < getterCount; i++) {
|
|
|
1437 |
(function(getter) {
|
|
|
1438 |
getter = getter.getter || getter;
|
|
|
1439 |
getterNames[getter] = i+1;
|
|
|
1440 |
var privField = "_" + getter;
|
|
|
1441 |
|
|
|
1442 |
args += (args ? "," : "") + getter;
|
|
|
1443 |
cnstr += "this." + privField + " = " + getter + ";\n";
|
|
|
1444 |
proto[getter] = proto[getter] || function(val) {
|
|
|
1445 |
if (!arguments.length) {
|
|
|
1446 |
return this[privField]; // If there is no argument, use as a getter
|
|
|
1447 |
}
|
|
|
1448 |
if ($observable) {
|
|
|
1449 |
$observable(this).setProperty(getter, val);
|
|
|
1450 |
} else {
|
|
|
1451 |
this[privField] = val;
|
|
|
1452 |
}
|
|
|
1453 |
};
|
|
|
1454 |
|
|
|
1455 |
if ($observable) {
|
|
|
1456 |
proto[getter].set = proto[getter].set || function(val) {
|
|
|
1457 |
this[privField] = val; // Setter called by observable property change
|
|
|
1458 |
};
|
|
|
1459 |
}
|
|
|
1460 |
})(getters[i]);
|
|
|
1461 |
}
|
|
|
1462 |
|
|
|
1463 |
// Constructor for new viewModel instance.
|
|
|
1464 |
cnstr = new Function(args, cnstr);
|
|
|
1465 |
|
|
|
1466 |
constructor = function() {
|
|
|
1467 |
cnstr.apply(this, arguments);
|
|
|
1468 |
// Pass additional parentRef str and parent obj to have a parentRef pointer on instance
|
|
|
1469 |
if (parent = arguments[getterCount + 1]) {
|
|
|
1470 |
addParentRef(this, arguments[getterCount], parent);
|
|
|
1471 |
}
|
|
|
1472 |
};
|
|
|
1473 |
|
|
|
1474 |
constructor.prototype = proto;
|
|
|
1475 |
proto.constructor = constructor;
|
|
|
1476 |
|
|
|
1477 |
vm.map = map;
|
|
|
1478 |
vm.getters = getters;
|
|
|
1479 |
vm.extend = extend;
|
|
|
1480 |
vm.id = id;
|
|
|
1481 |
return vm;
|
|
|
1482 |
}
|
|
|
1483 |
|
|
|
1484 |
function tmplObject(markup, options) {
|
|
|
1485 |
// Template object constructor
|
|
|
1486 |
var htmlTag,
|
|
|
1487 |
wrapMap = $subSettingsAdvanced._wm || {}, // Only used in JsViews. Otherwise empty: {}
|
|
|
1488 |
tmpl = {
|
|
|
1489 |
tmpls: [],
|
|
|
1490 |
links: {}, // Compiled functions for link expressions
|
|
|
1491 |
bnds: [],
|
|
|
1492 |
_is: "template",
|
|
|
1493 |
render: renderContent
|
|
|
1494 |
};
|
|
|
1495 |
|
|
|
1496 |
if (options) {
|
|
|
1497 |
tmpl = $extend(tmpl, options);
|
|
|
1498 |
}
|
|
|
1499 |
|
|
|
1500 |
tmpl.markup = markup;
|
|
|
1501 |
if (!tmpl.htmlTag) {
|
|
|
1502 |
// Set tmpl.tag to the top-level HTML tag used in the template, if any...
|
|
|
1503 |
htmlTag = rFirstElem.exec(markup);
|
|
|
1504 |
tmpl.htmlTag = htmlTag ? htmlTag[1].toLowerCase() : "";
|
|
|
1505 |
}
|
|
|
1506 |
htmlTag = wrapMap[tmpl.htmlTag];
|
|
|
1507 |
if (htmlTag && htmlTag !== wrapMap.div) {
|
|
|
1508 |
// When using JsViews, we trim templates which are inserted into HTML contexts where text nodes are not rendered (i.e. not 'Phrasing Content').
|
|
|
1509 |
// Currently not trimmed for <li> tag. (Not worth adding perf cost)
|
|
|
1510 |
tmpl.markup = $.trim(tmpl.markup);
|
|
|
1511 |
}
|
|
|
1512 |
|
|
|
1513 |
return tmpl;
|
|
|
1514 |
}
|
|
|
1515 |
|
|
|
1516 |
//==============
|
|
|
1517 |
// registerStore
|
|
|
1518 |
//==============
|
|
|
1519 |
|
|
|
1520 |
/**
|
|
|
1521 |
* Internal. Register a store type (used for template, tags, helpers, converters)
|
|
|
1522 |
*/
|
|
|
1523 |
function registerStore(storeName, storeSettings) {
|
|
|
1524 |
|
|
|
1525 |
/**
|
|
|
1526 |
* Generic store() function to register item, named item, or hash of items
|
|
|
1527 |
* Also used as hash to store the registered items
|
|
|
1528 |
* Used as implementation of $.templates(), $.views.templates(), $.views.tags(), $.views.helpers() and $.views.converters()
|
|
|
1529 |
*
|
|
|
1530 |
* @param {string|hash} name name - or selector, in case of $.templates(). Or hash of items
|
|
|
1531 |
* @param {any} [item] (e.g. markup for named template)
|
|
|
1532 |
* @param {template} [parentTmpl] For item being registered as private resource of template
|
|
|
1533 |
* @returns {any|$.views} item, e.g. compiled template - or $.views in case of registering hash of items
|
|
|
1534 |
*/
|
|
|
1535 |
function theStore(name, item, parentTmpl) {
|
|
|
1536 |
// The store is also the function used to add items to the store. e.g. $.templates, or $.views.tags
|
|
|
1537 |
|
|
|
1538 |
// For store of name 'thing', Call as:
|
|
|
1539 |
// $.views.things(items[, parentTmpl]),
|
|
|
1540 |
// or $.views.things(name[, item, parentTmpl])
|
|
|
1541 |
|
|
|
1542 |
var compile, itemName, thisStore, cnt,
|
|
|
1543 |
onStore = $sub.onStore[storeName];
|
|
|
1544 |
|
|
|
1545 |
if (name && typeof name === OBJECT && !name.nodeType && !name.markup && !name.getTgt && !(storeName === "viewModel" && name.getters || name.extend)) {
|
|
|
1546 |
// Call to $.views.things(items[, parentTmpl]),
|
|
|
1547 |
|
|
|
1548 |
// Adding items to the store
|
|
|
1549 |
// If name is a hash, then item is parentTmpl. Iterate over hash and call store for key.
|
|
|
1550 |
for (itemName in name) {
|
|
|
1551 |
theStore(itemName, name[itemName], item);
|
|
|
1552 |
}
|
|
|
1553 |
return item || $views;
|
|
|
1554 |
}
|
|
|
1555 |
// Adding a single unnamed item to the store
|
|
|
1556 |
if (name && "" + name !== name) { // name must be a string
|
|
|
1557 |
parentTmpl = item;
|
|
|
1558 |
item = name;
|
|
|
1559 |
name = undefined;
|
|
|
1560 |
}
|
|
|
1561 |
thisStore = parentTmpl
|
|
|
1562 |
? storeName === "viewModel"
|
|
|
1563 |
? parentTmpl
|
|
|
1564 |
: (parentTmpl[storeNames] = parentTmpl[storeNames] || {})
|
|
|
1565 |
: theStore;
|
|
|
1566 |
compile = storeSettings.compile;
|
|
|
1567 |
|
|
|
1568 |
if (item === undefined) {
|
|
|
1569 |
item = compile ? name : thisStore[name];
|
|
|
1570 |
name = undefined;
|
|
|
1571 |
}
|
|
|
1572 |
if (item === null) {
|
|
|
1573 |
// If item is null, delete this entry
|
|
|
1574 |
if (name) {
|
|
|
1575 |
delete thisStore[name];
|
|
|
1576 |
}
|
|
|
1577 |
} else {
|
|
|
1578 |
if (compile) {
|
|
|
1579 |
item = compile.call(thisStore, name, item, parentTmpl, 0) || {};
|
|
|
1580 |
item._is = storeName; // Only do this for compiled objects (tags, templates...)
|
|
|
1581 |
}
|
|
|
1582 |
if (name) {
|
|
|
1583 |
thisStore[name] = item;
|
|
|
1584 |
}
|
|
|
1585 |
}
|
|
|
1586 |
if (onStore) {
|
|
|
1587 |
// e.g. JsViews integration
|
|
|
1588 |
onStore(name, item, parentTmpl, compile);
|
|
|
1589 |
}
|
|
|
1590 |
return item;
|
|
|
1591 |
}
|
|
|
1592 |
|
|
|
1593 |
var storeNames = storeName + "s";
|
|
|
1594 |
$views[storeNames] = theStore;
|
|
|
1595 |
}
|
|
|
1596 |
|
|
|
1597 |
/**
|
|
|
1598 |
* Add settings such as:
|
|
|
1599 |
* $.views.settings.allowCode(true)
|
|
|
1600 |
* @param {boolean} value
|
|
|
1601 |
* @returns {Settings}
|
|
|
1602 |
*
|
|
|
1603 |
* allowCode = $.views.settings.allowCode()
|
|
|
1604 |
* @returns {boolean}
|
|
|
1605 |
*/
|
|
|
1606 |
function addSetting(st) {
|
|
|
1607 |
$viewsSettings[st] = $viewsSettings[st] || function(value) {
|
|
|
1608 |
return arguments.length
|
|
|
1609 |
? ($subSettings[st] = value, $viewsSettings)
|
|
|
1610 |
: $subSettings[st];
|
|
|
1611 |
};
|
|
|
1612 |
}
|
|
|
1613 |
|
|
|
1614 |
//========================
|
|
|
1615 |
// dataMap for render only
|
|
|
1616 |
//========================
|
|
|
1617 |
|
|
|
1618 |
function dataMap(mapDef) {
|
|
|
1619 |
function Map(source, options) {
|
|
|
1620 |
this.tgt = mapDef.getTgt(source, options);
|
|
|
1621 |
options.map = this;
|
|
|
1622 |
}
|
|
|
1623 |
|
|
|
1624 |
if ($isFunction(mapDef)) {
|
|
|
1625 |
// Simple map declared as function
|
|
|
1626 |
mapDef = {
|
|
|
1627 |
getTgt: mapDef
|
|
|
1628 |
};
|
|
|
1629 |
}
|
|
|
1630 |
|
|
|
1631 |
if (mapDef.baseMap) {
|
|
|
1632 |
mapDef = $extend($extend({}, mapDef.baseMap), mapDef);
|
|
|
1633 |
}
|
|
|
1634 |
|
|
|
1635 |
mapDef.map = function(source, options) {
|
|
|
1636 |
return new Map(source, options);
|
|
|
1637 |
};
|
|
|
1638 |
return mapDef;
|
|
|
1639 |
}
|
|
|
1640 |
|
|
|
1641 |
//==============
|
|
|
1642 |
// renderContent
|
|
|
1643 |
//==============
|
|
|
1644 |
|
|
|
1645 |
/** Render the template as a string, using the specified data and helpers/context
|
|
|
1646 |
* $("#tmpl").render(), tmpl.render(), tagCtx.render(), $.render.namedTmpl()
|
|
|
1647 |
*
|
|
|
1648 |
* @param {any} data
|
|
|
1649 |
* @param {hash} [context] helpers or context
|
|
|
1650 |
* @param {boolean} [noIteration]
|
|
|
1651 |
* @param {View} [parentView] internal
|
|
|
1652 |
* @param {string} [key] internal
|
|
|
1653 |
* @param {function} [onRender] internal
|
|
|
1654 |
* @returns {string} rendered template internal
|
|
|
1655 |
*/
|
|
|
1656 |
function renderContent(data, context, noIteration, parentView, key, onRender) {
|
|
|
1657 |
var i, l, tag, tmpl, tagCtx, isTopRenderCall, prevData, prevIndex,
|
|
|
1658 |
view = parentView,
|
|
|
1659 |
result = "";
|
|
|
1660 |
|
|
|
1661 |
if (context === true) {
|
|
|
1662 |
noIteration = context; // passing boolean as second param - noIteration
|
|
|
1663 |
context = undefined;
|
|
|
1664 |
} else if (typeof context !== OBJECT) {
|
|
|
1665 |
context = undefined; // context must be a boolean (noIteration) or a plain object
|
|
|
1666 |
}
|
|
|
1667 |
|
|
|
1668 |
if (tag = this.tag) {
|
|
|
1669 |
// This is a call from renderTag or tagCtx.render(...)
|
|
|
1670 |
tagCtx = this;
|
|
|
1671 |
view = view || tagCtx.view;
|
|
|
1672 |
tmpl = view._getTmpl(tag.template || tagCtx.tmpl);
|
|
|
1673 |
if (!arguments.length) {
|
|
|
1674 |
data = tag.contentCtx && $isFunction(tag.contentCtx)
|
|
|
1675 |
? data = tag.contentCtx(data)
|
|
|
1676 |
: view; // Default data context for wrapped block content is the first argument
|
|
|
1677 |
}
|
|
|
1678 |
} else {
|
|
|
1679 |
// This is a template.render(...) call
|
|
|
1680 |
tmpl = this;
|
|
|
1681 |
}
|
|
|
1682 |
|
|
|
1683 |
if (tmpl) {
|
|
|
1684 |
if (!parentView && data && data._is === "view") {
|
|
|
1685 |
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
|
|
|
1686 |
}
|
|
|
1687 |
|
|
|
1688 |
if (view && data === view) {
|
|
|
1689 |
// Inherit the data from the parent view.
|
|
|
1690 |
data = view.data;
|
|
|
1691 |
}
|
|
|
1692 |
|
|
|
1693 |
isTopRenderCall = !view;
|
|
|
1694 |
isRenderCall = isRenderCall || isTopRenderCall;
|
|
|
1695 |
if (isTopRenderCall) {
|
|
|
1696 |
(context = context || {}).root = data; // Provide ~root as shortcut to top-level data.
|
|
|
1697 |
}
|
|
|
1698 |
if (!isRenderCall || $subSettingsAdvanced.useViews || tmpl.useViews || view && view !== topView) {
|
|
|
1699 |
result = renderWithViews(tmpl, data, context, noIteration, view, key, onRender, tag);
|
|
|
1700 |
} else {
|
|
|
1701 |
if (view) { // In a block
|
|
|
1702 |
prevData = view.data;
|
|
|
1703 |
prevIndex = view.index;
|
|
|
1704 |
view.index = indexStr;
|
|
|
1705 |
} else {
|
|
|
1706 |
view = topView;
|
|
|
1707 |
prevData = view.data;
|
|
|
1708 |
view.data = data;
|
|
|
1709 |
view.ctx = context;
|
|
|
1710 |
}
|
|
|
1711 |
if ($isArray(data) && !noIteration) {
|
|
|
1712 |
// Create a view for the array, whose child views correspond to each data item. (Note: if key and parentView are passed in
|
|
|
1713 |
// along with parent view, treat as insert -e.g. from view.addViews - so parentView is already the view item for array)
|
|
|
1714 |
for (i = 0, l = data.length; i < l; i++) {
|
|
|
1715 |
view.index = i;
|
|
|
1716 |
view.data = data[i];
|
|
|
1717 |
result += tmpl.fn(data[i], view, $sub);
|
|
|
1718 |
}
|
|
|
1719 |
} else {
|
|
|
1720 |
view.data = data;
|
|
|
1721 |
result += tmpl.fn(data, view, $sub);
|
|
|
1722 |
}
|
|
|
1723 |
view.data = prevData;
|
|
|
1724 |
view.index = prevIndex;
|
|
|
1725 |
}
|
|
|
1726 |
if (isTopRenderCall) {
|
|
|
1727 |
isRenderCall = undefined;
|
|
|
1728 |
}
|
|
|
1729 |
}
|
|
|
1730 |
return result;
|
|
|
1731 |
}
|
|
|
1732 |
|
|
|
1733 |
function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, tag) {
|
|
|
1734 |
// Render template against data as a tree of subviews (nested rendered template instances), or as a string (top-level template).
|
|
|
1735 |
// If the data is the parent view, treat as noIteration, re-render with the same data context.
|
|
|
1736 |
// tmpl can be a string (e.g. rendered by a tag.render() method), or a compiled template.
|
|
|
1737 |
var i, l, newView, childView, itemResult, swapContent, contentTmpl, outerOnRender, tmplName, itemVar, newCtx, tagCtx, noLinking,
|
|
|
1738 |
result = "";
|
|
|
1739 |
|
|
|
1740 |
if (tag) {
|
|
|
1741 |
// This is a call from renderTag or tagCtx.render(...)
|
|
|
1742 |
tmplName = tag.tagName;
|
|
|
1743 |
tagCtx = tag.tagCtx;
|
|
|
1744 |
context = context ? extendCtx(context, tag.ctx) : tag.ctx;
|
|
|
1745 |
|
|
|
1746 |
if (tmpl === view.content) { // {{xxx tmpl=#content}}
|
|
|
1747 |
contentTmpl = tmpl !== view.ctx._wrp // We are rendering the #content
|
|
|
1748 |
? 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
|
|
|
1749 |
: undefined; // #content was the view.ctx._wrp block content - so within this view, there is no longer any #content to wrap.
|
|
|
1750 |
} else if (tmpl !== tagCtx.content) {
|
|
|
1751 |
if (tmpl === tag.template) { // Rendering {{tag}} tag.template, replacing block content.
|
|
|
1752 |
contentTmpl = tagCtx.tmpl; // Set #content to block content (or wrapped block content if tagCtx.props.tmpl is set)
|
|
|
1753 |
context._wrp = tagCtx.content; // Pass wrapped block content to nested views
|
|
|
1754 |
} else { // Rendering tagCtx.props.tmpl wrapper
|
|
|
1755 |
contentTmpl = tagCtx.content || view.content; // Set #content to wrapped block content
|
|
|
1756 |
}
|
|
|
1757 |
} else {
|
|
|
1758 |
contentTmpl = view.content; // Nested views inherit same wrapped #content property
|
|
|
1759 |
}
|
|
|
1760 |
|
|
|
1761 |
if (tagCtx.props.link === false) {
|
|
|
1762 |
// link=false setting on block tag
|
|
|
1763 |
// We will override inherited value of link by the explicit setting link=false taken from props
|
|
|
1764 |
// The child views of an unlinked view are also unlinked. So setting child back to true will not have any effect.
|
|
|
1765 |
context = context || {};
|
|
|
1766 |
context.link = false;
|
|
|
1767 |
}
|
|
|
1768 |
}
|
|
|
1769 |
|
|
|
1770 |
if (view) {
|
|
|
1771 |
onRender = onRender || view._.onRender;
|
|
|
1772 |
noLinking = context && context.link === false;
|
|
|
1773 |
|
|
|
1774 |
if (noLinking && view._.nl) {
|
|
|
1775 |
onRender = undefined;
|
|
|
1776 |
}
|
|
|
1777 |
|
|
|
1778 |
context = extendCtx(context, view.ctx);
|
|
|
1779 |
tagCtx = !tag && view.tag
|
|
|
1780 |
? view.tag.tagCtxs[view.tagElse]
|
|
|
1781 |
: tagCtx;
|
|
|
1782 |
}
|
|
|
1783 |
|
|
|
1784 |
if (itemVar = tagCtx && tagCtx.props.itemVar) {
|
|
|
1785 |
if (itemVar[0] !== "~") {
|
|
|
1786 |
syntaxError("Use itemVar='~myItem'");
|
|
|
1787 |
}
|
|
|
1788 |
itemVar = itemVar.slice(1);
|
|
|
1789 |
}
|
|
|
1790 |
|
|
|
1791 |
if (key === true) {
|
|
|
1792 |
swapContent = true;
|
|
|
1793 |
key = 0;
|
|
|
1794 |
}
|
|
|
1795 |
|
|
|
1796 |
// If link===false, do not call onRender, so no data-linking marker nodes
|
|
|
1797 |
if (onRender && tag && tag._.noVws) {
|
|
|
1798 |
onRender = undefined;
|
|
|
1799 |
}
|
|
|
1800 |
outerOnRender = onRender;
|
|
|
1801 |
if (onRender === true) {
|
|
|
1802 |
// Used by view.refresh(). Don't create a new wrapper view.
|
|
|
1803 |
outerOnRender = undefined;
|
|
|
1804 |
onRender = view._.onRender;
|
|
|
1805 |
}
|
|
|
1806 |
// Set additional context on views created here, (as modified context inherited from the parent, and to be inherited by child views)
|
|
|
1807 |
context = tmpl.helpers
|
|
|
1808 |
? extendCtx(tmpl.helpers, context)
|
|
|
1809 |
: context;
|
|
|
1810 |
|
|
|
1811 |
newCtx = context;
|
|
|
1812 |
if ($isArray(data) && !noIteration) {
|
|
|
1813 |
// Create a view for the array, whose child views correspond to each data item. (Note: if key and view are passed in
|
|
|
1814 |
// along with parent view, treat as insert -e.g. from view.addViews - so view is already the view item for array)
|
|
|
1815 |
newView = swapContent
|
|
|
1816 |
? view
|
|
|
1817 |
: (key !== undefined && view)
|
|
|
1818 |
|| new View(context, "array", view, data, tmpl, key, onRender, contentTmpl);
|
|
|
1819 |
newView._.nl= noLinking;
|
|
|
1820 |
if (view && view._.useKey) {
|
|
|
1821 |
// Parent is not an 'array view'
|
|
|
1822 |
newView._.bnd = !tag || tag._.bnd && tag; // For array views that are data bound for collection change events, set the
|
|
|
1823 |
// 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 ...}}
|
|
|
1824 |
newView.tag = tag;
|
|
|
1825 |
}
|
|
|
1826 |
for (i = 0, l = data.length; i < l; i++) {
|
|
|
1827 |
// Create a view for each data item.
|
|
|
1828 |
childView = new View(newCtx, "item", newView, data[i], tmpl, (key || 0) + i, onRender, newView.content);
|
|
|
1829 |
if (itemVar) {
|
|
|
1830 |
(childView.ctx = $extend({}, newCtx))[itemVar] = $sub._cp(data[i], "#data", childView);
|
|
|
1831 |
}
|
|
|
1832 |
itemResult = tmpl.fn(data[i], childView, $sub);
|
|
|
1833 |
result += newView._.onRender ? newView._.onRender(itemResult, childView) : itemResult;
|
|
|
1834 |
}
|
|
|
1835 |
} else {
|
|
|
1836 |
// Create a view for singleton data object. The type of the view will be the tag name, e.g. "if" or "mytag" except for
|
|
|
1837 |
// "item", "array" and "data" views. A "data" view is from programmatic render(object) against a 'singleton'.
|
|
|
1838 |
newView = swapContent ? view : new View(newCtx, tmplName || "data", view, data, tmpl, key, onRender, contentTmpl);
|
|
|
1839 |
|
|
|
1840 |
if (itemVar) {
|
|
|
1841 |
(newView.ctx = $extend({}, newCtx))[itemVar] = $sub._cp(data, "#data", newView);
|
|
|
1842 |
}
|
|
|
1843 |
|
|
|
1844 |
newView.tag = tag;
|
|
|
1845 |
newView._.nl = noLinking;
|
|
|
1846 |
result += tmpl.fn(data, newView, $sub);
|
|
|
1847 |
}
|
|
|
1848 |
if (tag) {
|
|
|
1849 |
newView.tagElse = tagCtx.index;
|
|
|
1850 |
tagCtx.contentView = newView;
|
|
|
1851 |
}
|
|
|
1852 |
return outerOnRender ? outerOnRender(result, newView) : result;
|
|
|
1853 |
}
|
|
|
1854 |
|
|
|
1855 |
//===========================
|
|
|
1856 |
// Build and compile template
|
|
|
1857 |
//===========================
|
|
|
1858 |
|
|
|
1859 |
// Generate a reusable function that will serve to render a template against data
|
|
|
1860 |
// (Compile AST then build template function)
|
|
|
1861 |
|
|
|
1862 |
function onRenderError(e, view, fallback) {
|
|
|
1863 |
var message = fallback !== undefined
|
|
|
1864 |
? $isFunction(fallback)
|
|
|
1865 |
? fallback.call(view.data, e, view)
|
|
|
1866 |
: fallback || ""
|
|
|
1867 |
: "{Error: " + (e.message||e) + "}";
|
|
|
1868 |
|
|
|
1869 |
if ($subSettings.onError && (fallback = $subSettings.onError.call(view.data, e, fallback && message, view)) !== undefined) {
|
|
|
1870 |
message = fallback; // There is a settings.debugMode(handler) onError override. Call it, and use return value (if any) to replace message
|
|
|
1871 |
}
|
|
|
1872 |
return view && !view._lc ? $converters.html(message) : message; // For data-link=\"{... onError=...}"... See onDataLinkedTagChange
|
|
|
1873 |
}
|
|
|
1874 |
|
|
|
1875 |
function error(message) {
|
|
|
1876 |
throw new $sub.Err(message);
|
|
|
1877 |
}
|
|
|
1878 |
|
|
|
1879 |
function syntaxError(message) {
|
|
|
1880 |
error("Syntax error\n" + message);
|
|
|
1881 |
}
|
|
|
1882 |
|
|
|
1883 |
function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) {
|
|
|
1884 |
// Compile markup to AST (abtract syntax tree) then build the template function code from the AST nodes
|
|
|
1885 |
// Used for compiling templates, and also by JsViews to build functions for data link expressions
|
|
|
1886 |
|
|
|
1887 |
//==== nested functions ====
|
|
|
1888 |
function pushprecedingContent(shift) {
|
|
|
1889 |
shift -= loc;
|
|
|
1890 |
if (shift) {
|
|
|
1891 |
content.push(markup.substr(loc, shift).replace(rNewLine, "\\n"));
|
|
|
1892 |
}
|
|
|
1893 |
}
|
|
|
1894 |
|
|
|
1895 |
function blockTagCheck(tagName, block) {
|
|
|
1896 |
if (tagName) {
|
|
|
1897 |
tagName += '}}';
|
|
|
1898 |
// '{{include}} block has {{/for}} with no open {{for}}'
|
|
|
1899 |
syntaxError((
|
|
|
1900 |
block
|
|
|
1901 |
? '{{' + block + '}} block has {{/' + tagName + ' without {{' + tagName
|
|
|
1902 |
: 'Unmatched or missing {{/' + tagName) + ', in template:\n' + markup);
|
|
|
1903 |
}
|
|
|
1904 |
}
|
|
|
1905 |
|
|
|
1906 |
function parseTag(all, bind, tagName, converter, colon, html, codeTag, params, slash, bind2, closeBlock, index) {
|
|
|
1907 |
/*
|
|
|
1908 |
|
|
|
1909 |
bind tagName cvt cln html code params slash bind2 closeBlk comment
|
|
|
1910 |
/(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}/g
|
|
|
1911 |
|
|
|
1912 |
(?:
|
|
|
1913 |
{(\^)?{ bind
|
|
|
1914 |
(?:
|
|
|
1915 |
(\w+ tagName
|
|
|
1916 |
(?=[\/\s}])
|
|
|
1917 |
)
|
|
|
1918 |
|
|
|
|
1919 |
(\w+)?(:) converter colon
|
|
|
1920 |
|
|
|
|
1921 |
(>) html
|
|
|
1922 |
|
|
|
|
1923 |
(\*) codeTag
|
|
|
1924 |
)
|
|
|
1925 |
\s*
|
|
|
1926 |
( params
|
|
|
1927 |
(?:[^}]|}(?!}))*?
|
|
|
1928 |
)
|
|
|
1929 |
(\/)? slash
|
|
|
1930 |
|
|
|
|
1931 |
{(\^)?{ bind2
|
|
|
1932 |
(?:
|
|
|
1933 |
(?:\/(\w+))\s* closeBlock
|
|
|
1934 |
|
|
|
|
1935 |
!--[\s\S]*?-- comment
|
|
|
1936 |
)
|
|
|
1937 |
)
|
|
|
1938 |
}}/g
|
|
|
1939 |
|
|
|
1940 |
*/
|
|
|
1941 |
if (codeTag && bind || slash && !tagName || params && params.slice(-1) === ":" || bind2) {
|
|
|
1942 |
syntaxError(all);
|
|
|
1943 |
}
|
|
|
1944 |
|
|
|
1945 |
// Build abstract syntax tree (AST): [tagName, converter, params, content, hash, bindings, contentMarkup]
|
|
|
1946 |
if (html) {
|
|
|
1947 |
colon = ":";
|
|
|
1948 |
converter = HTML;
|
|
|
1949 |
}
|
|
|
1950 |
slash = slash || isLinkExpr && !hasElse;
|
|
|
1951 |
|
|
|
1952 |
var late, openTagName, isLateOb,
|
|
|
1953 |
pathBindings = (bind || isLinkExpr) && [[]], // pathBindings is an array of arrays for arg bindings and a hash of arrays for prop bindings
|
|
|
1954 |
props = "",
|
|
|
1955 |
args = "",
|
|
|
1956 |
ctxProps = "",
|
|
|
1957 |
paramsArgs = "",
|
|
|
1958 |
paramsProps = "",
|
|
|
1959 |
paramsCtxProps = "",
|
|
|
1960 |
onError = "",
|
|
|
1961 |
useTrigger = "",
|
|
|
1962 |
// Block tag if not self-closing and not {{:}} or {{>}} (special case) and not a data-link expression
|
|
|
1963 |
block = !slash && !colon;
|
|
|
1964 |
|
|
|
1965 |
//==== nested helper function ====
|
|
|
1966 |
tagName = tagName || (params = params || "#data", colon); // {{:}} is equivalent to {{:#data}}
|
|
|
1967 |
pushprecedingContent(index);
|
|
|
1968 |
loc = index + all.length; // location marker - parsed up to here
|
|
|
1969 |
if (codeTag) {
|
|
|
1970 |
if (allowCode) {
|
|
|
1971 |
content.push(["*", "\n" + params.replace(/^:/, "ret+= ").replace(rUnescapeQuotes, "$1") + ";\n"]);
|
|
|
1972 |
}
|
|
|
1973 |
} else if (tagName) {
|
|
|
1974 |
if (tagName === "else") {
|
|
|
1975 |
if (rTestElseIf.test(params)) {
|
|
|
1976 |
syntaxError('For "{{else if expr}}" use "{{else expr}}"');
|
|
|
1977 |
}
|
|
|
1978 |
pathBindings = current[9] && [[]];
|
|
|
1979 |
current[10] = markup.substring(current[10], index); // contentMarkup for block tag
|
|
|
1980 |
openTagName = current[11] || current[0] || syntaxError("Mismatched: " + all);
|
|
|
1981 |
// current[0] is tagName, but for {{else}} nodes, current[11] is tagName of preceding open tag
|
|
|
1982 |
current = stack.pop();
|
|
|
1983 |
content = current[2];
|
|
|
1984 |
block = true;
|
|
|
1985 |
}
|
|
|
1986 |
if (params) {
|
|
|
1987 |
// remove newlines from the params string, to avoid compiled code errors for unterminated strings
|
|
|
1988 |
parseParams(params.replace(rNewLine, " "), pathBindings, tmpl, isLinkExpr)
|
|
|
1989 |
.replace(rBuildHash, function(all, onerror, isCtxPrm, key, keyToken, keyValue, arg, param) {
|
|
|
1990 |
if (key === "this:") {
|
|
|
1991 |
keyValue = "undefined"; // this=some.path is always a to parameter (one-way), so don't need to compile/evaluate some.path initialization
|
|
|
1992 |
}
|
|
|
1993 |
if (param) {
|
|
|
1994 |
isLateOb = isLateOb || param[0] === "@";
|
|
|
1995 |
}
|
|
|
1996 |
key = "'" + keyToken + "':";
|
|
|
1997 |
if (arg) {
|
|
|
1998 |
args += isCtxPrm + keyValue + ",";
|
|
|
1999 |
paramsArgs += "'" + param + "',";
|
|
|
2000 |
} else if (isCtxPrm) { // Contextual parameter, ~foo=expr
|
|
|
2001 |
ctxProps += key + 'j._cp(' + keyValue + ',"' + param + '",view),';
|
|
|
2002 |
// Compiled code for evaluating tagCtx on a tag will have: ctx:{'foo':j._cp(compiledExpr, "expr", view)}
|
|
|
2003 |
paramsCtxProps += key + "'" + param + "',";
|
|
|
2004 |
} else if (onerror) {
|
|
|
2005 |
onError += keyValue;
|
|
|
2006 |
} else {
|
|
|
2007 |
if (keyToken === "trigger") {
|
|
|
2008 |
useTrigger += keyValue;
|
|
|
2009 |
}
|
|
|
2010 |
if (keyToken === "lateRender") {
|
|
|
2011 |
late = param !== "false"; // Render after first pass
|
|
|
2012 |
}
|
|
|
2013 |
props += key + keyValue + ",";
|
|
|
2014 |
paramsProps += key + "'" + param + "',";
|
|
|
2015 |
hasHandlers = hasHandlers || rHasHandlers.test(keyToken);
|
|
|
2016 |
}
|
|
|
2017 |
return "";
|
|
|
2018 |
}).slice(0, -1);
|
|
|
2019 |
}
|
|
|
2020 |
|
|
|
2021 |
if (pathBindings && pathBindings[0]) {
|
|
|
2022 |
pathBindings.pop(); // Remove the binding that was prepared for next arg. (There is always an extra one ready).
|
|
|
2023 |
}
|
|
|
2024 |
|
|
|
2025 |
newNode = [
|
|
|
2026 |
tagName,
|
|
|
2027 |
converter || !!convertBack || hasHandlers || "",
|
|
|
2028 |
block && [],
|
|
|
2029 |
parsedParam(paramsArgs || (tagName === ":" ? "'#data'," : ""), paramsProps, paramsCtxProps), // {{:}} equivalent to {{:#data}}
|
|
|
2030 |
parsedParam(args || (tagName === ":" ? "data," : ""), props, ctxProps),
|
|
|
2031 |
onError,
|
|
|
2032 |
useTrigger,
|
|
|
2033 |
late,
|
|
|
2034 |
isLateOb,
|
|
|
2035 |
pathBindings || 0
|
|
|
2036 |
];
|
|
|
2037 |
content.push(newNode);
|
|
|
2038 |
if (block) {
|
|
|
2039 |
stack.push(current);
|
|
|
2040 |
current = newNode;
|
|
|
2041 |
current[10] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag
|
|
|
2042 |
current[11] = openTagName; // Used for checking syntax (matching close tag)
|
|
|
2043 |
}
|
|
|
2044 |
} else if (closeBlock) {
|
|
|
2045 |
blockTagCheck(closeBlock !== current[0] && closeBlock !== current[11] && closeBlock, current[0]); // Check matching close tag name
|
|
|
2046 |
current[10] = markup.substring(current[10], index); // contentMarkup for block tag
|
|
|
2047 |
current = stack.pop();
|
|
|
2048 |
}
|
|
|
2049 |
blockTagCheck(!current && closeBlock);
|
|
|
2050 |
content = current[2];
|
|
|
2051 |
}
|
|
|
2052 |
//==== /end of nested functions ====
|
|
|
2053 |
|
|
|
2054 |
var i, result, newNode, hasHandlers, bindings,
|
|
|
2055 |
allowCode = $subSettings.allowCode || tmpl && tmpl.allowCode
|
|
|
2056 |
|| $viewsSettings.allowCode === true, // include direct setting of settings.allowCode true for backward compat only
|
|
|
2057 |
astTop = [],
|
|
|
2058 |
loc = 0,
|
|
|
2059 |
stack = [],
|
|
|
2060 |
content = astTop,
|
|
|
2061 |
current = [,,astTop];
|
|
|
2062 |
|
|
|
2063 |
if (allowCode && tmpl._is) {
|
|
|
2064 |
tmpl.allowCode = allowCode;
|
|
|
2065 |
}
|
|
|
2066 |
|
|
|
2067 |
//TODO result = tmplFnsCache[markup]; // Only cache if template is not named and markup length < ...,
|
|
|
2068 |
//and there are no bindings or subtemplates?? Consider standard optimization for data-link="a.b.c"
|
|
|
2069 |
// if (result) {
|
|
|
2070 |
// tmpl.fn = result;
|
|
|
2071 |
// } else {
|
|
|
2072 |
|
|
|
2073 |
// result = markup;
|
|
|
2074 |
if (isLinkExpr) {
|
|
|
2075 |
if (convertBack !== undefined) {
|
|
|
2076 |
markup = markup.slice(0, -convertBack.length - 2) + delimCloseChar0;
|
|
|
2077 |
}
|
|
|
2078 |
markup = delimOpenChar0 + markup + delimCloseChar1;
|
|
|
2079 |
}
|
|
|
2080 |
|
|
|
2081 |
blockTagCheck(stack[0] && stack[0][2].pop()[0]);
|
|
|
2082 |
// Build the AST (abstract syntax tree) under astTop
|
|
|
2083 |
markup.replace(rTag, parseTag);
|
|
|
2084 |
|
|
|
2085 |
pushprecedingContent(markup.length);
|
|
|
2086 |
|
|
|
2087 |
if (loc = astTop[astTop.length - 1]) {
|
|
|
2088 |
blockTagCheck("" + loc !== loc && (+loc[10] === loc[10]) && loc[0]);
|
|
|
2089 |
}
|
|
|
2090 |
// result = tmplFnsCache[markup] = buildCode(astTop, tmpl);
|
|
|
2091 |
// }
|
|
|
2092 |
|
|
|
2093 |
if (isLinkExpr) {
|
|
|
2094 |
result = buildCode(astTop, markup, isLinkExpr);
|
|
|
2095 |
bindings = [];
|
|
|
2096 |
i = astTop.length;
|
|
|
2097 |
while (i--) {
|
|
|
2098 |
bindings.unshift(astTop[i][9]); // With data-link expressions, pathBindings array for tagCtx[i] is astTop[i][9]
|
|
|
2099 |
}
|
|
|
2100 |
setPaths(result, bindings);
|
|
|
2101 |
} else {
|
|
|
2102 |
result = buildCode(astTop, tmpl);
|
|
|
2103 |
}
|
|
|
2104 |
return result;
|
|
|
2105 |
}
|
|
|
2106 |
|
|
|
2107 |
function setPaths(fn, pathsArr) {
|
|
|
2108 |
var key, paths,
|
|
|
2109 |
i = 0,
|
|
|
2110 |
l = pathsArr.length;
|
|
|
2111 |
fn.deps = [];
|
|
|
2112 |
fn.paths = []; // The array of path binding (array/dictionary)s for each tag/else block's args and props
|
|
|
2113 |
for (; i < l; i++) {
|
|
|
2114 |
fn.paths.push(paths = pathsArr[i]);
|
|
|
2115 |
for (key in paths) {
|
|
|
2116 |
if (key !== "_jsvto" && paths.hasOwnProperty(key) && paths[key].length && !paths[key].skp) {
|
|
|
2117 |
fn.deps = fn.deps.concat(paths[key]); // deps is the concatenation of the paths arrays for the different bindings
|
|
|
2118 |
}
|
|
|
2119 |
}
|
|
|
2120 |
}
|
|
|
2121 |
}
|
|
|
2122 |
|
|
|
2123 |
function parsedParam(args, props, ctx) {
|
|
|
2124 |
return [args.slice(0, -1), props.slice(0, -1), ctx.slice(0, -1)];
|
|
|
2125 |
}
|
|
|
2126 |
|
|
|
2127 |
function paramStructure(paramCode, paramVals) {
|
|
|
2128 |
return '\n\tparams:{args:[' + paramCode[0] + '],\n\tprops:{' + paramCode[1] + '}'
|
|
|
2129 |
+ (paramCode[2] ? ',\n\tctx:{' + paramCode[2] + '}' : "")
|
|
|
2130 |
+ '},\n\targs:[' + paramVals[0] + '],\n\tprops:{' + paramVals[1] + '}'
|
|
|
2131 |
+ (paramVals[2] ? ',\n\tctx:{' + paramVals[2] + '}' : "");
|
|
|
2132 |
}
|
|
|
2133 |
|
|
|
2134 |
function parseParams(params, pathBindings, tmpl, isLinkExpr) {
|
|
|
2135 |
|
|
|
2136 |
function parseTokens(all, lftPrn0, lftPrn, bound, path, operator, err, eq, path2, late, prn,
|
|
|
2137 |
comma, lftPrn2, apos, quot, rtPrn, rtPrnDot, prn2, space, index, full) {
|
|
|
2138 |
// /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(~?[\w$.^]+)?\s*((\+\+|--)|\+|-|~(?![\w$])|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?(@)?[#~]?[\w$.^]+)([([])?)|(,\s*)|(?:(\()\s*)?\\?(?:(')|("))|(?:\s*(([)\]])(?=[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,
|
|
|
2139 |
//lftPrn0 lftPrn bound path operator err eq path2 late prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space
|
|
|
2140 |
// (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
|
|
|
2141 |
|
|
|
2142 |
function parsePath(allPath, not, object, helper, view, viewProperty, pathTokens, leafToken) {
|
|
|
2143 |
// /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
|
|
|
2144 |
// not object helper view viewProperty pathTokens leafToken
|
|
|
2145 |
subPath = object === ".";
|
|
|
2146 |
if (object) {
|
|
|
2147 |
path = path.slice(not.length);
|
|
|
2148 |
if (/^\.?constructor$/.test(leafToken||path)) {
|
|
|
2149 |
syntaxError(allPath);
|
|
|
2150 |
}
|
|
|
2151 |
if (!subPath) {
|
|
|
2152 |
allPath = (late // late path @a.b.c: not throw on 'property of undefined' if a undefined, and will use _getOb() after linking to resolve late.
|
|
|
2153 |
? (isLinkExpr ? '' : '(ltOb.lt=ltOb.lt||') + '(ob='
|
|
|
2154 |
: ""
|
|
|
2155 |
)
|
|
|
2156 |
+ (helper
|
|
|
2157 |
? 'view.ctxPrm("' + helper + '")'
|
|
|
2158 |
: view
|
|
|
2159 |
? "view"
|
|
|
2160 |
: "data")
|
|
|
2161 |
+ (late
|
|
|
2162 |
? ')===undefined' + (isLinkExpr ? '' : ')') + '?"":view._getOb(ob,"'
|
|
|
2163 |
: ""
|
|
|
2164 |
)
|
|
|
2165 |
+ (leafToken
|
|
|
2166 |
? (viewProperty
|
|
|
2167 |
? "." + viewProperty
|
|
|
2168 |
: helper
|
|
|
2169 |
? ""
|
|
|
2170 |
: (view ? "" : "." + object)
|
|
|
2171 |
) + (pathTokens || "")
|
|
|
2172 |
: (leafToken = helper ? "" : view ? viewProperty || "" : object, ""));
|
|
|
2173 |
allPath = allPath + (leafToken ? "." + leafToken : "");
|
|
|
2174 |
|
|
|
2175 |
allPath = not + (allPath.slice(0, 9) === "view.data"
|
|
|
2176 |
? allPath.slice(5) // convert #view.data... to data...
|
|
|
2177 |
: allPath)
|
|
|
2178 |
+ (late
|
|
|
2179 |
? (isLinkExpr ? '"': '",ltOb') + (prn ? ',1)':')')
|
|
|
2180 |
: ""
|
|
|
2181 |
);
|
|
|
2182 |
}
|
|
|
2183 |
if (bindings) {
|
|
|
2184 |
binds = named === "_linkTo" ? (bindto = pathBindings._jsvto = pathBindings._jsvto || []) : bndCtx.bd;
|
|
|
2185 |
if (theOb = subPath && binds[binds.length-1]) {
|
|
|
2186 |
if (theOb._cpfn) { // Computed property exprOb
|
|
|
2187 |
while (theOb.sb) {
|
|
|
2188 |
theOb = theOb.sb;
|
|
|
2189 |
}
|
|
|
2190 |
if (theOb.prm) {
|
|
|
2191 |
if (theOb.bnd) {
|
|
|
2192 |
path = "^" + path.slice(1);
|
|
|
2193 |
}
|
|
|
2194 |
theOb.sb = path;
|
|
|
2195 |
theOb.bnd = theOb.bnd || path[0] === "^";
|
|
|
2196 |
}
|
|
|
2197 |
}
|
|
|
2198 |
} else {
|
|
|
2199 |
binds.push(path);
|
|
|
2200 |
}
|
|
|
2201 |
if (prn && !subPath) {
|
|
|
2202 |
pathStart[fnDp] = ind;
|
|
|
2203 |
compiledPathStart[fnDp] = compiledPath[fnDp].length;
|
|
|
2204 |
}
|
|
|
2205 |
}
|
|
|
2206 |
}
|
|
|
2207 |
return allPath;
|
|
|
2208 |
}
|
|
|
2209 |
|
|
|
2210 |
//bound = bindings && bound;
|
|
|
2211 |
if (bound && !eq) {
|
|
|
2212 |
path = bound + path; // e.g. some.fn(...)^some.path - so here path is "^some.path"
|
|
|
2213 |
}
|
|
|
2214 |
operator = operator || "";
|
|
|
2215 |
lftPrn2 = lftPrn2 || "";
|
|
|
2216 |
lftPrn = lftPrn || lftPrn0 || lftPrn2;
|
|
|
2217 |
path = path || path2;
|
|
|
2218 |
|
|
|
2219 |
if (late && (late = !/\)|]/.test(full[index-1]))) {
|
|
|
2220 |
path = path.slice(1).split(".").join("^"); // Late path @z.b.c. Use "^" rather than "." to ensure that deep binding will be used
|
|
|
2221 |
}
|
|
|
2222 |
// Could do this - but not worth perf cost?? :-
|
|
|
2223 |
// if (!path.lastIndexOf("#data.", 0)) { path = path.slice(6); } // If path starts with "#data.", remove that.
|
|
|
2224 |
prn = prn || prn2 || "";
|
|
|
2225 |
var expr, binds, theOb, newOb, subPath, lftPrnFCall, ret,
|
|
|
2226 |
ind = index;
|
|
|
2227 |
|
|
|
2228 |
if (!aposed && !quoted) {
|
|
|
2229 |
if (err) {
|
|
|
2230 |
syntaxError(params);
|
|
|
2231 |
}
|
|
|
2232 |
if (rtPrnDot && bindings) {
|
|
|
2233 |
// 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
|
|
|
2234 |
// 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)
|
|
|
2235 |
expr = pathStart[fnDp-1];
|
|
|
2236 |
if (full.length - 1 > ind - (expr || 0)) { // We need to compile a subexpression
|
|
|
2237 |
expr = $.trim(full.slice(expr, ind + all.length));
|
|
|
2238 |
binds = bindto || bndStack[fnDp-1].bd;
|
|
|
2239 |
// Insert exprOb object, to be used during binding to return the computed object
|
|
|
2240 |
theOb = binds[binds.length-1];
|
|
|
2241 |
if (theOb && theOb.prm) {
|
|
|
2242 |
while (theOb.sb && theOb.sb.prm) {
|
|
|
2243 |
theOb = theOb.sb;
|
|
|
2244 |
}
|
|
|
2245 |
newOb = theOb.sb = {path: theOb.sb, bnd: theOb.bnd};
|
|
|
2246 |
} else {
|
|
|
2247 |
binds.push(newOb = {path: binds.pop()}); // Insert exprOb object, to be used during binding to return the computed object
|
|
|
2248 |
}
|
|
|
2249 |
if (theOb && theOb.sb === newOb) {
|
|
|
2250 |
compiledPath[fnDp] = compiledPath[fnDp-1].slice(theOb._cpPthSt) + compiledPath[fnDp];
|
|
|
2251 |
compiledPath[fnDp-1] = compiledPath[fnDp-1].slice(0, theOb._cpPthSt);
|
|
|
2252 |
}
|
|
|
2253 |
newOb._cpPthSt = compiledPathStart[fnDp-1];
|
|
|
2254 |
newOb._cpKey = expr;
|
|
|
2255 |
|
|
|
2256 |
compiledPath[fnDp] += full.slice(prevIndex, index);
|
|
|
2257 |
prevIndex = index;
|
|
|
2258 |
|
|
|
2259 |
newOb._cpfn = cpFnStore[expr] = cpFnStore[expr] || // Compiled function for computed value: get from store, or compile and store
|
|
|
2260 |
new Function("data,view,j", // Compiled function for computed value in template
|
|
|
2261 |
"//" + expr + "\nvar v;\nreturn ((v=" + compiledPath[fnDp] + (rtPrn === "]" ? ")]" : rtPrn) + ")!=null?v:null);");
|
|
|
2262 |
|
|
|
2263 |
compiledPath[fnDp-1] += (fnCall[prnDp] && $subSettingsAdvanced.cache ? "view.getCache(\"" + expr.replace(rEscapeQuotes, "\\$&") + "\"" : compiledPath[fnDp]);
|
|
|
2264 |
|
|
|
2265 |
newOb.prm = bndCtx.bd;
|
|
|
2266 |
newOb.bnd = newOb.bnd || newOb.path && newOb.path.indexOf("^") >= 0;
|
|
|
2267 |
}
|
|
|
2268 |
compiledPath[fnDp] = "";
|
|
|
2269 |
}
|
|
|
2270 |
if (prn === "[") {
|
|
|
2271 |
prn = "[j._sq(";
|
|
|
2272 |
}
|
|
|
2273 |
if (lftPrn === "[") {
|
|
|
2274 |
lftPrn = "[j._sq(";
|
|
|
2275 |
}
|
|
|
2276 |
}
|
|
|
2277 |
ret = (aposed
|
|
|
2278 |
// within single-quoted string
|
|
|
2279 |
? (aposed = !apos, (aposed ? all : lftPrn2 + '"'))
|
|
|
2280 |
: quoted
|
|
|
2281 |
// within double-quoted string
|
|
|
2282 |
? (quoted = !quot, (quoted ? all : lftPrn2 + '"'))
|
|
|
2283 |
:
|
|
|
2284 |
(
|
|
|
2285 |
(lftPrn
|
|
|
2286 |
? (
|
|
|
2287 |
prnStack[++prnDp] = true,
|
|
|
2288 |
prnInd[prnDp] = 0,
|
|
|
2289 |
bindings && (
|
|
|
2290 |
pathStart[fnDp++] = ind++,
|
|
|
2291 |
bndCtx = bndStack[fnDp] = {bd: []},
|
|
|
2292 |
compiledPath[fnDp] = "",
|
|
|
2293 |
compiledPathStart[fnDp] = 1
|
|
|
2294 |
),
|
|
|
2295 |
lftPrn) // Left paren, (not a function call paren)
|
|
|
2296 |
: "")
|
|
|
2297 |
+ (space
|
|
|
2298 |
? (prnDp
|
|
|
2299 |
? "" // A space within parens or within function call parens, so not a separator for tag args
|
|
|
2300 |
// New arg or prop - so insert backspace \b (\x08) as separator for named params, used subsequently by rBuildHash, and prepare new bindings array
|
|
|
2301 |
: (paramIndex = full.slice(paramIndex, ind), named
|
|
|
2302 |
? (named = boundName = bindto = false, "\b")
|
|
|
2303 |
: "\b,") + paramIndex + (paramIndex = ind + all.length, bindings && pathBindings.push(bndCtx.bd = []), "\b")
|
|
|
2304 |
)
|
|
|
2305 |
: eq
|
|
|
2306 |
// named param. Remove bindings for arg and create instead bindings array for prop
|
|
|
2307 |
? (fnDp && syntaxError(params), bindings && pathBindings.pop(), named = "_" + path, boundName = bound, paramIndex = ind + all.length,
|
|
|
2308 |
bindings && ((bindings = bndCtx.bd = pathBindings[named] = []), bindings.skp = !bound), path + ':')
|
|
|
2309 |
: path
|
|
|
2310 |
// path
|
|
|
2311 |
? (path.split("^").join(".").replace($sub.rPath, parsePath)
|
|
|
2312 |
+ (prn || operator)
|
|
|
2313 |
)
|
|
|
2314 |
: operator
|
|
|
2315 |
// operator
|
|
|
2316 |
? operator
|
|
|
2317 |
: rtPrn
|
|
|
2318 |
// function
|
|
|
2319 |
? rtPrn === "]" ? ")]" : ")"
|
|
|
2320 |
: comma
|
|
|
2321 |
? (fnCall[prnDp] || syntaxError(params), ",") // We don't allow top-level literal arrays or objects
|
|
|
2322 |
: lftPrn0
|
|
|
2323 |
? ""
|
|
|
2324 |
: (aposed = apos, quoted = quot, '"')
|
|
|
2325 |
))
|
|
|
2326 |
);
|
|
|
2327 |
|
|
|
2328 |
if (!aposed && !quoted) {
|
|
|
2329 |
if (rtPrn) {
|
|
|
2330 |
fnCall[prnDp] = false;
|
|
|
2331 |
prnDp--;
|
|
|
2332 |
}
|
|
|
2333 |
}
|
|
|
2334 |
|
|
|
2335 |
if (bindings) {
|
|
|
2336 |
if (!aposed && !quoted) {
|
|
|
2337 |
if (rtPrn) {
|
|
|
2338 |
if (prnStack[prnDp+1]) {
|
|
|
2339 |
bndCtx = bndStack[--fnDp];
|
|
|
2340 |
prnStack[prnDp+1] = false;
|
|
|
2341 |
}
|
|
|
2342 |
prnStart = prnInd[prnDp+1];
|
|
|
2343 |
}
|
|
|
2344 |
if (prn) {
|
|
|
2345 |
prnInd[prnDp+1] = compiledPath[fnDp].length + (lftPrn ? 1 : 0);
|
|
|
2346 |
if (path || rtPrn) {
|
|
|
2347 |
bndCtx = bndStack[++fnDp] = {bd: []};
|
|
|
2348 |
prnStack[prnDp+1] = true;
|
|
|
2349 |
}
|
|
|
2350 |
}
|
|
|
2351 |
}
|
|
|
2352 |
|
|
|
2353 |
compiledPath[fnDp] = (compiledPath[fnDp]||"") + full.slice(prevIndex, index);
|
|
|
2354 |
prevIndex = index+all.length;
|
|
|
2355 |
|
|
|
2356 |
if (!aposed && !quoted) {
|
|
|
2357 |
if (lftPrnFCall = lftPrn && prnStack[prnDp+1]) {
|
|
|
2358 |
compiledPath[fnDp-1] += lftPrn;
|
|
|
2359 |
compiledPathStart[fnDp-1]++;
|
|
|
2360 |
}
|
|
|
2361 |
if (prn === "(" && subPath && !newOb) {
|
|
|
2362 |
compiledPath[fnDp] = compiledPath[fnDp-1].slice(prnStart) + compiledPath[fnDp];
|
|
|
2363 |
compiledPath[fnDp-1] = compiledPath[fnDp-1].slice(0, prnStart);
|
|
|
2364 |
}
|
|
|
2365 |
}
|
|
|
2366 |
compiledPath[fnDp] += lftPrnFCall ? ret.slice(1) : ret;
|
|
|
2367 |
}
|
|
|
2368 |
|
|
|
2369 |
if (!aposed && !quoted && prn) {
|
|
|
2370 |
prnDp++;
|
|
|
2371 |
if (path && prn === "(") {
|
|
|
2372 |
fnCall[prnDp] = true;
|
|
|
2373 |
}
|
|
|
2374 |
}
|
|
|
2375 |
|
|
|
2376 |
if (!aposed && !quoted && prn2) {
|
|
|
2377 |
if (bindings) {
|
|
|
2378 |
compiledPath[fnDp] += prn;
|
|
|
2379 |
}
|
|
|
2380 |
ret += prn;
|
|
|
2381 |
}
|
|
|
2382 |
return ret;
|
|
|
2383 |
}
|
|
|
2384 |
|
|
|
2385 |
var named, bindto, boundName, result,
|
|
|
2386 |
quoted, // boolean for string content in double quotes
|
|
|
2387 |
aposed, // or in single quotes
|
|
|
2388 |
bindings = pathBindings && pathBindings[0], // bindings array for the first arg
|
|
|
2389 |
bndCtx = {bd: bindings},
|
|
|
2390 |
bndStack = {0: bndCtx},
|
|
|
2391 |
paramIndex = 0, // list,
|
|
|
2392 |
// 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
|
|
|
2393 |
// "a.b().c^d().e.f().g" - which has four chained paths, "a.b()", "^c.d()", ".e.f()" and ".g"
|
|
|
2394 |
prnDp = 0, // For tracking paren depth (not function call parens)
|
|
|
2395 |
fnDp = 0, // For tracking depth of function call parens
|
|
|
2396 |
prnInd = {}, // We are in a function call
|
|
|
2397 |
prnStart = 0, // tracks the start of the current path such as c^d() in the above example
|
|
|
2398 |
prnStack = {}, // tracks parens which are not function calls, and so are associated with new bndStack contexts
|
|
|
2399 |
fnCall = {}, // We are in a function call
|
|
|
2400 |
pathStart = {},// tracks the start of the current path such as c^d() in the above example
|
|
|
2401 |
compiledPathStart = {0: 0},
|
|
|
2402 |
compiledPath = {0:""},
|
|
|
2403 |
prevIndex = 0;
|
|
|
2404 |
|
|
|
2405 |
if (params[0] === "@") {
|
|
|
2406 |
params = params.replace(rBracketQuote, ".");
|
|
|
2407 |
}
|
|
|
2408 |
result = (params + (tmpl ? " " : "")).replace($sub.rPrm, parseTokens);
|
|
|
2409 |
|
|
|
2410 |
if (bindings) {
|
|
|
2411 |
result = compiledPath[0];
|
|
|
2412 |
}
|
|
|
2413 |
|
|
|
2414 |
return !prnDp && result || syntaxError(params); // Syntax error if unbalanced parens in params expression
|
|
|
2415 |
}
|
|
|
2416 |
|
|
|
2417 |
function buildCode(ast, tmpl, isLinkExpr) {
|
|
|
2418 |
// Build the template function code from the AST nodes, and set as property on the passed-in template object
|
|
|
2419 |
// Used for compiling templates, and also by JsViews to build functions for data link expressions
|
|
|
2420 |
var i, node, tagName, converter, tagCtx, hasTag, hasEncoder, getsVal, hasCnvt, useCnvt, tmplBindings, pathBindings, params, boundOnErrStart,
|
|
|
2421 |
boundOnErrEnd, tagRender, nestedTmpls, tmplName, nestedTmpl, tagAndElses, content, markup, nextIsElse, oldCode, isElse, isGetVal, tagCtxFn,
|
|
|
2422 |
onError, tagStart, trigger, lateRender, retStrOpen, retStrClose,
|
|
|
2423 |
tmplBindingKey = 0,
|
|
|
2424 |
useViews = $subSettingsAdvanced.useViews || tmpl.useViews || tmpl.tags || tmpl.templates || tmpl.helpers || tmpl.converters,
|
|
|
2425 |
code = "",
|
|
|
2426 |
tmplOptions = {},
|
|
|
2427 |
l = ast.length;
|
|
|
2428 |
|
|
|
2429 |
if ("" + tmpl === tmpl) {
|
|
|
2430 |
tmplName = isLinkExpr ? 'data-link="' + tmpl.replace(rNewLine, " ").slice(1, -1) + '"' : tmpl;
|
|
|
2431 |
tmpl = 0;
|
|
|
2432 |
} else {
|
|
|
2433 |
tmplName = tmpl.tmplName || "unnamed";
|
|
|
2434 |
if (tmpl.allowCode) {
|
|
|
2435 |
tmplOptions.allowCode = true;
|
|
|
2436 |
}
|
|
|
2437 |
if (tmpl.debug) {
|
|
|
2438 |
tmplOptions.debug = true;
|
|
|
2439 |
}
|
|
|
2440 |
tmplBindings = tmpl.bnds;
|
|
|
2441 |
nestedTmpls = tmpl.tmpls;
|
|
|
2442 |
}
|
|
|
2443 |
for (i = 0; i < l; i++) {
|
|
|
2444 |
// AST nodes: [0: tagName, 1: converter, 2: content, 3: params, 4: code, 5: onError, 6: trigger, 7:pathBindings, 8: contentMarkup]
|
|
|
2445 |
node = ast[i];
|
|
|
2446 |
|
|
|
2447 |
// Add newline for each callout to t() c() etc. and each markup string
|
|
|
2448 |
if ("" + node === node) {
|
|
|
2449 |
// a markup string to be inserted
|
|
|
2450 |
code += '+"' + node + '"';
|
|
|
2451 |
} else {
|
|
|
2452 |
// a compiled tag expression to be inserted
|
|
|
2453 |
tagName = node[0];
|
|
|
2454 |
if (tagName === "*") {
|
|
|
2455 |
// Code tag: {{* }}
|
|
|
2456 |
code += ";\n" + node[1] + "\nret=ret";
|
|
|
2457 |
} else {
|
|
|
2458 |
converter = node[1];
|
|
|
2459 |
content = !isLinkExpr && node[2];
|
|
|
2460 |
tagCtx = paramStructure(node[3], params = node[4]);
|
|
|
2461 |
trigger = node[6];
|
|
|
2462 |
lateRender = node[7];
|
|
|
2463 |
if (node[8]) { // latePath @a.b.c or @~a.b.c
|
|
|
2464 |
retStrOpen = "\nvar ob,ltOb={},ctxs=";
|
|
|
2465 |
retStrClose = ";\nctxs.lt=ltOb.lt;\nreturn ctxs;";
|
|
|
2466 |
} else {
|
|
|
2467 |
retStrOpen = "\nreturn ";
|
|
|
2468 |
retStrClose = "";
|
|
|
2469 |
}
|
|
|
2470 |
markup = node[10] && node[10].replace(rUnescapeQuotes, "$1");
|
|
|
2471 |
if (isElse = tagName === "else") {
|
|
|
2472 |
if (pathBindings) {
|
|
|
2473 |
pathBindings.push(node[9]);
|
|
|
2474 |
}
|
|
|
2475 |
} else {
|
|
|
2476 |
onError = node[5] || $subSettings.debugMode !== false && "undefined"; // If debugMode not false, set default onError handler on tag to "undefined" (see onRenderError)
|
|
|
2477 |
if (tmplBindings && (pathBindings = node[9])) { // Array of paths, or false if not data-bound
|
|
|
2478 |
pathBindings = [pathBindings];
|
|
|
2479 |
tmplBindingKey = tmplBindings.push(1); // Add placeholder in tmplBindings for compiled function
|
|
|
2480 |
}
|
|
|
2481 |
}
|
|
|
2482 |
useViews = useViews || params[1] || params[2] || pathBindings || /view.(?!index)/.test(params[0]);
|
|
|
2483 |
// useViews is for perf optimization. For render() we only use views if necessary - for the more advanced scenarios.
|
|
|
2484 |
// We use views if there are props, contextual properties or args with #... (other than #index) - but you can force
|
|
|
2485 |
// using the full view infrastructure, (and pay a perf price) by opting in: Set useViews: true on the template, manually...
|
|
|
2486 |
if (isGetVal = tagName === ":") {
|
|
|
2487 |
if (converter) {
|
|
|
2488 |
tagName = converter === HTML ? ">" : converter + tagName;
|
|
|
2489 |
}
|
|
|
2490 |
} else {
|
|
|
2491 |
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!!
|
|
|
2492 |
// Create template object for nested template
|
|
|
2493 |
nestedTmpl = tmplObject(markup, tmplOptions);
|
|
|
2494 |
nestedTmpl.tmplName = tmplName + "/" + tagName;
|
|
|
2495 |
// Compile to AST and then to compiled function
|
|
|
2496 |
nestedTmpl.useViews = nestedTmpl.useViews || useViews;
|
|
|
2497 |
buildCode(content, nestedTmpl);
|
|
|
2498 |
useViews = nestedTmpl.useViews;
|
|
|
2499 |
nestedTmpls.push(nestedTmpl);
|
|
|
2500 |
}
|
|
|
2501 |
|
|
|
2502 |
if (!isElse) {
|
|
|
2503 |
// This is not an else tag.
|
|
|
2504 |
tagAndElses = tagName;
|
|
|
2505 |
useViews = useViews || tagName && (!$tags[tagName] || !$tags[tagName].flow);
|
|
|
2506 |
// Switch to a new code string for this bound tag (and its elses, if it has any) - for returning the tagCtxs array
|
|
|
2507 |
oldCode = code;
|
|
|
2508 |
code = "";
|
|
|
2509 |
}
|
|
|
2510 |
nextIsElse = ast[i + 1];
|
|
|
2511 |
nextIsElse = nextIsElse && nextIsElse[0] === "else";
|
|
|
2512 |
}
|
|
|
2513 |
tagStart = onError ? ";\ntry{\nret+=" : "\n+";
|
|
|
2514 |
boundOnErrStart = "";
|
|
|
2515 |
boundOnErrEnd = "";
|
|
|
2516 |
|
|
|
2517 |
if (isGetVal && (pathBindings || trigger || converter && converter !== HTML || lateRender)) {
|
|
|
2518 |
// For convertVal we need a compiled function to return the new tagCtx(s)
|
|
|
2519 |
tagCtxFn = new Function("data,view,j", "// " + tmplName + " " + (++tmplBindingKey) + " " + tagName
|
|
|
2520 |
+ retStrOpen + "{" + tagCtx + "};" + retStrClose);
|
|
|
2521 |
tagCtxFn._er = onError;
|
|
|
2522 |
tagCtxFn._tag = tagName;
|
|
|
2523 |
tagCtxFn._bd = !!pathBindings; // data-linked tag {^{.../}}
|
|
|
2524 |
tagCtxFn._lr = lateRender;
|
|
|
2525 |
|
|
|
2526 |
if (isLinkExpr) {
|
|
|
2527 |
return tagCtxFn;
|
|
|
2528 |
}
|
|
|
2529 |
|
|
|
2530 |
setPaths(tagCtxFn, pathBindings);
|
|
|
2531 |
tagRender = 'c("' + converter + '",view,';
|
|
|
2532 |
useCnvt = true;
|
|
|
2533 |
boundOnErrStart = tagRender + tmplBindingKey + ",";
|
|
|
2534 |
boundOnErrEnd = ")";
|
|
|
2535 |
}
|
|
|
2536 |
code += (isGetVal
|
|
|
2537 |
? (isLinkExpr ? (onError ? "try{\n" : "") + "return " : tagStart) + (useCnvt // Call _cnvt if there is a converter: {{cnvt: ... }} or {^{cnvt: ... }}
|
|
|
2538 |
? (useCnvt = undefined, useViews = hasCnvt = true, tagRender + (tagCtxFn
|
|
|
2539 |
? ((tmplBindings[tmplBindingKey - 1] = tagCtxFn), tmplBindingKey) // Store the compiled tagCtxFn in tmpl.bnds, and pass the key to convertVal()
|
|
|
2540 |
: "{" + tagCtx + "}") + ")")
|
|
|
2541 |
: tagName === ">"
|
|
|
2542 |
? (hasEncoder = true, "h(" + params[0] + ")")
|
|
|
2543 |
: (getsVal = true, "((v=" + params[0] + ')!=null?v:' + (isLinkExpr ? 'null)' : '"")'))
|
|
|
2544 |
// Non strict equality so data-link="title{:expr}" with expr=null/undefined removes title attribute
|
|
|
2545 |
)
|
|
|
2546 |
: (hasTag = true, "\n{view:view,content:false,tmpl:" // Add this tagCtx to the compiled code for the tagCtxs to be passed to renderTag()
|
|
|
2547 |
+ (content ? nestedTmpls.length : "false") + "," // For block tags, pass in the key (nestedTmpls.length) to the nested content template
|
|
|
2548 |
+ tagCtx + "},"));
|
|
|
2549 |
|
|
|
2550 |
if (tagAndElses && !nextIsElse) {
|
|
|
2551 |
// This is a data-link expression or an inline tag without any elses, or the last {{else}} of an inline tag
|
|
|
2552 |
// We complete the code for returning the tagCtxs array
|
|
|
2553 |
code = "[" + code.slice(0, -1) + "]";
|
|
|
2554 |
tagRender = 't("' + tagAndElses + '",view,this,';
|
|
|
2555 |
if (isLinkExpr || pathBindings) {
|
|
|
2556 |
// This is a bound tag (data-link expression or inline bound tag {^{tag ...}}) so we store a compiled tagCtxs function in tmp.bnds
|
|
|
2557 |
code = new Function("data,view,j", " // " + tmplName + " " + tmplBindingKey + " " + tagAndElses + retStrOpen + code
|
|
|
2558 |
+ retStrClose);
|
|
|
2559 |
code._er = onError;
|
|
|
2560 |
code._tag = tagAndElses;
|
|
|
2561 |
if (pathBindings) {
|
|
|
2562 |
setPaths(tmplBindings[tmplBindingKey - 1] = code, pathBindings);
|
|
|
2563 |
}
|
|
|
2564 |
code._lr = lateRender;
|
|
|
2565 |
if (isLinkExpr) {
|
|
|
2566 |
return code; // For a data-link expression we return the compiled tagCtxs function
|
|
|
2567 |
}
|
|
|
2568 |
boundOnErrStart = tagRender + tmplBindingKey + ",undefined,";
|
|
|
2569 |
boundOnErrEnd = ")";
|
|
|
2570 |
}
|
|
|
2571 |
|
|
|
2572 |
// This is the last {{else}} for an inline tag.
|
|
|
2573 |
// For a bound tag, pass the tagCtxs fn lookup key to renderTag.
|
|
|
2574 |
// For an unbound tag, include the code directly for evaluating tagCtxs array
|
|
|
2575 |
code = oldCode + tagStart + tagRender + (pathBindings && tmplBindingKey || code) + ")";
|
|
|
2576 |
pathBindings = 0;
|
|
|
2577 |
tagAndElses = 0;
|
|
|
2578 |
}
|
|
|
2579 |
if (onError && !nextIsElse) {
|
|
|
2580 |
useViews = true;
|
|
|
2581 |
code += ';\n}catch(e){ret' + (isLinkExpr ? "urn " : "+=") + boundOnErrStart + 'j._err(e,view,' + onError + ')' + boundOnErrEnd + ';}' + (isLinkExpr ? "" : '\nret=ret');
|
|
|
2582 |
}
|
|
|
2583 |
}
|
|
|
2584 |
}
|
|
|
2585 |
}
|
|
|
2586 |
// Include only the var references that are needed in the code
|
|
|
2587 |
code = "// " + tmplName
|
|
|
2588 |
+ (tmplOptions.debug ? "\ndebugger;" : "")
|
|
|
2589 |
+ "\nvar v"
|
|
|
2590 |
+ (hasTag ? ",t=j._tag" : "") // has tag
|
|
|
2591 |
+ (hasCnvt ? ",c=j._cnvt" : "") // converter
|
|
|
2592 |
+ (hasEncoder ? ",h=j._html" : "") // html converter
|
|
|
2593 |
+ (isLinkExpr
|
|
|
2594 |
? (node[8] // late @... path?
|
|
|
2595 |
? ", ob"
|
|
|
2596 |
: ""
|
|
|
2597 |
) + ";\n"
|
|
|
2598 |
: ',ret=""')
|
|
|
2599 |
+ code
|
|
|
2600 |
+ (isLinkExpr ? "\n" : ";\nreturn ret;");
|
|
|
2601 |
|
|
|
2602 |
try {
|
|
|
2603 |
code = new Function("data,view,j", code);
|
|
|
2604 |
} catch (e) {
|
|
|
2605 |
syntaxError("Compiled template code:\n\n" + code + '\n: "' + (e.message||e) + '"');
|
|
|
2606 |
}
|
|
|
2607 |
if (tmpl) {
|
|
|
2608 |
tmpl.fn = code;
|
|
|
2609 |
tmpl.useViews = !!useViews;
|
|
|
2610 |
}
|
|
|
2611 |
return code;
|
|
|
2612 |
}
|
|
|
2613 |
|
|
|
2614 |
//==========
|
|
|
2615 |
// Utilities
|
|
|
2616 |
//==========
|
|
|
2617 |
|
|
|
2618 |
// Merge objects, in particular contexts which inherit from parent contexts
|
|
|
2619 |
function extendCtx(context, parentContext) {
|
|
|
2620 |
// Return copy of parentContext, unless context is defined and is different, in which case return a new merged context
|
|
|
2621 |
// If neither context nor parentContext are defined, return undefined
|
|
|
2622 |
return context && context !== parentContext
|
|
|
2623 |
? (parentContext
|
|
|
2624 |
? $extend($extend({}, parentContext), context)
|
|
|
2625 |
: context)
|
|
|
2626 |
: parentContext && $extend({}, parentContext);
|
|
|
2627 |
}
|
|
|
2628 |
|
|
|
2629 |
function getTargetProps(source, tagCtx) {
|
|
|
2630 |
// this pointer is theMap - which has tagCtx.props too
|
|
|
2631 |
// arguments: tagCtx.args.
|
|
|
2632 |
var key, prop,
|
|
|
2633 |
map = tagCtx.map,
|
|
|
2634 |
propsArr = map && map.propsArr;
|
|
|
2635 |
|
|
|
2636 |
if (!propsArr) { // map.propsArr is the full array of {key:..., prop:...} objects
|
|
|
2637 |
propsArr = [];
|
|
|
2638 |
if (typeof source === OBJECT || $isFunction(source)) {
|
|
|
2639 |
for (key in source) {
|
|
|
2640 |
prop = source[key];
|
|
|
2641 |
if (key !== $expando && source.hasOwnProperty(key) && (!tagCtx.props.noFunctions || !$.isFunction(prop))) {
|
|
|
2642 |
propsArr.push({key: key, prop: prop});
|
|
|
2643 |
}
|
|
|
2644 |
}
|
|
|
2645 |
}
|
|
|
2646 |
if (map) {
|
|
|
2647 |
map.propsArr = map.options && propsArr; // If bound {^{props}} and not isRenderCall, store propsArr on map (map.options is defined only for bound, && !isRenderCall)
|
|
|
2648 |
}
|
|
|
2649 |
}
|
|
|
2650 |
return getTargetSorted(propsArr, tagCtx); // Obtains map.tgt, by filtering, sorting and splicing the full propsArr
|
|
|
2651 |
}
|
|
|
2652 |
|
|
|
2653 |
function getTargetSorted(value, tagCtx) {
|
|
|
2654 |
// getTgt
|
|
|
2655 |
var mapped, start, end,
|
|
|
2656 |
tag = tagCtx.tag,
|
|
|
2657 |
props = tagCtx.props,
|
|
|
2658 |
propParams = tagCtx.params.props,
|
|
|
2659 |
filter = props.filter,
|
|
|
2660 |
sort = props.sort,
|
|
|
2661 |
directSort = sort === true,
|
|
|
2662 |
step = parseInt(props.step),
|
|
|
2663 |
reverse = props.reverse ? -1 : 1;
|
|
|
2664 |
|
|
|
2665 |
if (!$isArray(value)) {
|
|
|
2666 |
return value;
|
|
|
2667 |
}
|
|
|
2668 |
if (directSort || sort && "" + sort === sort) {
|
|
|
2669 |
// Temporary mapped array holds objects with index and sort-value
|
|
|
2670 |
mapped = value.map(function(item, i) {
|
|
|
2671 |
item = directSort ? item : getPathObject(item, sort);
|
|
|
2672 |
return {i: i, v: "" + item === item ? item.toLowerCase() : item};
|
|
|
2673 |
});
|
|
|
2674 |
// Sort mapped array
|
|
|
2675 |
mapped.sort(function(a, b) {
|
|
|
2676 |
return a.v > b.v ? reverse : a.v < b.v ? -reverse : 0;
|
|
|
2677 |
});
|
|
|
2678 |
// Map to new array with resulting order
|
|
|
2679 |
value = mapped.map(function(item){
|
|
|
2680 |
return value[item.i];
|
|
|
2681 |
});
|
|
|
2682 |
} else if ((sort || reverse < 0) && !tag.dataMap) {
|
|
|
2683 |
value = value.slice(); // Clone array first if not already a new array
|
|
|
2684 |
}
|
|
|
2685 |
if ($isFunction(sort)) {
|
|
|
2686 |
value = value.sort(function() { // Wrap the sort function to provide tagCtx as 'this' pointer
|
|
|
2687 |
return sort.apply(tagCtx, arguments);
|
|
|
2688 |
});
|
|
|
2689 |
}
|
|
|
2690 |
if (reverse < 0 && (!sort || $isFunction(sort))) { // Reverse result if not already reversed in sort
|
|
|
2691 |
value = value.reverse();
|
|
|
2692 |
}
|
|
|
2693 |
|
|
|
2694 |
if (value.filter && filter) { // IE8 does not support filter
|
|
|
2695 |
value = value.filter(filter, tagCtx);
|
|
|
2696 |
if (tagCtx.tag.onFilter) {
|
|
|
2697 |
tagCtx.tag.onFilter(tagCtx);
|
|
|
2698 |
}
|
|
|
2699 |
}
|
|
|
2700 |
|
|
|
2701 |
if (propParams.sorted) {
|
|
|
2702 |
mapped = (sort || reverse < 0) ? value : value.slice();
|
|
|
2703 |
if (tag.sorted) {
|
|
|
2704 |
$.observable(tag.sorted).refresh(mapped); // Note that this might cause the start and end props to be modified - e.g. by pager tag control
|
|
|
2705 |
} else {
|
|
|
2706 |
tagCtx.map.sorted = mapped;
|
|
|
2707 |
}
|
|
|
2708 |
}
|
|
|
2709 |
|
|
|
2710 |
start = props.start; // Get current value - after possible changes triggered by tag.sorted refresh() above
|
|
|
2711 |
end = props.end;
|
|
|
2712 |
if (propParams.start && start === undefined || propParams.end && end === undefined) {
|
|
|
2713 |
start = end = 0;
|
|
|
2714 |
}
|
|
|
2715 |
if (!isNaN(start) || !isNaN(end)) { // start or end specified, but not the auto-create Number array scenario of {{for start=xxx end=yyy}}
|
|
|
2716 |
start = +start || 0;
|
|
|
2717 |
end = end === undefined || end > value.length ? value.length : +end;
|
|
|
2718 |
value = value.slice(start, end);
|
|
|
2719 |
}
|
|
|
2720 |
if (step > 1) {
|
|
|
2721 |
start = 0;
|
|
|
2722 |
end = value.length;
|
|
|
2723 |
mapped = [];
|
|
|
2724 |
for (; start<end; start+=step) {
|
|
|
2725 |
mapped.push(value[start]);
|
|
|
2726 |
}
|
|
|
2727 |
value = mapped;
|
|
|
2728 |
}
|
|
|
2729 |
if (propParams.paged && tag.paged) {
|
|
|
2730 |
$observable(tag.paged).refresh(value);
|
|
|
2731 |
}
|
|
|
2732 |
|
|
|
2733 |
return value;
|
|
|
2734 |
}
|
|
|
2735 |
|
|
|
2736 |
/** Render the template as a string, using the specified data and helpers/context
|
|
|
2737 |
* $("#tmpl").render()
|
|
|
2738 |
*
|
|
|
2739 |
* @param {any} data
|
|
|
2740 |
* @param {hash} [helpersOrContext]
|
|
|
2741 |
* @param {boolean} [noIteration]
|
|
|
2742 |
* @returns {string} rendered template
|
|
|
2743 |
*/
|
|
|
2744 |
function $fnRender(data, context, noIteration) {
|
|
|
2745 |
var tmplElem = this.jquery && (this[0] || error('Unknown template')), // Targeted element not found for jQuery template selector such as "#myTmpl"
|
|
|
2746 |
tmpl = tmplElem.getAttribute(tmplAttr);
|
|
|
2747 |
|
|
|
2748 |
return renderContent.call(tmpl && $.data(tmplElem)[jsvTmpl] || $templates(tmplElem),
|
|
|
2749 |
data, context, noIteration);
|
|
|
2750 |
}
|
|
|
2751 |
|
|
|
2752 |
//========================== Register converters ==========================
|
|
|
2753 |
|
|
|
2754 |
function getCharEntity(ch) {
|
|
|
2755 |
// Get character entity for HTML, Attribute and optional data encoding
|
|
|
2756 |
return charEntities[ch] || (charEntities[ch] = "&#" + ch.charCodeAt(0) + ";");
|
|
|
2757 |
}
|
|
|
2758 |
|
|
|
2759 |
function getCharFromEntity(match, token) {
|
|
|
2760 |
// Get character from HTML entity, for optional data unencoding
|
|
|
2761 |
return charsFromEntities[token] || "";
|
|
|
2762 |
}
|
|
|
2763 |
|
|
|
2764 |
function htmlEncode(text) {
|
|
|
2765 |
// HTML encode: Replace < > & ' " ` etc. by corresponding entities.
|
|
|
2766 |
return text != undefined ? rIsHtml.test(text) && ("" + text).replace(rHtmlEncode, getCharEntity) || text : "";
|
|
|
2767 |
}
|
|
|
2768 |
|
|
|
2769 |
function dataEncode(text) {
|
|
|
2770 |
// Encode just < > and & - intended for 'safe data' along with {{:}} rather than {{>}}
|
|
|
2771 |
return "" + text === text ? text.replace(rDataEncode, getCharEntity) : text;
|
|
|
2772 |
}
|
|
|
2773 |
|
|
|
2774 |
function dataUnencode(text) {
|
|
|
2775 |
// Unencode just < > and & - intended for 'safe data' along with {{:}} rather than {{>}}
|
|
|
2776 |
return "" + text === text ? text.replace(rDataUnencode, getCharFromEntity) : text;
|
|
|
2777 |
}
|
|
|
2778 |
|
|
|
2779 |
//========================== Initialize ==========================
|
|
|
2780 |
|
|
|
2781 |
$sub = $views.sub;
|
|
|
2782 |
$viewsSettings = $views.settings;
|
|
|
2783 |
|
|
|
2784 |
if (!(jsr || $ && $.render)) {
|
|
|
2785 |
// JsRender/JsViews not already loaded (or loaded without jQuery, and we are now moving from jsrender namespace to jQuery namepace)
|
|
|
2786 |
for (jsvStoreName in jsvStores) {
|
|
|
2787 |
registerStore(jsvStoreName, jsvStores[jsvStoreName]);
|
|
|
2788 |
}
|
|
|
2789 |
|
|
|
2790 |
$converters = $views.converters;
|
|
|
2791 |
$helpers = $views.helpers;
|
|
|
2792 |
$tags = $views.tags;
|
|
|
2793 |
|
|
|
2794 |
$sub._tg.prototype = {
|
|
|
2795 |
baseApply: baseApply,
|
|
|
2796 |
cvtArgs: convertArgs,
|
|
|
2797 |
bndArgs: convertBoundArgs,
|
|
|
2798 |
ctxPrm: contextParameter
|
|
|
2799 |
};
|
|
|
2800 |
|
|
|
2801 |
topView = $sub.topView = new View();
|
|
|
2802 |
|
|
|
2803 |
//BROWSER-SPECIFIC CODE
|
|
|
2804 |
if ($) {
|
|
|
2805 |
|
|
|
2806 |
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
2807 |
// jQuery (= $) is loaded
|
|
|
2808 |
|
|
|
2809 |
$.fn.render = $fnRender;
|
|
|
2810 |
$expando = $.expando;
|
|
|
2811 |
if ($.observable) {
|
|
|
2812 |
if (versionNumber !== (versionNumber = $.views.jsviews)) {
|
|
|
2813 |
// Different version of jsRender was loaded
|
|
|
2814 |
throw "jquery.observable.js requires jsrender.js " + versionNumber;
|
|
|
2815 |
}
|
|
|
2816 |
$extend($sub, $.views.sub); // jquery.observable.js was loaded before jsrender.js
|
|
|
2817 |
$views.map = $.views.map;
|
|
|
2818 |
}
|
|
|
2819 |
|
|
|
2820 |
} else {
|
|
|
2821 |
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
2822 |
// jQuery is not loaded.
|
|
|
2823 |
|
|
|
2824 |
$ = {};
|
|
|
2825 |
|
|
|
2826 |
if (setGlobals) {
|
|
|
2827 |
global.jsrender = $; // We are loading jsrender.js from a script element, not AMD or CommonJS, so set global
|
|
|
2828 |
}
|
|
|
2829 |
|
|
|
2830 |
// Error warning if jsrender.js is used as template engine on Node.js (e.g. Express or Hapi...)
|
|
|
2831 |
// Use jsrender-node.js instead...
|
|
|
2832 |
$.renderFile = $.__express = $.compile = function() { throw "Node.js: use npm jsrender, or jsrender-node.js"; };
|
|
|
2833 |
|
|
|
2834 |
//END BROWSER-SPECIFIC CODE
|
|
|
2835 |
$.isFunction = function(ob) {
|
|
|
2836 |
return typeof ob === "function";
|
|
|
2837 |
};
|
|
|
2838 |
|
|
|
2839 |
$.isArray = Array.isArray || function(obj) {
|
|
|
2840 |
return ({}.toString).call(obj) === "[object Array]";
|
|
|
2841 |
};
|
|
|
2842 |
|
|
|
2843 |
$sub._jq = function(jq) { // private method to move from JsRender APIs from jsrender namespace to jQuery namespace
|
|
|
2844 |
if (jq !== $) {
|
|
|
2845 |
$extend(jq, $); // map over from jsrender namespace to jQuery namespace
|
|
|
2846 |
$ = jq;
|
|
|
2847 |
$.fn.render = $fnRender;
|
|
|
2848 |
delete $.jsrender;
|
|
|
2849 |
$expando = $.expando;
|
|
|
2850 |
}
|
|
|
2851 |
};
|
|
|
2852 |
|
|
|
2853 |
$.jsrender = versionNumber;
|
|
|
2854 |
}
|
|
|
2855 |
$subSettings = $sub.settings;
|
|
|
2856 |
$subSettings.allowCode = false;
|
|
|
2857 |
$isFunction = $.isFunction;
|
|
|
2858 |
$.render = $render;
|
|
|
2859 |
$.views = $views;
|
|
|
2860 |
$.templates = $templates = $views.templates;
|
|
|
2861 |
|
|
|
2862 |
for (setting in $subSettings) {
|
|
|
2863 |
addSetting(setting);
|
|
|
2864 |
}
|
|
|
2865 |
|
|
|
2866 |
/**
|
|
|
2867 |
* $.views.settings.debugMode(true)
|
|
|
2868 |
* @param {boolean} debugMode
|
|
|
2869 |
* @returns {Settings}
|
|
|
2870 |
*
|
|
|
2871 |
* debugMode = $.views.settings.debugMode()
|
|
|
2872 |
* @returns {boolean}
|
|
|
2873 |
*/
|
|
|
2874 |
($viewsSettings.debugMode = function(debugMode) {
|
|
|
2875 |
return debugMode === undefined
|
|
|
2876 |
? $subSettings.debugMode
|
|
|
2877 |
: (
|
|
|
2878 |
$subSettings._clFns && $subSettings._clFns(), // Clear linkExprStore (cached compiled expressions), since debugMode setting affects compilation for expressions
|
|
|
2879 |
$subSettings.debugMode = debugMode,
|
|
|
2880 |
$subSettings.onError = debugMode + "" === debugMode
|
|
|
2881 |
? function() { return debugMode; }
|
|
|
2882 |
: $isFunction(debugMode)
|
|
|
2883 |
? debugMode
|
|
|
2884 |
: undefined,
|
|
|
2885 |
$viewsSettings);
|
|
|
2886 |
})(false); // jshint ignore:line
|
|
|
2887 |
|
|
|
2888 |
$subSettingsAdvanced = $subSettings.advanced = {
|
|
|
2889 |
cache: true, // By default use cached values of computed values (Otherwise, set advanced cache setting to false)
|
|
|
2890 |
useViews: false,
|
|
|
2891 |
_jsv: false // For global access to JsViews store
|
|
|
2892 |
};
|
|
|
2893 |
|
|
|
2894 |
//========================== Register tags ==========================
|
|
|
2895 |
|
|
|
2896 |
$tags({
|
|
|
2897 |
"if": {
|
|
|
2898 |
render: function(val) {
|
|
|
2899 |
// This function is called once for {{if}} and once for each {{else}}.
|
|
|
2900 |
// We will use the tag.rendering object for carrying rendering state across the calls.
|
|
|
2901 |
// If not done (a previous block has not been rendered), look at expression for this block and render the block if expression is truthy
|
|
|
2902 |
// Otherwise return ""
|
|
|
2903 |
var self = this,
|
|
|
2904 |
tagCtx = self.tagCtx,
|
|
|
2905 |
ret = (self.rendering.done || !val && (tagCtx.args.length || !tagCtx.index))
|
|
|
2906 |
? ""
|
|
|
2907 |
: (self.rendering.done = true,
|
|
|
2908 |
self.selected = tagCtx.index,
|
|
|
2909 |
undefined); // Test is satisfied, so render content on current context
|
|
|
2910 |
return ret;
|
|
|
2911 |
},
|
|
|
2912 |
contentCtx: true, // Inherit parent view data context
|
|
|
2913 |
flow: true
|
|
|
2914 |
},
|
|
|
2915 |
"for": {
|
|
|
2916 |
sortDataMap: dataMap(getTargetSorted),
|
|
|
2917 |
init: function(val, cloned) {
|
|
|
2918 |
this.setDataMap(this.tagCtxs);
|
|
|
2919 |
},
|
|
|
2920 |
render: function(val) {
|
|
|
2921 |
// This function is called once for {{for}} and once for each {{else}}.
|
|
|
2922 |
// We will use the tag.rendering object for carrying rendering state across the calls.
|
|
|
2923 |
var value, filter, srtField, isArray, i, sorted, end, step,
|
|
|
2924 |
self = this,
|
|
|
2925 |
tagCtx = self.tagCtx,
|
|
|
2926 |
range = tagCtx.argDefault === false,
|
|
|
2927 |
props = tagCtx.props,
|
|
|
2928 |
iterate = range || tagCtx.args.length, // Not final else and not auto-create range
|
|
|
2929 |
result = "",
|
|
|
2930 |
done = 0;
|
|
|
2931 |
|
|
|
2932 |
if (!self.rendering.done) {
|
|
|
2933 |
value = iterate ? val : tagCtx.view.data; // For the final else, defaults to current data without iteration.
|
|
|
2934 |
|
|
|
2935 |
if (range) {
|
|
|
2936 |
range = props.reverse ? "unshift" : "push";
|
|
|
2937 |
end = +props.end;
|
|
|
2938 |
step = +props.step || 1;
|
|
|
2939 |
value = []; // auto-create integer array scenario of {{for start=xxx end=yyy}}
|
|
|
2940 |
for (i = +props.start || 0; (end - i) * step > 0; i += step) {
|
|
|
2941 |
value[range](i);
|
|
|
2942 |
}
|
|
|
2943 |
}
|
|
|
2944 |
if (value !== undefined) {
|
|
|
2945 |
isArray = $isArray(value);
|
|
|
2946 |
result += tagCtx.render(value, !iterate || props.noIteration);
|
|
|
2947 |
// Iterates if data is an array, except on final else - or if noIteration property
|
|
|
2948 |
// set to true. (Use {{include}} to compose templates without array iteration)
|
|
|
2949 |
done += isArray ? value.length : 1;
|
|
|
2950 |
}
|
|
|
2951 |
if (self.rendering.done = done) {
|
|
|
2952 |
self.selected = tagCtx.index;
|
|
|
2953 |
}
|
|
|
2954 |
// If nothing was rendered we will look at the next {{else}}. Otherwise, we are done.
|
|
|
2955 |
}
|
|
|
2956 |
return result;
|
|
|
2957 |
},
|
|
|
2958 |
setDataMap: function(tagCtxs) {
|
|
|
2959 |
var tagCtx, props, paramsProps,
|
|
|
2960 |
self = this,
|
|
|
2961 |
l = tagCtxs.length;
|
|
|
2962 |
while (l--) {
|
|
|
2963 |
tagCtx = tagCtxs[l];
|
|
|
2964 |
props = tagCtx.props;
|
|
|
2965 |
paramsProps = tagCtx.params.props;
|
|
|
2966 |
tagCtx.argDefault = props.end === undefined || tagCtx.args.length > 0; // Default to #data except for auto-create range scenario {{for start=xxx end=yyy step=zzz}}
|
|
|
2967 |
props.dataMap = (tagCtx.argDefault !== false && $isArray(tagCtx.args[0]) &&
|
|
|
2968 |
(paramsProps.sort || paramsProps.start || paramsProps.end || paramsProps.step || paramsProps.filter || paramsProps.reverse
|
|
|
2969 |
|| props.sort || props.start || props.end || props.step || props.filter || props.reverse))
|
|
|
2970 |
&& self.sortDataMap;
|
|
|
2971 |
}
|
|
|
2972 |
},
|
|
|
2973 |
flow: true
|
|
|
2974 |
},
|
|
|
2975 |
props: {
|
|
|
2976 |
baseTag: "for",
|
|
|
2977 |
dataMap: dataMap(getTargetProps),
|
|
|
2978 |
init: noop, // Don't execute the base init() of the "for" tag
|
|
|
2979 |
flow: true
|
|
|
2980 |
},
|
|
|
2981 |
include: {
|
|
|
2982 |
flow: true
|
|
|
2983 |
},
|
|
|
2984 |
"*": {
|
|
|
2985 |
// {{* code... }} - Ignored if template.allowCode and $.views.settings.allowCode are false. Otherwise include code in compiled template
|
|
|
2986 |
render: retVal,
|
|
|
2987 |
flow: true
|
|
|
2988 |
},
|
|
|
2989 |
":*": {
|
|
|
2990 |
// {{:* returnedExpression }} - Ignored if template.allowCode and $.views.settings.allowCode are false. Otherwise include code in compiled template
|
|
|
2991 |
render: retVal,
|
|
|
2992 |
flow: true
|
|
|
2993 |
},
|
|
|
2994 |
dbg: $helpers.dbg = $converters.dbg = dbgBreak // Register {{dbg/}}, {{dbg:...}} and ~dbg() to throw and catch, as breakpoints for debugging.
|
|
|
2995 |
});
|
|
|
2996 |
|
|
|
2997 |
$converters({
|
|
|
2998 |
html: htmlEncode,
|
|
|
2999 |
attr: htmlEncode, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
|
|
|
3000 |
encode: dataEncode,
|
|
|
3001 |
unencode: dataUnencode, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
|
|
|
3002 |
url: function(text) {
|
|
|
3003 |
// URL encoding helper.
|
|
|
3004 |
return text != undefined ? encodeURI("" + text) : text === null ? text : ""; // null returns null, e.g. to remove attribute. undefined returns ""
|
|
|
3005 |
}
|
|
|
3006 |
});
|
|
|
3007 |
}
|
|
|
3008 |
//========================== Define default delimiters ==========================
|
|
|
3009 |
$subSettings = $sub.settings;
|
|
|
3010 |
$isArray = ($||jsr).isArray;
|
|
|
3011 |
$viewsSettings.delimiters("{{", "}}", "^");
|
|
|
3012 |
|
|
|
3013 |
if (jsrToJq) { // Moving from jsrender namespace to jQuery namepace - copy over the stored items (templates, converters, helpers...)
|
|
|
3014 |
jsr.views.sub._jq($);
|
|
|
3015 |
}
|
|
|
3016 |
return $ || jsr;
|
|
|
3017 |
}, window));
|