marko/components/renderer.js

210 lines
7.5 KiB
JavaScript

var componentsUtil = require('./util');
var componentLookup = componentsUtil.$__componentLookup;
var emitLifecycleEvent = componentsUtil.$__emitLifecycleEvent;
var ComponentsContext = require('./ComponentsContext');
var getComponentsContext = ComponentsContext.$__getComponentsContext;
var repeatedRegExp = /\[\]$/;
var registry = require('./registry');
var copyProps = require('raptor-util/copyProps');
var isServer = componentsUtil.$__isServer === true;
var COMPONENT_BEGIN_ASYNC_ADDED_KEY = '$wa';
function resolveComponentKey(globalComponentsContext, key, scope) {
if (key[0] == '#') {
return key.substring(1);
} else {
var resolvedId;
if (repeatedRegExp.test(key)) {
resolvedId = globalComponentsContext.$__nextRepeatedId(scope, key);
} else {
resolvedId = scope + '-' + key;
}
return resolvedId;
}
}
function preserveComponentEls(existingComponent, out, globalComponentsContext) {
var rootEls = existingComponent.$__getRootEls({});
for (var elId in rootEls) {
var el = rootEls[elId];
// We put a placeholder element in the output stream to ensure that the existing
// DOM node is matched up correctly when using morphdom.
out.element(el.tagName, { id: elId });
globalComponentsContext.$__preserveDOMNode(elId); // Mark the element as being preserved (for morphdom)
}
existingComponent.$__reset(); // The component is no longer dirty so reset internal flags
return true;
}
function handleBeginAsync(event) {
var parentOut = event.parentOut;
var asyncOut = event.out;
var componentsContext = parentOut.data.components;
if (componentsContext !== undefined) {
// All of the components in this async block should be
// initialized after the components in the parent. Therefore,
// we will create a new ComponentsContext for the nested
// async block and will create a new component stack where the current
// component in the parent block is the only component in the nested
// stack (to begin with). This will result in top-level components
// of the async block being added as children of the component in the
// parent block.
var nestedComponentsContext = componentsContext.$__createNestedComponentsContext(asyncOut);
asyncOut.data.components = nestedComponentsContext;
}
// Carry along the component arguments
asyncOut.$__componentArgs = parentOut.$__componentArgs;
}
function createRendererFunc(templateRenderFunc, componentProps, renderingLogic) {
renderingLogic = renderingLogic || {};
var onInput = renderingLogic.onInput;
var typeName = componentProps.type;
var roots = componentProps.roots;
var assignedId = componentProps.id;
var isSplit = componentProps.split === true;
var shouldApplySplitMixins = isSplit;
return function renderer(input, out) {
var outGlobal = out.global;
if (out.isSync() === false) {
if (!outGlobal[COMPONENT_BEGIN_ASYNC_ADDED_KEY]) {
outGlobal[COMPONENT_BEGIN_ASYNC_ADDED_KEY] = true;
out.on('beginAsync', handleBeginAsync);
}
}
var componentsContext = getComponentsContext(out);
var globalComponentsContext = componentsContext.$__globalContext;
var component = globalComponentsContext.$__rerenderComponent;
var isRerender = component !== undefined;
var id = assignedId;
var isExisting;
var customEvents;
var scope;
if (component) {
id = component.id;
isExisting = true;
globalComponentsContext.$__rerenderComponent = null;
} else {
var componentArgs = out.$__componentArgs;
if (componentArgs) {
out.$__componentArgs = null;
scope = componentArgs[0];
if (scope) {
scope = scope.id;
}
var key = componentArgs[1];
if (key != null) {
key = key.toString();
}
id = id || resolveComponentKey(globalComponentsContext, key, scope);
customEvents = componentArgs[2];
}
}
id = id || componentsContext.$__nextComponentId();
if (isServer) {
component = registry.$__createComponent(
renderingLogic,
id,
input,
out,
typeName,
customEvents,
scope);
input = component.$__updatedInput;
component.$__updatedInput = undefined; // We don't want $__updatedInput to be serialized to the browser
} else {
if (!component) {
if (isRerender) {
// Look in in the DOM to see if a component with the same ID and type already exists.
component = componentLookup[id];
if (component && component.$__type !== typeName) {
component = undefined;
}
}
if (component) {
isExisting = true;
} else {
isExisting = false;
// We need to create a new instance of the component
component = registry.$__createComponent(typeName, id);
if (shouldApplySplitMixins === true) {
shouldApplySplitMixins = false;
var renderingLogicProps = typeof renderingLogic == 'function' ?
renderingLogic.prototype :
renderingLogic;
copyProps(renderingLogicProps, component.constructor.prototype);
}
}
// Set this flag to prevent the component from being queued for update
// based on the new input. The component is about to be rerendered
// so we don't want to queue it up as a result of calling `setInput()`
component.$__updateQueued = true;
if (customEvents !== undefined) {
component.$__setCustomEvents(customEvents, scope);
}
if (isExisting === false) {
emitLifecycleEvent(component, 'create', input, out);
}
input = component.$__setInput(input, onInput, out);
if (isExisting === true) {
if (component.$__isDirty === false || component.shouldUpdate(input, component.$__state) === false) {
preserveComponentEls(component, out, globalComponentsContext);
return;
}
}
}
component.$__global = outGlobal;
emitLifecycleEvent(component, 'render', out);
}
var componentDef = componentsContext.$__beginComponent(component, isSplit);
componentDef.$__roots = roots;
componentDef.$__isExisting = isExisting;
// Render the template associated with the component using the final template
// data that we constructed
templateRenderFunc(input, out, componentDef, component, component.$__rawState);
componentDef.$__end();
};
}
module.exports = createRendererFunc;
// exports used by the legacy renderer
createRendererFunc.$__resolveComponentKey = resolveComponentKey;
createRendererFunc.$__preserveComponentEls = preserveComponentEls;
createRendererFunc.$__handleBeginAsync = handleBeginAsync;