mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
156 lines
5.5 KiB
JavaScript
156 lines
5.5 KiB
JavaScript
'use strict';
|
|
var nextRepeatedId = require('./nextRepeatedId');
|
|
var repeatedRegExp = /\[\]$/;
|
|
var componentUtil = require('./util');
|
|
var nextComponentId = componentUtil.$__nextComponentId;
|
|
var attachBubblingEvent = componentUtil.$__attachBubblingEvent;
|
|
|
|
var extend = require('raptor-util/extend');
|
|
var registry = require('./registry');
|
|
|
|
/**
|
|
* A ComponentDef is used to hold the metadata collected at runtime for
|
|
* a single component and this information is used to instantiate the component
|
|
* later (after the rendered HTML has been added to the DOM)
|
|
*/
|
|
function ComponentDef(component, componentId, out, componentStack, componentStackLen) {
|
|
this.$__out = out; // The AsyncWriter that this component is associated with
|
|
this.$__componentStack = componentStack;
|
|
this.$__componentStackLen = componentStackLen;
|
|
this.$__component = component;
|
|
this.id = componentId;
|
|
|
|
this.$__scope = // The ID of the component that this component is scoped within
|
|
this.$__customEvents = // An array containing information about custom events
|
|
this.$__roots = // IDs of root elements if there are multiple root elements
|
|
this.$__children = // An array of nested ComponentDef instances
|
|
this.$__domEvents = // An array of DOM events that need to be added (in sets of three)
|
|
this.$__bubblingDomEvents = // Used to keep track of bubbling DOM events for components rendered on the server
|
|
undefined;
|
|
|
|
this.$__isExisting = false;
|
|
|
|
this.$__nextIdIndex = 0; // The unique integer to use for the next scoped ID
|
|
}
|
|
|
|
ComponentDef.prototype = {
|
|
$__end: function() {
|
|
this.$__componentStack.length = this.$__componentStackLen;
|
|
},
|
|
|
|
/**
|
|
* Register a nested component for this component. We maintain a tree of components
|
|
* so that we can instantiate nested components before their parents.
|
|
*/
|
|
$__addChild: function (componentDef) {
|
|
var children = this.$__children;
|
|
|
|
if (children) {
|
|
children.push(componentDef);
|
|
} else {
|
|
this.$__children = [componentDef];
|
|
}
|
|
},
|
|
/**
|
|
* This helper method generates a unique and fully qualified DOM element ID
|
|
* that is unique within the scope of the current component. This method prefixes
|
|
* the the nestedId with the ID of the current component. If nestedId ends
|
|
* with `[]` then it is treated as a repeated ID and we will generate
|
|
* an ID with the current index for the current nestedId.
|
|
* (e.g. "myParentId-foo[0]", "myParentId-foo[1]", etc.)
|
|
*/
|
|
elId: function (nestedId) {
|
|
if (nestedId == null) {
|
|
return this.id;
|
|
} else {
|
|
if (typeof nestedId === 'string' && repeatedRegExp.test(nestedId)) {
|
|
return nextRepeatedId(this.$__out, this.id, nestedId);
|
|
} else {
|
|
return this.id + '-' + nestedId;
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* Registers a DOM event for a nested HTML element associated with the
|
|
* component. This is only done for non-bubbling events that require
|
|
* direct event listeners to be added.
|
|
* @param {String} type The DOM event type ("mouseover", "mousemove", etc.)
|
|
* @param {String} targetMethod The name of the method to invoke on the scoped component
|
|
* @param {String} elId The DOM element ID of the DOM element that the event listener needs to be added too
|
|
*/
|
|
e: function(type, targetMethod, elId, extraArgs) {
|
|
if (!targetMethod) {
|
|
// The event handler method is allowed to be conditional. At render time if the target
|
|
// method is null then we do not attach any direct event listeners.
|
|
return;
|
|
}
|
|
|
|
var domEvents = this.$__domEvents;
|
|
this.$__domEvents = (domEvents || (this.$__domEvents = [])).concat([
|
|
type,
|
|
targetMethod,
|
|
elId,
|
|
extraArgs]);
|
|
},
|
|
/**
|
|
* Returns the next auto generated unique ID for a nested DOM element or nested DOM component
|
|
*/
|
|
$__nextId: function() {
|
|
return this.id ?
|
|
this.id + '-w' + (this.$__nextIdIndex++) :
|
|
nextComponentId(this.$__out);
|
|
},
|
|
|
|
d: function(handlerMethodName, extraArgs) {
|
|
return attachBubblingEvent(this, handlerMethodName, extraArgs);
|
|
}
|
|
};
|
|
|
|
ComponentDef.$__deserialize = function(o, types) {
|
|
var id = o[0];
|
|
var typeName = types[o[1]];
|
|
var input = o[2];
|
|
var extra = o[3];
|
|
|
|
var state = extra.s;
|
|
var componentProps = extra.w;
|
|
|
|
var component = typeName /* legacy */ && registry.$__createComponent(typeName, id);
|
|
|
|
if (extra.b) {
|
|
component.$__bubblingDomEvents = extra.b;
|
|
}
|
|
|
|
|
|
// Preview newly created component from being queued for update since we area
|
|
// just building it from the server info
|
|
component.$__updateQueued = true;
|
|
|
|
if (state) {
|
|
var undefinedPropNames = extra.u;
|
|
if (undefinedPropNames) {
|
|
for(var i=0; i<undefinedPropNames.length; i++) {
|
|
state[undefinedPropNames[i]] = undefined;
|
|
}
|
|
}
|
|
// We go through the setter here so that we convert the state object
|
|
// to an instance of `State`
|
|
component.state = state;
|
|
}
|
|
|
|
component.$__input = input;
|
|
|
|
if (componentProps) {
|
|
extend(component, componentProps);
|
|
}
|
|
|
|
return {
|
|
$__component: component,
|
|
$__roots: extra.r,
|
|
$__scope: extra.p,
|
|
$__domEvents: extra.d,
|
|
$__customEvents: extra.e
|
|
};
|
|
};
|
|
|
|
module.exports = ComponentDef; |