can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
289 lines (288 loc) • 10.3 kB
JavaScript
/*!
* CanJS - 2.3.34
* http://canjs.com/
* Copyright (c) 2018 Bitovi
* Mon, 30 Apr 2018 20:56:51 GMT
* Licensed MIT
*/
/*can@2.3.34#view/view*/
var can = require('../util/util.js');
var isFunction = can.isFunction, makeArray = can.makeArray, hookupId = 1;
var makeRenderer = function (textRenderer) {
var renderer = function () {
return $view.frag(textRenderer.apply(this, arguments));
};
renderer.render = function () {
return textRenderer.apply(textRenderer, arguments);
};
return renderer;
};
var checkText = function (text, url) {
if (!text.length) {
throw new Error('can.view: No template or empty template:' + url);
}
};
var getRenderer = function (obj, async) {
if (isFunction(obj)) {
var def = can.Deferred();
return def.resolve(obj);
}
var url = typeof obj === 'string' ? obj : obj.url, suffix = obj.engine && '.' + obj.engine || url.match(/\.[\w\d]+$/), type, el, id;
if (url.match(/^#/)) {
url = url.substr(1);
}
if (el = document.getElementById(url)) {
suffix = '.' + el.type.match(/\/(x\-)?(.+)/)[2];
}
if (!suffix && !$view.cached[url]) {
url += suffix = $view.ext;
}
if (can.isArray(suffix)) {
suffix = suffix[0];
}
id = $view.toId(url);
if (url.match(/^\/\//)) {
url = url.substr(2);
url = !window.steal ? url : steal.config().root.mapJoin('' + steal.id(url));
}
if (window.require) {
if (require.toUrl) {
url = require.toUrl(url);
}
}
type = $view.types[suffix];
if ($view.cached[id]) {
return $view.cached[id];
} else if (el) {
return $view.registerView(id, el.innerHTML, type);
} else {
var d = new can.Deferred();
can.ajax({
async: async,
url: url,
dataType: 'text',
error: function (jqXHR) {
checkText('', url);
d.reject(jqXHR);
},
success: function (text) {
checkText(text, url);
$view.registerView(id, text, type, d);
}
});
return d;
}
};
var getDeferreds = function (data) {
var deferreds = [];
if (can.isPromise(data)) {
return [data];
} else {
for (var prop in data) {
if (can.isPromise(data[prop])) {
deferreds.push(data[prop]);
}
}
}
return deferreds;
};
var usefulPart = function (resolved) {
return can.isArray(resolved) && resolved[1] === 'success' ? resolved[0] : resolved;
};
var $view = can.view = can.template = function (view, data, helpers, callback) {
if (isFunction(helpers)) {
callback = helpers;
helpers = undefined;
}
return $view.renderAs('fragment', view, data, helpers, callback);
};
can.extend($view, {
frag: function (result, parentNode) {
return $view.hookup($view.fragment(result), parentNode);
},
fragment: function (result) {
return can.frag(result, document);
},
toId: function (src) {
return can.map(src.toString().split(/\/|\./g), function (part) {
if (part) {
return part;
}
}).join('_');
},
toStr: function (txt) {
return txt == null ? '' : '' + txt;
},
hookup: function (fragment, parentNode) {
var hookupEls = [], id, func;
can.each(fragment.childNodes ? can.makeArray(fragment.childNodes) : fragment, function (node) {
if (node.nodeType === 1) {
hookupEls.push(node);
hookupEls.push.apply(hookupEls, can.makeArray(node.getElementsByTagName('*')));
}
});
can.each(hookupEls, function (el) {
if (el.getAttribute && (id = el.getAttribute('data-view-id')) && (func = $view.hookups[id])) {
func(el, parentNode, id);
delete $view.hookups[id];
el.removeAttribute('data-view-id');
}
});
return fragment;
},
hookups: {},
hook: function (cb) {
$view.hookups[++hookupId] = cb;
return ' data-view-id=\'' + hookupId + '\'';
},
cached: {},
cachedRenderers: {},
cache: true,
register: function (info) {
this.types['.' + info.suffix] = info;
can[info.suffix] = $view[info.suffix] = function (id, text) {
var renderer, renderFunc;
if (!text) {
renderFunc = function () {
if (!renderer) {
if (info.fragRenderer) {
renderer = info.fragRenderer(null, id);
} else {
renderer = makeRenderer(info.renderer(null, id));
}
}
return renderer.apply(this, arguments);
};
renderFunc.render = function () {
var textRenderer = info.renderer(null, id);
return textRenderer.apply(textRenderer, arguments);
};
return renderFunc;
}
var registeredRenderer = function () {
if (!renderer) {
if (info.fragRenderer) {
renderer = info.fragRenderer(id, text);
} else {
renderer = info.renderer(id, text);
}
}
return renderer.apply(this, arguments);
};
if (info.fragRenderer) {
return $view.preload(id, registeredRenderer);
} else {
return $view.preloadStringRenderer(id, registeredRenderer);
}
};
},
types: {},
ext: '.ejs',
registerScript: function (type, id, src) {
return 'can.view.preloadStringRenderer(\'' + id + '\',' + $view.types['.' + type].script(id, src) + ');';
},
preload: function (id, renderer) {
var def = $view.cached[id] = new can.Deferred().resolve(function (data, helpers) {
return renderer.call(data, data, helpers);
});
def.__view_id = id;
$view.cachedRenderers[id] = renderer;
return renderer;
},
preloadStringRenderer: function (id, stringRenderer) {
return this.preload(id, makeRenderer(stringRenderer));
},
render: function (view, data, helpers, callback, nodelist) {
return can.view.renderAs('string', view, data, helpers, callback, nodelist);
},
renderTo: function (format, renderer, data, helpers, nodelist) {
return (format === 'string' && renderer.render ? renderer.render : renderer)(data, helpers, nodelist);
},
renderAs: function (format, view, data, helpers, callback, nodelist) {
if (callback !== undefined && typeof callback.expression === 'string') {
nodelist = callback;
callback = undefined;
}
if (isFunction(helpers)) {
callback = helpers;
helpers = undefined;
}
var deferreds = getDeferreds(data);
var deferred, dataCopy, async, response;
if (deferreds.length) {
deferred = new can.Deferred();
dataCopy = can.extend({}, data);
deferreds.push(getRenderer(view, true));
can.when.apply(can, deferreds).then(function (resolved) {
var objs = makeArray(arguments), renderer = objs.pop(), result;
if (can.isPromise(data)) {
dataCopy = usefulPart(resolved);
} else {
for (var prop in data) {
if (can.isPromise(data[prop])) {
dataCopy[prop] = usefulPart(objs.shift());
}
}
}
result = can.view.renderTo(format, renderer, dataCopy, helpers, nodelist);
deferred.resolve(result, dataCopy);
if (callback) {
callback(result, dataCopy);
}
}, function () {
deferred.reject.apply(deferred, arguments);
});
return deferred;
} else {
async = isFunction(callback);
deferred = can.__notObserve(getRenderer)(view, async);
if (async) {
response = deferred;
deferred.then(function (renderer) {
callback(data ? can.view.renderTo(format, renderer, data, helpers, nodelist) : renderer);
});
} else {
if (deferred.state() === 'resolved' && deferred.__view_id) {
var currentRenderer = $view.cachedRenderers[deferred.__view_id];
return data ? can.view.renderTo(format, currentRenderer, data, helpers, nodelist) : currentRenderer;
} else {
deferred.then(function (renderer) {
response = data ? can.view.renderTo(format, renderer, data, helpers, nodelist) : renderer;
});
}
}
return response;
}
},
registerView: function (id, text, type, def) {
var info = typeof type === 'object' ? type : $view.types[type || $view.ext], renderer;
if (info.fragRenderer) {
renderer = info.fragRenderer(id, text);
} else {
renderer = makeRenderer(info.renderer(id, text));
}
def = def || new can.Deferred();
if ($view.cache) {
$view.cached[id] = def;
def.__view_id = id;
$view.cachedRenderers[id] = renderer;
}
return def.resolve(renderer);
},
simpleHelper: function (fn) {
return function () {
var realArgs = [];
var fnArgs = arguments;
can.each(fnArgs, function (val, i) {
if (i <= fnArgs.length) {
while (val && val.isComputed) {
val = val();
}
realArgs.push(val);
}
});
return fn.apply(this, realArgs);
};
}
});
module.exports = can;