jsviews
Version:
Next-generation MVVM and MVP framework - built on top of JsRender templates. Bringing templates to life...
1,511 lines (1,378 loc) • 105 kB
JavaScript
/*! JsRender v1.0.16: http://jsviews.com/#jsrender */
/*! **VERSION FOR WEB** (For NODE.JS see http://jsviews.com/download/jsrender-node.js) */
/*
* Best-of-breed templating in browser or on Node.js.
* Does not require jQuery, or HTML DOM
* Integrates with JsViews (http://jsviews.com/#jsviews)
*
* Copyright 2025, Boris Moore
* Released under the MIT License.
*/
//jshint -W018, -W041, -W120
(function(factory, global) {
// global var is the this object, which is window when running in the usual browser environment
var $ = global.jQuery;
if (typeof exports === "object") { // CommonJS e.g. Browserify
module.exports = $
? factory(global, $)
: function($) { // If no global jQuery, take optional jQuery passed as parameter: require('jsrender')(jQuery)
if ($ && !$.fn) {
throw "Provide jQuery or null";
}
return factory(global, $);
};
} else if (typeof define === "function" && define.amd) { // AMD script loader, e.g. RequireJS
define(function() {
return factory(global);
});
} else { // Browser using plain <script> tag
factory(global, false);
}
} (
// factory (for jsrender.js)
function(global, $) {
"use strict";
//========================== Top-level vars ==========================
// global var is the this object, which is window when running in the usual browser environment
var setGlobals = $ === false; // Only set globals if script block in browser (not AMD and not CommonJS)
$ = $ && $.fn ? $ : global.jQuery; // $ is jQuery passed in by CommonJS loader (Browserify), or global jQuery.
var versionNumber = "v1.0.16",
jsvStoreName, rTag, rTmplString, topView, $views, $expando,
_ocp = "_ocp", // Observable contextual parameter
$isFunction = function(ob) { return typeof ob === "function"; },
$isArray = Array.isArray,
$templates, $converters, $helpers, $tags, $sub, $subSettings, $subSettingsAdvanced, $viewsSettings,
delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar, setting, baseOnError,
isRenderCall,
rNewLine = /[ \t]*(\r\n|\n|\r)/g,
rUnescapeQuotes = /\\(['"\\])/g, // Unescape quotes and trim
rEscapeQuotes = /['"\\]/g, // Escape quotes and \ character
rBuildHash = /(?:\x08|^)(onerror:)?(?:(~?)(([\w$.]+):)?([^\x08]+))\x08(,)?([^\x08]+)/gi,
rTestElseIf = /^if\s/,
rFirstElem = /<(\w+)[>\s]/,
rAttrEncode = /[\x00`><"'&=]/g, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
rIsHtml = /[\x00`><\"'&=]/,
rHasHandlers = /^on[A-Z]|^convert(Back)?$/,
rWrappedInViewMarker = /^\#\d+_`[\s\S]*\/\d+_`$/,
rHtmlEncode = rAttrEncode,
rDataEncode = /[&<>]/g,
rDataUnencode = /&(amp|gt|lt);/g,
rBracketQuote = /\[['"]?|['"]?\]/g,
viewId = 0,
charEntities = {
"&": "&",
"<": "<",
">": ">",
"\x00": "�",
"'": "'",
'"': """,
"`": "`",
"=": "="
},
charsFromEntities = {
amp: "&",
gt: ">",
lt: "<"
},
HTML = "html",
STRING = "string",
OBJECT = "object",
tmplAttr = "data-jsv-tmpl",
jsvTmpl = "jsvTmpl",
indexStr = "For #index in nested block use #getIndex().",
cpFnStore = {}, // Compiled furnctions for computed values in template expressions (properties, methods, helpers)
$render = {},
jsr = global.jsrender,
jsrToJq = jsr && $ && !$.render, // JsRender already loaded, without jQuery. but we will re-load it now to attach to jQuery
jsvStores = {
template: {
compile: compileTmpl
},
tag: {
compile: compileTag
},
viewModel: {
compile: compileViewModel
},
helper: {},
converter: {}
};
// views object ($.views if jQuery is loaded, jsrender.views if no jQuery, e.g. in Node.js)
$views = {
jsviews: versionNumber,
sub: {
// subscription, e.g. JsViews integration
rPath: /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
// not object helper view viewProperty pathTokens leafToken
rPrm: /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(~?[\w$.^]+)?\s*((\+\+|--)|\+|-|~(?![\w$])|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?(@)?[#~]?[\w$.^]+)([([])?)|(,\s*)|(?:(\()\s*)?\\?(?:(')|("))|(?:\s*(([)\]])(?=[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,
// lftPrn0 lftPrn bound path operator err eq path2 late prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space
View: View,
Err: JsViewsError,
tmplFn: tmplFn,
parse: parseParams,
extend: $extend,
extendCtx: extendCtx,
syntaxErr: syntaxError,
onStore: {
template: function(name, item) {
if (item === null) {
delete $render[name];
} else if (name) {
$render[name] = item;
}
}
},
addSetting: addSetting,
settings: {
allowCode: false
},
advSet: noop, // Update advanced settings
_thp: tagHandlersFromProps,
_gm: getMethod,
_tg: function() {}, // Constructor for tagDef
_cnvt: convertVal,
_tag: renderTag,
_er: error,
_err: onRenderError,
_cp: retVal, // Get observable contextual parameters (or properties) ~foo=expr. In JsRender, simply returns val.
_sq: function(token) {
if (token === "constructor") {
syntaxError("");
}
return token;
}
},
settings: {
delimiters: $viewsDelimiters,
advanced: function(value) {
return value
? (
$extend($subSettingsAdvanced, value),
$sub.advSet(),
$viewsSettings
)
: $subSettingsAdvanced;
}
},
map: dataMap // If jsObservable loaded first, use that definition of dataMap
};
function getDerivedMethod(baseMethod, method) {
return function() {
var ret,
tag = this,
prevBase = tag.base;
tag.base = baseMethod; // Within method call, calling this.base will call the base method
ret = method.apply(tag, arguments); // Call the method
tag.base = prevBase; // Replace this.base to be the base method of the previous call, for chained calls
return ret;
};
}
function getMethod(baseMethod, method) {
// For derived methods (or handlers declared declaratively as in {{:foo onChange=~fooChanged}} replace by a derived method, to allow using this.base(...)
// or this.baseApply(arguments) to call the base implementation. (Equivalent to this._super(...) and this._superApply(arguments) in jQuery UI)
if ($isFunction(method)) {
method = getDerivedMethod(
!baseMethod
? noop // no base method implementation, so use noop as base method
: baseMethod._d
? baseMethod // baseMethod is a derived method, so use it
: getDerivedMethod(noop, baseMethod), // baseMethod is not derived so make its base method be the noop method
method
);
method._d = (baseMethod && baseMethod._d || 0) + 1; // Add flag for derived method (incremented for derived of derived...)
}
return method;
}
function tagHandlersFromProps(tag, tagCtx) {
var prop,
props = tagCtx.props;
for (prop in props) {
if (rHasHandlers.test(prop) && !(tag[prop] && tag[prop].fix)) { // Don't override handlers with fix expando (used in datepicker and spinner)
tag[prop] = prop !== "convert" ? getMethod(tag.constructor.prototype[prop], props[prop]) : props[prop];
// Copy over the onFoo props, convert and convertBack from tagCtx.props to tag (overrides values in tagDef).
// Note: unsupported scenario: if handlers are dynamically added ^onFoo=expression this will work, but dynamically removing will not work.
}
}
}
function retVal(val) {
return val;
}
function noop() {
return "";
}
function dbgBreak(val) {
// Usage examples: {{dbg:...}}, {{:~dbg(...)}}, {{dbg .../}}, {^{for ... onAfterLink=~dbg}} etc.
try {
console.log("JsRender dbg breakpoint: " + val);
throw "dbg breakpoint"; // To break here, stop on caught exceptions.
}
catch (e) {}
return this.base ? this.baseApply(arguments) : val;
}
function JsViewsError(message) {
// Error exception type for JsViews/JsRender
// Override of $.views.sub.Error is possible
this.name = ($.link ? "JsViews" : "JsRender") + " Error";
this.message = message || this.name;
}
function $extend(target, source) {
if (target) {
for (var name in source) {
if (name !== "__proto__") { // Prevent prototype pollution attacks
target[name] = source[name];
}
}
return target;
}
}
(JsViewsError.prototype = new Error()).constructor = JsViewsError;
//========================== Top-level functions ==========================
//===================
// views.delimiters
//===================
/**
* Set the tag opening and closing delimiters and 'link' character. Default is "{{", "}}" and "^"
* openChars, closeChars: opening and closing strings, each with two characters
* $.views.settings.delimiters(...)
*
* @param {string} openChars
* @param {string} [closeChars]
* @param {string} [link]
* @returns {Settings}
*
* Get delimiters
* delimsArray = $.views.settings.delimiters()
*
* @returns {string[]}
*/
function $viewsDelimiters(openChars, closeChars, link) {
if (!openChars) {
return $subSettings.delimiters;
}
if (Array.isArray(openChars)) {
return $viewsDelimiters.apply($views, openChars);
}
linkChar = link ? link[0] : linkChar;
if (!/^(\W|_){5}$/.test(openChars + closeChars + linkChar)) {
error("Invalid delimiters"); // Must be non-word characters, and openChars and closeChars must each be length 2
}
delimOpenChar0 = openChars[0];
delimOpenChar1 = openChars[1];
delimCloseChar0 = closeChars[0];
delimCloseChar1 = closeChars[1];
$subSettings.delimiters = [delimOpenChar0 + delimOpenChar1, delimCloseChar0 + delimCloseChar1, linkChar];
// Escape the characters - since they could be regex special characters
openChars = "\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1; // Default is "{^{"
closeChars = "\\" + delimCloseChar0 + "\\" + delimCloseChar1; // Default is "}}"
// Build regex with new delimiters
// [tag (followed by / space or }) or cvtr+colon or html or code] followed by space+params then convertBack?
rTag = "(?:(\\w+(?=[\\/\\s\\" + delimCloseChar0 + "]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"
+ delimCloseChar0 + "]|\\" + delimCloseChar0 + "(?!\\" + delimCloseChar1 + "))*?)";
// Make rTag available to JsViews (or other components) for parsing binding expressions
$sub.rTag = "(?:" + rTag + ")";
// { ^? { tag+params slash? or closingTag or comment
rTag = new RegExp("(?:" + openChars + rTag + "(\\/)?|\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1 + "(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))" + closeChars, "g");
// Default: bind tagName cvt cln html code params slash bind2 closeBlk comment
// /(?:{(\^)?{(?:(\w+(?=[\/\s}]))|(\w+)?(:)|(>)|(\*))\s*((?:[^}]|}(?!}))*?)(\/)?|{(\^)?{(?:(?:\/(\w+))\s*|!--[\s\S]*?--))}}
$sub.rTmpl = new RegExp("^\\s|\\s$|<.*>|([^\\\\]|^)[{}]|" + openChars + ".*" + closeChars);
// $sub.rTmpl looks for initial or final white space, html tags or { or } char not preceded by \\, or JsRender tags {{xxx}}.
// Each of these strings are considered NOT to be jQuery selectors
return $viewsSettings;
}
//=========
// View.get
//=========
function getView(inner, type) { //view.get(inner, type)
if (!type && inner !== true) {
// view.get(type)
type = inner;
inner = undefined;
}
var views, i, l, found,
view = this,
root = type === "root";
// view.get("root") returns view.root, view.get() returns view.parent, view.get(true) returns view.views[0].
if (inner) {
// Go through views - this one, and all nested ones, depth-first - and return first one with given type.
// If type is undefined, i.e. view.get(true), return first child view.
found = type && view.type === type && view;
if (!found) {
views = view.views;
if (view._.useKey) {
for (i in views) {
if (found = type ? views[i].get(inner, type) : views[i]) {
break;
}
}
} else {
for (i = 0, l = views.length; !found && i < l; i++) {
found = type ? views[i].get(inner, type) : views[i];
}
}
}
} else if (root) {
// Find root view. (view whose parent is top view)
found = view.root;
} else if (type) {
while (view && !found) {
// Go through views - this one, and all parent ones - and return first one with given type.
found = view.type === type ? view : undefined;
view = view.parent;
}
} else {
found = view.parent;
}
return found || undefined;
}
function getNestedIndex() {
var view = this.get("item");
return view ? view.index : undefined;
}
getNestedIndex.depends = function() {
return [this.get("item"), "index"];
};
function getIndex() {
return this.index;
}
getIndex.depends = "index";
//==================
// View.ctxPrm, etc.
//==================
/* Internal private: view._getOb() */
function getPathObject(ob, path, ltOb, fn) {
// Iterate through path to late paths: @a.b.c paths
// Return "" (or noop if leaf is a function @a.b.c(...) ) if intermediate object not yet available
var prevOb, tokens, l,
i = 0;
if (ltOb === 1) {
fn = 1;
ltOb = undefined;
}
// Paths like ^a^b^c or ~^a^b^c will not throw if an object in path is undefined.
if (path) {
tokens = path.split(".");
l = tokens.length;
for (; ob && i < l; i++) {
prevOb = ob;
ob = tokens[i] ? ob[tokens[i]] : ob;
if ($isFunction(ob)) {
ob = ob.call(prevOb);
}
}
}
if (ltOb) {
ltOb.lt = ltOb.lt || i<l; // If i < l there was an object in the path not yet available
}
return ob === undefined
? fn ? noop : ""
: fn ? function() {
return ob.apply(prevOb, arguments);
} : ob;
}
function contextParameter(key, value, get) {
// Helper method called as view.ctxPrm(key) for helpers or template parameters ~foo - from compiled template or from context callback
var wrapped, deps, res, obsCtxPrm, tagElse, callView, newRes,
storeView = this,
isUpdate = !isRenderCall && arguments.length > 1,
store = storeView.ctx;
if (key) {
if (!storeView._) { // tagCtx.ctxPrm() call
tagElse = storeView.index;
storeView = storeView.tag;
}
callView = storeView;
if (store && store.hasOwnProperty(key) || (store = $helpers).hasOwnProperty(key)) {
res = store[key];
if (key === "tag" || key === "tagCtx" || key === "root" || key === "parentTags") {
return res;
}
} else {
store = undefined;
}
if (!isRenderCall && storeView.tagCtx || storeView.linked) { // Data-linked view, or tag instance
if (!res || !res._cxp) {
// Not a contextual parameter
// Set storeView to tag (if this is a tag.ctxPrm() call) or to root view ("data" view of linked template)
storeView = storeView.tagCtx || $isFunction(res)
? storeView // Is a tag, not a view, or is a computed contextual parameter, so scope to the callView, not the 'scope view'
: (storeView = storeView.scope || storeView,
!storeView.isTop && storeView.ctx.tag // If this view is in a tag, set storeView to the tag
|| storeView);
if (res !== undefined && storeView.tagCtx) {
// If storeView is a tag, but the contextual parameter has been set at at higher level (e.g. helpers)...
storeView = storeView.tagCtx.view.scope; // then move storeView to the outer level (scope of tag container view)
}
store = storeView._ocps;
res = store && store.hasOwnProperty(key) && store[key] || res;
if (!(res && res._cxp) && (get || isUpdate)) {
// Create observable contextual parameter
(store || (storeView._ocps = storeView._ocps || {}))[key]
= res
= [{
_ocp: res, // The observable contextual parameter value
_vw: callView,
_key: key
}];
res._cxp = {
path: _ocp,
ind: 0,
updateValue: function(val, path) {
$.observable(res[0]).setProperty(_ocp, val); // Set the value (res[0]._ocp)
return this;
}
};
}
}
if (obsCtxPrm = res && res._cxp) {
// If this helper resource is an observable contextual parameter
if (arguments.length > 2) {
deps = res[1] ? $sub._ceo(res[1].deps) : [_ocp]; // fn deps (with any exprObs cloned using $sub._ceo)
deps.unshift(res[0]); // view
deps._cxp = obsCtxPrm;
// In a context callback for a contextual param, we set get = true, to get ctxPrm [view, dependencies...] array - needed for observe call
return deps;
}
tagElse = obsCtxPrm.tagElse;
newRes = res[1] // linkFn for compiled expression
? obsCtxPrm.tag && obsCtxPrm.tag.cvtArgs
? obsCtxPrm.tag.cvtArgs(tagElse, 1)[obsCtxPrm.ind] // = tag.bndArgs() - for tag contextual parameter
: res[1](res[0].data, res[0], $sub) // = fn(data, view, $sub) for compiled binding expression
: res[0]._ocp; // Observable contextual parameter (uninitialized, or initialized as static expression, so no path dependencies)
if (isUpdate) {
$sub._ucp(key, value, storeView, obsCtxPrm); // Update observable contextual parameter
return storeView;
}
res = newRes;
}
}
if (res && $isFunction(res)) {
// If a helper is of type function we will wrap it, so if called with no this pointer it will be called with the
// view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too.
// Note that helper functions on deeper paths will have specific this pointers, from the preceding path.
// For example, ~util.foo() will have the ~util object as 'this' pointer
wrapped = function() {
return res.apply((!this || this === global) ? callView : this, arguments);
};
$extend(wrapped, res); // Attach same expandos (if any) to the wrapped function
}
return wrapped || res;
}
}
/* Internal private: view._getTmpl() */
function getTemplate(tmpl) {
return tmpl && (tmpl.fn
? tmpl
: this.getRsc("templates", tmpl) || $templates(tmpl)); // not yet compiled
}
//==============
// views._cnvt
//==============
function convertVal(converter, view, tagCtx, onError) {
// Called from compiled template code for {{:}}
// self is template object or linkCtx object
var tag, linkCtx, value, argsLen, bindTo,
// If tagCtx is an integer, then it is the key for the compiled function to return the boundTag tagCtx
boundTag = typeof tagCtx === "number" && view.tmpl.bnds[tagCtx-1];
if (onError === undefined && boundTag && boundTag._lr) { // lateRender
onError = "";
}
if (onError !== undefined) {
tagCtx = onError = {props: {}, args: [onError]};
} else if (boundTag) {
tagCtx = boundTag(view.data, view, $sub);
}
boundTag = boundTag._bd && boundTag;
if (converter || boundTag) {
linkCtx = view._lc; // For data-link="{cvt:...}"... See onDataLinkedTagChange
tag = linkCtx && linkCtx.tag;
tagCtx.view = view;
if (!tag) {
tag = $extend(new $sub._tg(), {
_: {
bnd: boundTag,
unlinked: true,
lt: tagCtx.lt // If a late path @some.path has not returned @some object, mark tag as late
},
inline: !linkCtx,
tagName: ":",
convert: converter,
onArrayChange: true,
flow: true,
tagCtx: tagCtx,
tagCtxs: [tagCtx],
_is: "tag"
});
argsLen = tagCtx.args.length;
if (argsLen>1) {
bindTo = tag.bindTo = [];
while (argsLen--) {
bindTo.unshift(argsLen); // Bind to all the arguments - generate bindTo array: [0,1,2...]
}
}
if (linkCtx) {
linkCtx.tag = tag;
tag.linkCtx = linkCtx;
}
tagCtx.ctx = extendCtx(tagCtx.ctx, (linkCtx ? linkCtx.view : view).ctx);
tagHandlersFromProps(tag, tagCtx);
}
tag._er = onError && value;
tag.ctx = tagCtx.ctx || tag.ctx || {};
tagCtx.ctx = undefined;
value = tag.cvtArgs()[0]; // If there is a convertBack but no convert, converter will be "true"
tag._er = onError && value;
} else {
value = tagCtx.args[0];
}
// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
value = boundTag && view._.onRender
? view._.onRender(value, view, tag)
: value;
return value != undefined ? value : "";
}
function convertArgs(tagElse, bound) { // tag.cvtArgs() or tag.cvtArgs(tagElse?, true?)
var l, key, boundArgs, args, bindFrom, tag, converter,
tagCtx = this;
if (tagCtx.tagName) {
tag = tagCtx;
tagCtx = (tag.tagCtxs || [tagCtx])[tagElse||0];
if (!tagCtx) {
return;
}
} else {
tag = tagCtx.tag;
}
bindFrom = tag.bindFrom;
args = tagCtx.args;
if ((converter = tag.convert) && typeof converter === STRING) {
converter = converter === "true"
? undefined
: (tagCtx.view.getRsc("converters", converter) || error("Unknown converter: '" + converter + "'"));
}
if (converter && !bound) { // If there is a converter, use a copy of the tagCtx.args array for rendering, and replace the args[0] in
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)
}
if (bindFrom) { // Get the values of the boundArgs
boundArgs = [];
l = bindFrom.length;
while (l--) {
key = bindFrom[l];
boundArgs.unshift(argOrProp(tagCtx, key));
}
if (bound) {
args = boundArgs; // Call to bndArgs() - returns the boundArgs
}
}
if (converter) {
converter = converter.apply(tag, boundArgs || args);
if (converter === undefined) {
return args; // Returning undefined from a converter is equivalent to not having a converter.
}
bindFrom = bindFrom || [0];
l = bindFrom.length;
if (!$isArray(converter) || (converter.arg0 !== false && (l === 1 || converter.length !== l || converter.arg0))) {
converter = [converter]; // Returning converter as first arg, even if converter value is an array
bindFrom = [0];
l = 1;
}
if (bound) { // Call to bndArgs() - so apply converter to all boundArgs
args = converter; // The array of values returned from the converter
} else { // Call to cvtArgs()
while (l--) {
key = bindFrom[l];
if (+key === key) {
args[key] = converter[l];
}
}
}
}
return args;
}
function argOrProp(context, key) {
context = context[+key === key ? "args" : "props"];
return context && context[key];
}
function convertBoundArgs(tagElse) { // tag.bndArgs()
return this.cvtArgs(tagElse, 1);
}
//=============
// views.tag
//=============
/* view.getRsc() */
function getResource(resourceType, itemName) {
var res, store,
view = this;
if (typeof itemName === STRING) {
while ((res === undefined) && view) {
store = view.tmpl && view.tmpl[resourceType];
res = store && store[itemName];
view = view.parent;
}
return res || $views[resourceType][itemName];
}
}
function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) {
function bindToOrBindFrom(type) {
var bindArray = tag[type];
if (bindArray !== undefined) {
bindArray = $isArray(bindArray) ? bindArray : [bindArray];
m = bindArray.length;
while (m--) {
key = bindArray[m];
if (!isNaN(parseInt(key))) {
bindArray[m] = parseInt(key); // Convert "0" to 0, etc.
}
}
}
return bindArray || [0];
}
parentView = parentView || topView;
var tag, tagDef, template, tags, attr, parentTag, l, m, n, itemRet, tagCtx, tagCtxCtx, ctxPrm, bindTo, bindFrom, initVal,
content, callInit, mapDef, thisMap, args, bdArgs, props, tagDataMap, contentCtx, key, bindFromLength, bindToLength, linkedElement, defaultCtx,
i = 0,
ret = "",
linkCtx = parentView._lc || false, // For data-link="{myTag...}"... See onDataLinkedTagChange
ctx = parentView.ctx,
parentTmpl = tmpl || parentView.tmpl,
// If tagCtxs is an integer, then it is the key for the compiled function to return the boundTag tagCtxs
boundTag = typeof tagCtxs === "number" && parentView.tmpl.bnds[tagCtxs-1];
if (tagName._is === "tag") {
tag = tagName;
tagName = tag.tagName;
tagCtxs = tag.tagCtxs;
template = tag.template;
} else {
tagDef = parentView.getRsc("tags", tagName) || error("Unknown tag: {{" + tagName + "}} ");
template = tagDef.template;
}
if (onError === undefined && boundTag && (boundTag._lr = (tagDef.lateRender && boundTag._lr!== false || boundTag._lr))) {
onError = ""; // If lateRender, set temporary onError, to skip initial rendering (and render just "")
}
if (onError !== undefined) {
ret += onError;
tagCtxs = onError = [{props: {}, args: [], params: {props:{}}}];
} else if (boundTag) {
tagCtxs = boundTag(parentView.data, parentView, $sub);
}
l = tagCtxs.length;
for (; i < l; i++) {
tagCtx = tagCtxs[i];
content = tagCtx.tmpl;
if (!linkCtx || !linkCtx.tag || i && !linkCtx.tag.inline || tag._er || content && +content===content) {
// Initialize tagCtx
// For block tags, tagCtx.tmpl is an integer > 0
if (content && parentTmpl.tmpls) {
tagCtx.tmpl = tagCtx.content = parentTmpl.tmpls[content - 1]; // Set the tmpl property to the content of the block tag
}
tagCtx.index = i;
tagCtx.ctxPrm = contextParameter;
tagCtx.render = renderContent;
tagCtx.cvtArgs = convertArgs;
tagCtx.bndArgs = convertBoundArgs;
tagCtx.view = parentView;
tagCtx.ctx = extendCtx(extendCtx(tagCtx.ctx, tagDef && tagDef.ctx), ctx); // Clone and extend parentView.ctx
}
if (tmpl = tagCtx.props.tmpl) {
// If the tmpl property is overridden, set the value (when initializing, or, in case of binding: ^tmpl=..., when updating)
tagCtx.tmpl = parentView._getTmpl(tmpl);
tagCtx.content = tagCtx.content || tagCtx.tmpl;
}
if (!tag) {
// This will only be hit for initial tagCtx (not for {{else}}) - if the tag instance does not exist yet
// If the tag has not already been instantiated, we will create a new instance.
// ~tag will access the tag, even within the rendering of the template content of this tag.
// From child/descendant tags, can access using ~tag.parent, or ~parentTags.tagName
tag = new tagDef._ctr();
callInit = !!tag.init;
tag.parent = parentTag = ctx && ctx.tag;
tag.tagCtxs = tagCtxs;
if (linkCtx) {
tag.inline = false;
linkCtx.tag = tag;
}
tag.linkCtx = linkCtx;
if (tag._.bnd = boundTag || linkCtx.fn) {
// Bound if {^{tag...}} or data-link="{tag...}"
tag._.ths = tagCtx.params.props["this"]; // Tag has a this=expr binding, to get javascript reference to tag instance
tag._.lt = tagCtxs.lt; // If a late path @some.path has not returned @some object, mark tag as late
tag._.arrVws = {};
} else if (tag.dataBoundOnly) {
error(tagName + " must be data-bound:\n{^{" + tagName + "}}");
}
//TODO better perf for childTags() - keep child tag.tags array, (and remove child, when disposed)
// tag.tags = [];
} else if (linkCtx && linkCtx.fn._lr) {
callInit = !!tag.init;
}
tagDataMap = tag.dataMap;
tagCtx.tag = tag;
if (tagDataMap && tagCtxs) {
tagCtx.map = tagCtxs[i].map; // Copy over the compiled map instance from the previous tagCtxs to the refreshed ones
}
if (!tag.flow) {
tagCtxCtx = tagCtx.ctx = tagCtx.ctx || {};
// tags hash: tag.ctx.tags, merged with parentView.ctx.tags,
tags = tag.parents = tagCtxCtx.parentTags = ctx && extendCtx(tagCtxCtx.parentTags, ctx.parentTags) || {};
if (parentTag) {
tags[parentTag.tagName] = parentTag;
//TODO better perf for childTags: parentTag.tags.push(tag);
}
tags[tag.tagName] = tagCtxCtx.tag = tag;
tagCtxCtx.tagCtx = tagCtx;
}
}
if (!(tag._er = onError)) {
tagHandlersFromProps(tag, tagCtxs[0]);
tag.rendering = {rndr: tag.rendering}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...)
for (i = 0; i < l; i++) { // Iterate tagCtx for each {{else}} block
tagCtx = tag.tagCtx = tagCtxs[i];
props = tagCtx.props;
tag.ctx = tagCtx.ctx;
if (!i) {
if (callInit) {
tag.init(tagCtx, linkCtx, tag.ctx);
callInit = undefined;
}
if (!tagCtx.args.length && tagCtx.argDefault !== false && tag.argDefault !== false) {
tagCtx.args = args = [tagCtx.view.data]; // Missing first arg defaults to the current data context
tagCtx.params.args = ["#data"];
}
bindTo = bindToOrBindFrom("bindTo");
if (tag.bindTo !== undefined) {
tag.bindTo = bindTo;
}
if (tag.bindFrom !== undefined) {
tag.bindFrom = bindToOrBindFrom("bindFrom");
} else if (tag.bindTo) {
tag.bindFrom = tag.bindTo = bindTo;
}
bindFrom = tag.bindFrom || bindTo;
bindToLength = bindTo.length;
bindFromLength = bindFrom.length;
if (tag._.bnd && (linkedElement = tag.linkedElement)) {
tag.linkedElement = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement];
if (bindToLength !== linkedElement.length) {
error("linkedElement not same length as bindTo");
}
}
if (linkedElement = tag.linkedCtxParam) {
tag.linkedCtxParam = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement];
if (bindFromLength !== linkedElement.length) {
error("linkedCtxParam not same length as bindFrom/bindTo");
}
}
if (bindFrom) {
tag._.fromIndex = {}; // Hash of bindFrom index which has same path value as bindTo index. fromIndex = tag._.fromIndex[toIndex]
tag._.toIndex = {}; // Hash of bindFrom index which has same path value as bindTo index. fromIndex = tag._.fromIndex[toIndex]
n = bindFromLength;
while (n--) {
key = bindFrom[n];
m = bindToLength;
while (m--) {
if (key === bindTo[m]) {
tag._.fromIndex[m] = n;
tag._.toIndex[n] = m;
}
}
}
}
if (linkCtx) {
// Set attr on linkCtx to ensure outputting to the correct target attribute.
// Setting either linkCtx.attr or this.attr in the init() allows per-instance choice of target attrib.
linkCtx.attr = tag.attr = linkCtx.attr || tag.attr || linkCtx._dfAt;
}
attr = tag.attr;
tag._.noVws = attr && attr !== HTML;
}
args = tag.cvtArgs(i);
if (tag.linkedCtxParam) {
bdArgs = tag.cvtArgs(i, 1);
m = bindFromLength;
defaultCtx = tag.constructor.prototype.ctx;
while (m--) {
if (ctxPrm = tag.linkedCtxParam[m]) {
key = bindFrom[m];
initVal = bdArgs[m];
// Create tag contextual parameter
tagCtx.ctx[ctxPrm] = $sub._cp(
defaultCtx && initVal === undefined ? defaultCtx[ctxPrm]: initVal,
initVal !== undefined && argOrProp(tagCtx.params, key),
tagCtx.view,
tag._.bnd && {tag: tag, cvt: tag.convert, ind: m, tagElse: i}
);
}
}
}
if ((mapDef = props.dataMap || tagDataMap) && (args.length || props.dataMap)) {
thisMap = tagCtx.map;
if (!thisMap || thisMap.src !== args[0] || isUpdate) {
if (thisMap && thisMap.src) {
thisMap.unmap(); // only called if observable map - not when only used in JsRender, e.g. by {{props}}
}
mapDef.map(args[0], tagCtx, thisMap, !tag._.bnd);
thisMap = tagCtx.map;
}
args = [thisMap.tgt];
}
itemRet = undefined;
if (tag.render) {
itemRet = tag.render.apply(tag, args);
if (parentView.linked && itemRet && !rWrappedInViewMarker.test(itemRet)) {
// When a tag renders content from the render method, with data linking then we need to wrap with view markers, if absent,
// to provide a contentView for the tag, which will correctly dispose bindings if deleted. The 'tmpl' for this view will
// be a dumbed-down template which will always return the itemRet string (no matter what the data is). The itemRet string
// is not compiled as template markup, so can include "{{" or "}}" without triggering syntax errors
tmpl = { // 'Dumbed-down' template which always renders 'static' itemRet string
links: []
};
tmpl.render = tmpl.fn = function() {
return itemRet;
};
itemRet = renderWithViews(tmpl, parentView.data, undefined, true, parentView, undefined, undefined, tag);
}
}
if (!args.length) {
args = [parentView]; // no arguments - (e.g. {{else}}) get data context from view.
}
if (itemRet === undefined) {
contentCtx = args[0]; // Default data context for wrapped block content is the first argument
if (tag.contentCtx) { // Set tag.contentCtx to true, to inherit parent context, or to a function to provide alternate context.
contentCtx = tag.contentCtx === true ? parentView : tag.contentCtx(contentCtx);
}
itemRet = tagCtx.render(contentCtx, true) || (isUpdate ? undefined : "");
}
ret = ret
? ret + (itemRet || "")
: itemRet !== undefined
? "" + itemRet
: undefined; // If no return value from render, and no template/content tagCtx.render(...), return undefined
}
tag.rendering = tag.rendering.rndr; // Remove tag.rendering object (if this is outermost render call. (In case of nested calls)
}
tag.tagCtx = tagCtxs[0];
tag.ctx = tag.tagCtx.ctx;
if (tag._.noVws && tag.inline) {
// inline tag with attr set to "text" will insert HTML-encoded content - as if it was element-based innerText
ret = attr === "text"
? $converters.html(ret)
: "";
}
return boundTag && parentView._.onRender
// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
? parentView._.onRender(ret, parentView, tag)
: ret;
}
//=================
// View constructor
//=================
function View(context, type, parentView, data, template, key, onRender, contentTmpl) {
// Constructor for view object in view hierarchy. (Augmented by JsViews if JsViews is loaded)
var views, parentView_, tag, self_,
self = this,
isArray = type === "array";
// If the data is an array, this is an 'array view' with a views array for each child 'item view'
// If the data is not an array, this is an 'item view' with a views 'hash' object for any child nested views
self.content = contentTmpl;
self.views = isArray ? [] : {};
self.data = data;
self.tmpl = template;
self_ = self._ = {
key: 0,
// ._.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
useKey: isArray ? 0 : 1,
id: "" + viewId++,
onRender: onRender,
bnds: {}
};
self.linked = !!onRender;
self.type = type || "top";
if (type) {
self.cache = {_ct: $subSettings._cchCt}; // Used for caching results of computed properties and helpers (view.getCache)
}
if (!parentView || parentView.type === "top") {
(self.ctx = context || {}).root = self.data;
}
if (self.parent = parentView) {
self.root = parentView.root || self; // view whose parent is top view
views = parentView.views;
parentView_ = parentView._;
self.isTop = parentView_.scp; // Is top content view of a link("#container", ...) call
self.scope = (!context.tag || context.tag === parentView.ctx.tag) && !self.isTop && parentView.scope || self;
// Scope for contextParams - closest non flow tag ancestor or root view
if (parentView_.useKey) {
// Parent is not an 'array view'. Add this view to its views object
// self._key = is the key in the parent view hash
views[self_.key = "_" + parentView_.useKey++] = self;
self.index = indexStr;
self.getIndex = getNestedIndex;
} else if (views.length === (self_.key = self.index = key)) { // Parent is an 'array view'. Add this view to its views array
views.push(self); // Adding to end of views array. (Using push when possible - better perf than splice)
} else {
views.splice(key, 0, self); // Inserting in views array
}
// If no context was passed in, use parent context
// If context was passed in, it should have been merged already with parent context
self.ctx = context || parentView.ctx;
} else if (type) {
self.root = self; // view whose parent is top view
}
}
View.prototype = {
get: getView,
getIndex: getIndex,
ctxPrm: contextParameter,
getRsc: getResource,
_getTmpl: getTemplate,
_getOb: getPathObject,
getCache: function(key) { // Get cached value of computed value
if ($subSettings._cchCt > this.cache._ct) {
this.cache = {_ct: $subSettings._cchCt};
}
return this.cache[key] !== undefined ? this.cache[key] : (this.cache[key] = cpFnStore[key](this.data, this, $sub));
},
_is: "view"
};
//====================================================
// Registration
//====================================================
function compileChildResources(parentTmpl) {
var storeName, storeNames, resources;
for (storeName in jsvStores) {
storeNames = storeName + "s";
if (parentTmpl[storeNames]) {
resources = parentTmpl[storeNames]; // Resources not yet compiled
parentTmpl[storeNames] = {}; // Remove uncompiled resources
$views[storeNames](resources, parentTmpl); // Add back in the compiled resources
}
}
}
//===============
// compileTag
//===============
function compileTag(name, tagDef, parentTmpl) {
var tmpl, baseTag, prop,
compiledDef = new $sub._tg();
function Tag() {
var tag = this;
tag._ = {
unlinked: true
};
tag.inline = true;
tag.tagName = name;
}
if ($isFunction(tagDef)) {
// Simple tag declared as function. No presenter instantation.
tagDef = {
depends: tagDef.depends,
render: tagDef
};
} else if (typeof tagDef === STRING) {
tagDef = {template: tagDef};
}
if (baseTag = tagDef.baseTag) {
tagDef.flow = !!tagDef.flow; // Set flow property, so defaults to false even if baseTag has flow=true
baseTag = typeof baseTag === STRING
? (parentTmpl && parentTmpl.tags[baseTag] || $tags[baseTag])
: baseTag;
if (!baseTag) {
error('baseTag: "' + tagDef.baseTag + '" not found');
}
compiledDef = $extend(compiledDef, baseTag);
for (prop in tagDef) {
compiledDef[prop] = getMethod(baseTag[prop], tagDef[prop]);
}
} else {
compiledDef = $extend(compiledDef, tagDef);
}
// Tag declared as object, used as the prototype for tag instantiation (control/presenter)
if ((tmpl = compiledDef.template) !== undefined) {
compiledDef.template = typeof tmpl === STRING ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
}
(Tag.prototype = compiledDef).constructor = compiledDef._ctr = Tag;
if (parentTmpl) {
compiledDef._parentTmpl = parentTmpl;
}
return compiledDef;
}
function baseApply(args) {
// In derived method (or handler declared declaratively as in {{:foo onChange=~fooChanged}} can call base method,
// using this.baseApply(arguments) (Equivalent to this._superApply(arguments) in jQuery UI)
return this.base.apply(this, args);
}
//===============
// compileTmpl
//===============
function compileTmpl(name, tmpl, parentTmpl, options) {
// tmpl is either a template object, a selector for a template script block, or the name of a compiled template
//==== nested functions ====
function lookupTemplate(value) {
// If value is of type string - treat as selector, or name of compiled template
// Return the template object, if already compiled, or the markup string
var currentName, tmpl;
if ((typeof value === STRING) || value.nodeType > 0 && (elem = value)) {
if (!elem) {
if (/^\.?\/[^\\:*?"<>]*$/.test(value)) {
// value="./some/file.html" (or "/some/file.html")
// If the template is not named, use "./some/file.html" as name.
if (tmpl = $templates[name = name || value]) {
value = tmpl;
} else {
// BROWSER-SPECIFIC CODE (not on Node.js):
// Look for server-generated script block with id "./some/file.html"
elem = document.getElementById(value);
}
} else if (value.charAt(0) === "#") {
elem = document.getElementById(value.slice(1));
} if (!elem && $.fn && !$sub.rTmpl.test(value)) {
try {
elem = $(value, document)[0]; // if jQuery is loaded, test for selector returning elements, and get first element
} catch (e) {}
}// END BROWSER-SPECIFIC CODE
} //BROWSER-SPECIFIC CODE
if (elem) {
if (elem.tagName !== "SCRIPT") {
error(value + ": Use script block, not " + elem.tagName);
}
if (options) {
// We will compile a new template using the markup in the script element
value = elem.innerHTML;
} else {
// We will cache a single copy of the compiled template, and associate it with the name
// (renaming from a previous name if there was one).
currentName = elem.getAttribute(tmplAttr);
if (currentName) {
if (currentName !== jsvTmpl) {
value = $templates[currentName];
delete $templates[currentName];
} else if ($.fn) {
value = $.data(elem)[jsvTmpl]; // Get cached compiled template
}
}
if (!currentName || !value) { // Not yet compiled, or cached version lost
name = name || ($.fn ? jsvTmpl : value);
value = compileTmpl(name, elem.innerHTML, parentTmpl, options);
}
value.tmplName = name = name || currentName;
if (name !== jsvTmpl) {
$templates[name] = value;
}
elem.setAttribute(tmplAttr, name);
if ($.fn) {
$.data(elem, jsvTmpl, value);
}
}
} // END BROWSER-SPECIFIC CODE
elem = undefined;
} else if (!value.fn) {
value = undefined;
// If value is not a string. HTML element, or compiled template, return undefined
}
return value;
}
var elem, compiledTmpl,
tmplOrMarkup = tmpl = tmpl || "";
$sub._html = $converters.html;
//==== Compile the template ====
if (options === 0) {
options = undefined;
tmplOrMarkup = lookupTemplate(tmplOrMarkup); // Top-level compile so do a template lookup
}
// If options, then this was already compiled from a (script) element template declaration.
// If not, then if tmpl is a template object, use it for options
options = options || (tmpl.markup
? tmpl.bnds
? $extend({}, tmpl)
: tmpl
: {}
);
options.tmplName = options.tmplName || name || "unnamed";
if (parentTmpl) {
options._parentTmpl = parentTmpl;
}
// If tmpl is not a markup string or a selector string, then it must be a template object
// In that case, get it from the markup property of the object
if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = lookupTemplate(tmpl.markup)) && tmplOrMarkup.fn) {
// If the string references a compiled template object, need to recompile to merge any modified options
tmplOrMarkup = tmplOrMarkup.markup;
}
if (tmplOrMarkup !== undefined) {
if (tmplOrMarkup.render || tmpl.render) {
// tmpl is already compiled, so use it
if (tmplOrMarkup.tmpls) {
compiledTmpl = tmplOrMarkup;
}
} else {
// tmplOrMarkup is a markup string, not a compiled template
// Create template object
tmpl = tmplObject(tmplOrMarkup, options);
// Compile to AST and then to compiled function
tmplFn(tmplOrMarkup.replace(rEscapeQuotes, "\\$&"), tmpl);
}
if (!compiledTmpl) {
compiledTmpl = $extend(function() {
return compiledTmpl.render.apply(compiledTmpl, arguments);
}, tmpl);
compileChildResources(compiledTmpl);
}
return compiledTmpl;
}
}
//==== /end of function compileTmpl ====
//=================
// compileViewModel
//=================
function getDefaultVal(defaultVal, data) {
return $isFunction(defaultVal)
? defaultVal.call(data)
: defaultVal;
}
function addParentRef(ob, ref, parent) {
Object.defineProperty(ob, ref, {
value: parent,
configurable: true
});
}
function compileViewModel(name, type) {
var i, constructor, parent,
viewModels = this,
getters = type.getters,
extend = type.extend,
id = type.id,
proto = $.extend({
_is: name || "unnamed",
unmap: unmap,
merge: merge
}, extend),
args = "",
cnstr = "",
getterCount = getters ? getters.length : 0,
$observable = $.observable,
getterNames = {};
function JsvVm(args) {
constructor.apply(this, args);
}
function vm() {
return new JsvVm(arguments);
}
function iterate(data, action) {
var getterType, defaultVal, prop, ob, parentRef,
j = 0;
for (; j < getterCount; j++) {
prop = getters[j];
getterType = undefined;
if (typeof prop !== STRING) {
getterType = prop;
prop = getterType.getter;
parentRef = getterType.parentRef;
}
if ((ob = data[prop]) === undefined && getterType && (defaultVal = getterType.defaultVal) !== undefined) {
ob = getDefaultVal(defaultVal, data);
}
action(ob, getterType && viewModels[getterType.type], prop, parentRef);
}
}
function map(data) {
data = typeof data === STRING
? JSON.parse(data) // Accept JSON string
: data; // or object/array
var l, prop, childOb, parentRef,
j = 0,
ob = data,
arr = [];
if ($isArray(data)) {
data = data || [];
l = data.length;
for (; j<l; j++) {
arr.push(this.map(data[j]));
}
arr._is = name;
arr.unmap = unmap;
arr.merge = merge;
return arr;
}
if (data) {
iterate(data, function(ob, viewModel) {
if (viewModel) { // Iterate to build getters arg array (value, or mapped value)
ob = viewModel.map(ob);
} else if (typeof ob === STRING && (ob[0] === "{" && ob[ob.length-1] === "}" || ob[0] === "[" && ob[ob.length-1] === "]")){
// If it is a string with the start/end char: {/}, or [/], then assume it is a JSON expression
ob = JSON.parse(ob);
}
arr.push(ob);
});
ob = this.apply(this, arr); // Instantiate this View Model, passing getters args array to constructor
j = getterCount;
while (j--) {
childOb = arr[j];
parentRef = getters[j].parentRef;
if (parentRef && childOb && childOb.unmap) {
if ($isArray(childOb)) {
l = childOb.length;
while (l--) {
addParentRef(childOb[l], parentRef, ob);
}
} else {
addParentRef(childOb, parentRef, ob);
}
}
}
for (prop in data) { // Copy over any other properties. that are not get/set properties
if (prop !== $expando && !getterNames[prop]) {
ob[prop] = data[prop];
}
}
}
return ob;
}
function merge(data, parent, parentRef) {
data = typeof data === STRING
? JSON.parse(data) // Accept JSON string
: data; // or object/array
var j, l, m, prop, mod, found, assigned, ob, newModArr, childOb,
k = 0,
model = this;
if ($isArray(model)) {
assigned = {};
newModArr = [];
l = data.length;
m = model.length;
for (; k<l; k++) {
ob = data[k];
found = false;
for (j=0; j<m && !found; j++) {
if (assigned[j]) {
continue;
}
mod = model[j];
if (id) {
assigned[j] = found = typeof id === STRING
? (ob[id] && (getterNames[id] ? mod[id]() : mod[id]) === ob[id])
: id(mod, ob);
}
}
if (found) {
mod.merge(ob);
newModArr.push(mod);
} else {
newModArr.push(childOb = vm.map(ob));
if (parentRef) {
addParentRef(childOb, parentRef, parent);
}
}
}
if ($observable) {
$observable(model).refresh(newModArr, true);
} else {
model.splice.apply(model, [0, model.length].concat(newModArr));
}
return;
}
iterate(data, function(ob, viewModel, getter, parentRef) {
if (viewModel) {
model[getter]().merge(ob, model, parentRef); // Update typed property
} else if (model[getter]() !== ob) {
if (typeof ob === STRING && (ob[0] === "{" && ob[ob.length-1] === "}" || ob[0] === "[" && ob[ob.length-1] === "]")){
// If it is a string with the start/end char: {/}, or [/], then assume it is a JSON expression
ob = JSON.parse(ob);
}
model[getter](ob); // Update non-typed property
}
});
for (prop in data) {
if (prop !== $expando && !getterNames[prop]) {
model[prop] = data[prop];
}
}
}
function unmap() {
var ob, prop, getterType, arr, value,
k = 0,
model = this;
function unmapArray(modelArr) {
var arr = [],
i = 0,
l = modelArr.length;
for (; i<l; i++) {
arr.push(modelArr[i].unmap());
}
return arr;
}
if ($isArray(model)) {
return unmapArray(model);
}
ob = {};
for (; k < getterCount; k++) {
prop = getters[k];
getterType = undefined;
if (typeof prop !== STRING) {
getterType = prop;
prop = getterType.getter;
}
value = model[prop]();
ob[prop] = getterType && value && viewModels[getterType.type]
? $isArray(value)
? unmapArray(value)
: value.unmap()
: value;
}
for (prop in model) {
if (model.hasOwnProperty(prop) && (prop.charAt(0) !== "_" || !getterNames[prop.slice(1)]) && prop !== $expando && !$isFunction(model[prop])) {
ob[prop] = model[prop];
}
}
return ob;
}
JsvVm.prototype = proto;
for (i=0; i < getterCount; i++) {
(function(getter) {
getter = getter.getter || getter;
getterNames[getter] = i+1;
var privField = "_" + getter;
args += (args ? "," : "") + getter;
cnstr += "this." + privField + " = " + getter + ";\n";
proto[getter] = proto[getter] || function(val) {
if (!arguments.length) {
return this[privField]; // If there is no argument, use as a getter
}
if ($observable) {
$observable(this).setProperty(getter, val);
} else {
this[privField] = val;
}
};
if ($observable) {
proto[getter].set = proto[getter].set || function(val) {
this[privField] = val; // Setter called by observable property change
};
}
})(getters[i]);
}
// Constructor for new viewModel instance.
cnstr = new Function(args, cnstr);
constructor = function() {
cnstr.apply(this, arguments);
// Pass additional parentRef str and parent obj to have a parentRef pointer on instance
if (parent = arguments[getterCount + 1]) {
addParentRef(this, arguments[getterCount], parent);
}
};
constructor.prototype = proto;
proto.constructor = constructor;
vm.map = map;
vm.getters = getters;
vm.extend = extend;
vm.id = id;
return vm;
}
function tmplObject(markup, options) {
// Template object constructor
var htmlTag,
wrapMap = $subSettingsAdvanced._wm || {}, // Only used in JsViews. Otherwise empty: {}
tmpl = {
tmpls: [],
links: {}, // Compiled functions for link expressions
bnds: [],
_is: "template",
render: renderContent
};
if (opti