can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
257 lines (256 loc) • 12.5 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#component/component*/
define([
'can/util/library',
'can/view/callbacks',
'can/elements',
'can/view/bindings',
'can/control',
'can/observe',
'can/view/mustache',
'can/util/view_model'
], function (can, viewCallbacks, elements, bindings) {
var paramReplacer = /\{([^\}]+)\}/g;
var Component = can.Component = can.Construct.extend({
setup: function () {
can.Construct.setup.apply(this, arguments);
if (can.Component) {
var self = this, protoViewModel = this.prototype.scope || this.prototype.viewModel;
this.Control = ComponentControl.extend(this.prototype.events);
if (!protoViewModel || typeof protoViewModel === 'object' && !(protoViewModel instanceof can.Map)) {
this.Map = can.Map.extend(protoViewModel || {});
} else if (protoViewModel.prototype instanceof can.Map) {
this.Map = protoViewModel;
}
this.attributeScopeMappings = {};
can.each(this.Map ? this.Map.defaults : {}, function (val, prop) {
if (val === '@') {
self.attributeScopeMappings[prop] = prop;
}
});
if (this.prototype.template) {
if (typeof this.prototype.template === 'function') {
var temp = this.prototype.template;
this.renderer = function () {
return can.view.frag(temp.apply(null, arguments));
};
} else {
this.renderer = can.view.mustache(this.prototype.template);
}
}
can.view.tag(this.prototype.tag, function (el, options) {
new self(el, options);
});
}
}
}, {
setup: function (el, componentTagData) {
var initialViewModelData = {}, component = this, lexicalContent = (typeof this.leakScope === 'undefined' ? false : !this.leakScope) && !!this.template, viewModel, frag, teardownFunctions = [], callTeardownFunctions = function () {
for (var i = 0, len = teardownFunctions.length; i < len; i++) {
teardownFunctions[i]();
}
}, $el = can.$(el), setupBindings = !can.data($el, 'preventDataBindings');
can.each(this.constructor.attributeScopeMappings, function (val, prop) {
initialViewModelData[prop] = el.getAttribute(can.hyphenate(val));
});
if (setupBindings) {
teardownFunctions.push(bindings.behaviors.viewModel(el, componentTagData, function (initialViewModelData) {
initialViewModelData['%root'] = componentTagData.scope.attr('%root');
var protoViewModel = component.scope || component.viewModel;
if (component.constructor.Map) {
viewModel = new component.constructor.Map(initialViewModelData);
} else if (protoViewModel instanceof can.Map) {
viewModel = protoViewModel;
} else if (can.isFunction(protoViewModel)) {
var scopeResult = protoViewModel.call(component, initialViewModelData, componentTagData.scope, el);
if (scopeResult instanceof can.Map) {
viewModel = scopeResult;
} else if (scopeResult.prototype instanceof can.Map) {
viewModel = new scopeResult(initialViewModelData);
} else {
viewModel = new (can.Map.extend(scopeResult))(initialViewModelData);
}
}
var oldSerialize = viewModel.serialize;
viewModel.serialize = function () {
var result = oldSerialize.apply(this, arguments);
delete result['%root'];
return result;
};
return viewModel;
}, initialViewModelData));
}
this.scope = this.viewModel = viewModel;
can.data($el, 'scope', this.viewModel);
can.data($el, 'viewModel', this.viewModel);
can.data($el, 'preventDataBindings', true);
var shadowScope;
if (lexicalContent) {
shadowScope = can.view.Scope.refsScope().add(this.viewModel, { viewModel: true });
} else {
shadowScope = (this.constructor.renderer ? componentTagData.scope.add(new can.view.Scope.Refs()) : componentTagData.scope).add(this.viewModel, { viewModel: true });
}
var options = { helpers: {} }, addHelper = function (name, fn) {
options.helpers[name] = function () {
return fn.apply(viewModel, arguments);
};
};
can.each(this.helpers || {}, function (val, prop) {
if (can.isFunction(val)) {
addHelper(prop, val);
}
});
can.each(this.simpleHelpers || {}, function (val, prop) {
if (options.helpers[prop]) {
can.dev.warn('Component ' + component.tag + ' already has a helper called ' + prop);
}
addHelper(prop, can.view.simpleHelper(val));
});
this._control = new this.constructor.Control(el, {
scope: this.viewModel,
viewModel: this.viewModel,
destroy: callTeardownFunctions
});
var nodeList = can.view.nodeLists.register([], undefined, componentTagData.parentNodeList || true, false);
nodeList.expression = '<' + this.tag + '>';
teardownFunctions.push(function () {
can.view.nodeLists.unregister(nodeList);
});
if (this.constructor.renderer) {
if (!options.tags) {
options.tags = {};
}
options.tags.content = function contentHookup(el, contentTagData) {
var subtemplate = componentTagData.subtemplate || contentTagData.subtemplate, renderingLightContent = subtemplate === componentTagData.subtemplate;
if (subtemplate) {
delete options.tags.content;
var lightTemplateData;
if (renderingLightContent) {
if (lexicalContent) {
lightTemplateData = componentTagData;
} else {
lightTemplateData = {
scope: contentTagData.scope.cloneFromRef(),
options: contentTagData.options
};
}
} else {
lightTemplateData = contentTagData;
}
if (contentTagData.parentNodeList) {
var frag = subtemplate(lightTemplateData.scope, lightTemplateData.options, contentTagData.parentNodeList);
elements.replace([el], frag);
} else {
can.view.live.replace([el], subtemplate(lightTemplateData.scope, lightTemplateData.options));
}
options.tags.content = contentHookup;
}
};
frag = this.constructor.renderer(shadowScope, componentTagData.options.add(options), nodeList);
} else {
if (componentTagData.templateType === 'legacy') {
frag = can.view.frag(componentTagData.subtemplate ? componentTagData.subtemplate(shadowScope, componentTagData.options.add(options)) : '');
} else {
frag = componentTagData.subtemplate ? componentTagData.subtemplate(shadowScope, componentTagData.options.add(options), nodeList) : document.createDocumentFragment();
}
}
can.appendChild(el, frag, can.document);
can.view.nodeLists.update(nodeList, can.childNodes(el));
}
});
var ComponentControl = can.Control.extend({
_lookup: function (options) {
return [
options.scope,
options,
window
];
},
_action: function (methodName, options, controlInstance) {
var hasObjectLookup, readyCompute;
paramReplacer.lastIndex = 0;
hasObjectLookup = paramReplacer.test(methodName);
if (!controlInstance && hasObjectLookup) {
return;
} else if (!hasObjectLookup) {
return can.Control._action.apply(this, arguments);
} else {
readyCompute = can.compute(function () {
var delegate;
var name = methodName.replace(paramReplacer, function (matched, key) {
var value;
if (key === 'scope' || key === 'viewModel') {
delegate = options.viewModel;
return '';
}
key = key.replace(/^(scope|^viewModel)\./, '');
value = can.compute.read(options.viewModel, can.compute.read.reads(key), { readCompute: false }).value;
if (value === undefined) {
value = can.getObject(key);
}
if (typeof value === 'string') {
return value;
} else {
delegate = value;
return '';
}
});
var parts = name.split(/\s+/g), event = parts.pop();
return {
processor: this.processors[event] || this.processors.click,
parts: [
name,
parts.join(' '),
event
],
delegate: delegate || undefined
};
}, this);
var handler = function (ev, ready) {
controlInstance._bindings.control[methodName](controlInstance.element);
controlInstance._bindings.control[methodName] = ready.processor(ready.delegate || controlInstance.element, ready.parts[2], ready.parts[1], methodName, controlInstance);
};
readyCompute.bind('change', handler);
controlInstance._bindings.readyComputes[methodName] = {
compute: readyCompute,
handler: handler
};
return readyCompute();
}
}
}, {
setup: function (el, options) {
this.scope = options.scope;
this.viewModel = options.viewModel;
return can.Control.prototype.setup.call(this, el, options);
},
off: function () {
if (this._bindings) {
can.each(this._bindings.readyComputes || {}, function (value) {
value.compute.unbind('change', value.handler);
});
}
can.Control.prototype.off.apply(this, arguments);
this._bindings.readyComputes = {};
},
destroy: function () {
can.Control.prototype.destroy.apply(this, arguments);
if (typeof this.options.destroy === 'function') {
this.options.destroy.apply(this, arguments);
}
}
});
var $ = can.$;
if ($.fn) {
$.fn.scope = $.fn.viewModel = function () {
return can.viewModel.apply(can, [this].concat(can.makeArray(arguments)));
};
}
return Component;
});