Ejemplo n.º 1
0
		viewCallbacks.tag(proto.tag, function(el, componentTagData){
			var viewModel;
			var teardownBindings = stacheBindings.behaviors.viewModel(el, componentTagData, function(initialViewModelData) {
				if(typeof proto.viewModel === "function") {
					return viewModel = new proto.viewModel(initialViewModelData);
				} else if(proto.viewModel instanceof CanMap){
					return viewModel = proto.viewModel;
				} else {
					var VM = CanMap.extend(proto.viewModel);
					return viewModel = new VM(initialViewModelData);
				}

			}, {});
			domData.set.call(el, "viewModel", viewModel);
			domData.set.call(el, "preventDataBindings", true);

			if(proto.template) {
				var shadowScope = componentTagData.scope.add(new Scope.Refs())
					.add(viewModel, {
						viewModel: true
					});
				domData.set.call(el, "shadowScope", shadowScope);
				var nodeList = nodeLists.register([], function(){
					teardownBindings();
				}, componentTagData.parentNodeList || true, false);
				var frag = proto.template(shadowScope, componentTagData.options, nodeList);

				domMutate.appendChild.call(el, frag);
			}
		});
Ejemplo n.º 2
0
		attribute: function (el, attributeName, compute) {
			var hook;
			listen(el, compute, function () {
				domAttr.set(el, attributeName, hook.render());
			});
			var hooks;
			// Get the list of hookups or create one for this element.
			// Hooks is a map of attribute names to hookup `data`s.
			// Each hookup data has:
			// `render` - A `function` to render the value of the attribute.
			// `funcs` - A list of hookup `function`s on that attribute.
			// `batchNum` - The last event `batchNum`, used for performance.
			hooks = domData.get.call(el, 'hooks');
			if (!hooks) {
				domData.set.call(el, 'hooks', hooks = {});
			}
			// Get the attribute value.
			// Cast to String. String expected for rendering. Attr may return other types for some attributes.
			var attr = String(domAttr.get(el, attributeName)),
				// Split the attribute value by the template.
				// Only split out the first __!!__ so if we have multiple hookups in the same attribute,
				// they will be put in the right spot on first render
				parts = attr.split(live.attributePlaceholder),
				goodParts = [];

			goodParts.push(parts.shift(), parts.join(live.attributePlaceholder));
			// If we already had a hookup for this attribute...
			if (hooks[attributeName]) {
				// Just add to that attribute's list of `function`s.
				hooks[attributeName].computes.push(compute);
			} else {
				// Create the hookup data.
				hooks[attributeName] = {
					render: function () {
						var i = 0,
							// attr doesn't have a value in IE
							newAttr = attr ? attr.replace(live.attributeReplace, function () {
								return view.contentText(hook.computes[i++]());
							}) : view.contentText(hook.computes[i++]());
						return newAttr;
					},
					computes: [compute],
					batchNum: undefined
				};
			}
			// Save the hook for slightly faster performance.
			hook = hooks[attributeName];
			// Insert the value in parts.
			goodParts.splice(1, 0, compute());

			// Set the attribute.
			domAttr.set(el, attributeName, goodParts.join(''));
		},
Ejemplo n.º 3
0
	function CanElement(){
		var self = Reflect.construct(BaseElement, arguments, this.constructor);

		self._rendered = false;
		var Element = self.constructor;
		if(Element.view) {
			self.attachShadow({ mode: "open" });
		}

		// Mark the element as its own viewModel for binding purposes
		var existingViewModel = domData.get.call(self, "viewModel");
		if(existingViewModel) {
			assign(self, existingViewModel.get());
		}
		domData.set.call(self, "viewModel", self);

		return self;
	}
Ejemplo n.º 4
0
tag("can-import", function(el, tagData){
	var moduleName = el.getAttribute("from");
	// If the module is part of the helpers pass that into can.import
	// as the parentName
	var templateModule = tagData.options.get("helpers.module");
	var parentName = templateModule ? templateModule.id : undefined;

	if(!moduleName) {
		return Promise.reject("No module name provided");
	}

	var importPromise = importer(moduleName, parentName);
	importPromise.catch(function(err) {
		canLog.error(err);
	});

	// Set the viewModel to the promise
	canData.set.call(el, "viewModel", importPromise);
	canData.set.call(el, "scope", importPromise);

	// Set the scope
	var scope = tagData.scope.add(importPromise);

	// If there is a can-tag present we will hand-off rendering to that tag.
	var handOffTag = el.getAttribute("can-tag");
	if(handOffTag) {
		var callback = tag(handOffTag);
		canData.set.call(el,"preventDataBindings", true);
		callback(el, assign(tagData, {
			scope: scope
		}));
		canData.set.call(el,"preventDataBindings", false);

		canData.set.call(el, "viewModel", importPromise);
		canData.set.call(el, "scope", importPromise);
	}
	// Render the subtemplate and register nodeLists
	else {
		var frag = tagData.subtemplate ?
			tagData.subtemplate(scope, tagData.options) :
			document.createDocumentFragment();

		var nodeList = nodeLists.register([], undefined, true);
		events.one.call(el, "removed", function(){
			nodeLists.unregister(nodeList);
		});

		el.appendChild(frag);
		nodeLists.update(nodeList, el.childNodes);
	}
});
Ejemplo n.º 5
0
module.exports = ns.viewModel = function (el, attr, val) {
	el = typeof el === 'string' ? getDocument().querySelector(el) : el;

	var scope = domData.get.call(el, "viewModel");
	if(!scope) {
		scope = types.DefaultMap ? new types.DefaultMap() : new SimpleMap();
		domData.set.call(el, "viewModel", scope);
	}
	switch (arguments.length) {
		case 0:
		case 1:
			return scope;
		case 2:
			return "attr" in scope ? scope.attr(attr) : scope[attr];
		default:
			if("attr" in scope) {
				scope.attr(attr, val);
			} else {
				scope[attr] = val;
			}
			return el;
	}
};
Ejemplo n.º 6
0
	viewCallbacks.tag(tagName, function(el, tagData){
		var getList = el.getAttribute("getList") || el.getAttribute("get-list");
		var getInstance = el.getAttribute("get");

		var attrValue = getList || getInstance;
		var method = getList ? "getList" : "get";

		var attrInfo = expression.parse('tmp(' + removeBrackets(attrValue)+")", {baseMethodType: "Call"});
		// -> {hash: {foo: 'bar', zed: 5, abc: {get: 'myValue'}}}


		var addedToPageData = false;
		var addToPageData = Observation.ignore(function(set, promise){
			if(!addedToPageData) {
				var root = tagData.scope.attr("%root") || tagData.scope.attr("@root");
				if( root && root.pageData ) {
					if(method === "get"){
						set = connection.id(set);
					}
					root.pageData(connection.name, set, promise);
				}
			}
			addedToPageData = true;
		});

		var request = compute(function(){
			var hash = {};
			if(typeof attrInfo.hash === "object") {
				// old expression data
				each(attrInfo.hash, function(val, key) {
					if (val && val.hasOwnProperty("get")) {
						hash[key] = tagData.scope.read(val.get, {}).value;
					} else {
						hash[key] = val;
					}
				});
			} else if(typeof attrInfo.hash === "function"){
				// new expression data
				var getHash = attrInfo.hash(tagData.scope, tagData.options, {});
				each(getHash(), function(val, key) {
					hash[key] = convertToValue(val);
				});
			} else {
				hash = attrInfo.argExprs.length ? attrInfo.argExprs[0].value(tagData.scope, tagData.options)()
					: {};
			}

			var promise = connection[method](hash);
			addToPageData(hash, promise);
			return promise;
		});

		domData.set.call(el, "viewModel", request);

		var nodeList = nodeLists.register([], undefined, tagData.parentNodeList || true);

		var frag = tagData.subtemplate ?
					tagData.subtemplate( tagData.scope.add(request), tagData.options, nodeList ) :
					document.createDocumentFragment();

		// Append the resulting document fragment to the element
		domMutate.appendChild.call(el, frag);

		// update the nodeList with the new children so the mapping gets applied
		nodeLists.update(nodeList, el.childNodes);

		// add to pageData

		canEvent.one.call(el, 'removed', function() {
			nodeLists.unregister(nodeList);
		});
	});
Ejemplo n.º 7
0
		setup: function(el, componentTagData) {
			var component = this;
			// If a template is not provided, we fall back to
			// dynamic scoping regardless of settings.
			var lexicalContent = (
					(typeof this.leakScope === "undefined" ? true : !this.leakScope) &&
					!!(this.template || this.view)
				);
			// an array of teardown stuff that should happen when the element is removed
			var teardownFunctions = [];
			var initialViewModelData = {};
			var callTeardownFunctions = function() {
					for (var i = 0, len = teardownFunctions.length; i < len; i++) {
						teardownFunctions[i]();
					}
				};
			var setupBindings = !domData.get.call(el, "preventDataBindings");
			var viewModel, frag;

			// ## Scope
			var teardownBindings;
			if (setupBindings) {
				var setupFn = componentTagData.setupBindings ||
					function(el, callback, data){
						return stacheBindings.behaviors.viewModel(el, componentTagData,
																											callback, data);
					};
				teardownBindings = setupFn(el, function(initialViewModelData) {

					var ViewModel = component.constructor.ViewModel,
						viewModelHandler = component.constructor.viewModelHandler,
						viewModelInstance = component.constructor.viewModelInstance;

					if(viewModelHandler) {
						var scopeResult = viewModelHandler.call(component, initialViewModelData, componentTagData.scope, el);
						if (types.isMapLike( scopeResult ) ) {
							// If the function returns a can.Map, use that as the viewModel
							viewModelInstance = scopeResult;
						} else if ( types.isMapLike(scopeResult.prototype) ) {
							// If `scopeResult` is of a `can.Map` type, use it to wrap the `initialViewModelData`
							ViewModel = scopeResult;
						} else {
							// Otherwise extend `can.Map` with the `scopeResult` and initialize it with the `initialViewModelData`
							ViewModel = types.DefaultMap.extend(scopeResult);
						}
					}

					if(ViewModel) {
						viewModelInstance = new component.constructor.ViewModel(initialViewModelData);
					}
					viewModel = viewModelInstance;
					return viewModelInstance;
				}, initialViewModelData);
			}

			// Set `viewModel` to `this.viewModel` and set it to the element's `data` object as a `viewModel` property
			this.viewModel = viewModel;

			domData.set.call(el, "viewModel", viewModel);
			domData.set.call(el, "preventDataBindings", true);

			// Create a real Scope object out of the viewModel property
			// The scope used to render the component's template.
			// However, if there is no template, the "light" dom is rendered with this anyway.
			var shadowScope;
			if (lexicalContent) {
				shadowScope = Scope.refsScope().add(this.viewModel, {
					viewModel: true
				});
			} else {
				// if this component has a template,
				// render the template with it's own Refs scope
				// otherwise, just add this component's viewModel.
				shadowScope = (this.constructor.renderer ?
						componentTagData.scope.add(new 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);
					};
				};

			// ## Helpers

			// Setup helpers to callback with `this` as the component
			canEach(this.helpers || {}, function(val, prop) {
				if (isFunction(val)) {
					addHelper(prop, val);
				}
			});

			// ## `events` control

			// Create a control to listen to events
			this._control = new this.constructor.Control(el, {
				// Pass the viewModel to the control so we can listen to it's changes from the controller.
				scope: this.viewModel,
				viewModel: this.viewModel,
				destroy: callTeardownFunctions
			});

			// ## Rendering

			// Keep a nodeList so we can kill any directly nested nodeLists within this component
			var nodeList = nodeLists.register([], function() {
				domDispatch.call(el, "beforeremove", [], false);
				if(teardownBindings) {
					teardownBindings();
				}
			}, componentTagData.parentNodeList || true, false);
			nodeList.expression = "<" + this.tag + ">";
			teardownFunctions.push(function() {
				nodeLists.unregister(nodeList);
			});

			// If this component has a template (that we've already converted to a renderer)
			if (this.constructor.renderer) {
				// If `options.tags` doesn't exist set it to an empty object.
				if (!options.tags) {
					options.tags = {};
				}

				// We need be alerted to when a <content> element is rendered so we can put the original contents of the widget in its place
				options.tags.content = function contentHookup(el, contentTagData) {
					// First check if there was content within the custom tag
					// otherwise, render what was within <content>, the default code.
					// `componentTagData.subtemplate` is the content inside this component
					var subtemplate = componentTagData.subtemplate || contentTagData.subtemplate,
						renderingLightContent = subtemplate === componentTagData.subtemplate;

					if (subtemplate) {

						// `contentTagData.options` is a viewModel of helpers where `<content>` was found, so
						// the right helpers should already be available.
						// However, `_tags.content` is going to point to this current content callback.  We need to
						// remove that so it will walk up the chain

						delete options.tags.content;

						// By default, light dom scoping is
						// dynamic. This means that any `{{foo}}`
						// bindings inside the "light dom" content of
						// the component will have access to the
						// internal viewModel. This can be overridden to be
						// lexical with the leakScope option.
						var lightTemplateData;
						if (renderingLightContent) {
							if (lexicalContent) {
								// render with the same scope the component was found within.
								lightTemplateData = componentTagData;
							} else {
								// render with the component's viewModel mixed in, however
								// we still want the outer refs to be used, NOT the component's refs
								// <component> {{some value }} </component>
								// To fix this, we
								// walk down the scope to the component's ref, clone scopes from that point up
								// use that as the new scope.
								lightTemplateData = {
									scope: contentTagData.scope.cloneFromRef(),
									options: contentTagData.options
								};
							}

						} else {
							// we are rendering default content so this content should
							// use the same scope as the <content> tag was found within.
							lightTemplateData = contentTagData;
						}

						if (contentTagData.parentNodeList) {
							var frag = subtemplate(lightTemplateData.scope, lightTemplateData.options, contentTagData.parentNodeList);
							nodeLists.replace([el], frag);
						} else {
							nodeLists.replace([el], subtemplate(lightTemplateData.scope, lightTemplateData.options));
						}

						// Restore the content tag so it could potentially be used again (as in lists)
						options.tags.content = contentHookup;
					}
				};
				// Render the component's template
				frag = this.constructor.renderer(shadowScope, componentTagData.options.add(options), nodeList);
			} else {
				// Otherwise render the contents between the element
				frag = componentTagData.subtemplate ?
					componentTagData.subtemplate(shadowScope, componentTagData.options.add(options), nodeList) :
					document.createDocumentFragment();

			}
			// Append the resulting document fragment to the element
			domMutate.appendChild.call(el, frag);

			// update the nodeList with the new children so the mapping gets applied
			nodeLists.update(nodeList, getChildNodes(el));
		}