mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
188 lines
6.7 KiB
JavaScript
188 lines
6.7 KiB
JavaScript
var componentsUtil = require('./util');
|
|
var componentLookup = componentsUtil.$__componentLookup;
|
|
var emitLifecycleEvent = componentsUtil.$__emitLifecycleEvent;
|
|
var nextRepeatedId = require('./nextRepeatedId');
|
|
var repeatedRegExp = /\[\]$/;
|
|
var ComponentsContext = require('./ComponentsContext');
|
|
var registry = require('./registry');
|
|
|
|
var WIDGETS_BEGIN_ASYNC_ADDED_KEY = '$wa';
|
|
|
|
function resolveComponentRef(out, ref, scope) {
|
|
if (ref.charAt(0) === '#') {
|
|
return ref.substring(1);
|
|
} else {
|
|
var resolvedId;
|
|
|
|
if (repeatedRegExp.test(ref)) {
|
|
resolvedId = nextRepeatedId(out, scope, ref);
|
|
} else {
|
|
resolvedId = scope + '-' + ref;
|
|
}
|
|
|
|
return resolvedId;
|
|
}
|
|
}
|
|
|
|
function preserveComponentEls(existingComponent, out, componentsContext) {
|
|
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 });
|
|
|
|
componentsContext.$__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 = asyncOut.global.components;
|
|
var componentStack;
|
|
|
|
if (componentsContext && (componentStack = componentsContext.$__componentStack)) {
|
|
// 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 = new ComponentsContext(asyncOut, componentStack[componentStack.length-1]);
|
|
asyncOut.data.components = nestedComponentsContext;
|
|
}
|
|
asyncOut.data.$w = parentOut.data.$w;
|
|
}
|
|
|
|
|
|
|
|
function createRendererFunc(templateRenderFunc, componentProps, renderingLogic) {
|
|
if (typeof renderingLogic == 'function') {
|
|
var ctor = renderingLogic;
|
|
renderingLogic = renderingLogic.prototype;
|
|
renderingLogic.onCreate = renderingLogic.onCreate || ctor;
|
|
}
|
|
|
|
renderingLogic = renderingLogic || {};
|
|
var onInput = renderingLogic.onInput;
|
|
var typeName = componentProps.type;
|
|
var roots = componentProps.roots;
|
|
var assignedId = componentProps.id;
|
|
|
|
return function renderer(input, out) {
|
|
var outGlobal = out.global;
|
|
|
|
if (!out.isSync()) {
|
|
if (!outGlobal[WIDGETS_BEGIN_ASYNC_ADDED_KEY]) {
|
|
outGlobal[WIDGETS_BEGIN_ASYNC_ADDED_KEY] = true;
|
|
out.on('beginAsync', handleBeginAsync);
|
|
}
|
|
}
|
|
|
|
var component = outGlobal.$w;
|
|
var isRerender = component !== undefined;
|
|
var id = assignedId;
|
|
var isExisting;
|
|
var customEvents;
|
|
var scope;
|
|
|
|
if (component) {
|
|
id = component.id;
|
|
isExisting = true;
|
|
outGlobal.$w = null;
|
|
} else {
|
|
var componentArgs = input && input.$w || out.data.$w;
|
|
|
|
if (componentArgs) {
|
|
scope = componentArgs[0];
|
|
|
|
if (scope) {
|
|
scope = scope.id;
|
|
}
|
|
|
|
var ref = componentArgs[1];
|
|
if (ref != null) {
|
|
ref = ref.toString();
|
|
}
|
|
id = id || resolveComponentRef(out, ref, scope);
|
|
customEvents = componentArgs[2];
|
|
delete input.$w;
|
|
}
|
|
}
|
|
|
|
var componentsContext = ComponentsContext.$__getComponentsContext(out);
|
|
id = id || componentsContext.$__nextComponentId();
|
|
|
|
if (registry.$__isServer) {
|
|
component = registry.$__createComponent(renderingLogic, id, input, out, typeName);
|
|
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);
|
|
}
|
|
|
|
// 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 (!isExisting) {
|
|
emitLifecycleEvent(component, 'create', input, out);
|
|
}
|
|
|
|
input = component.$__setInput(input, onInput, out);
|
|
|
|
if (isExisting) {
|
|
if (!component.$__isDirty || !component.shouldUpdate(input, component.$__state)) {
|
|
preserveComponentEls(component, out, componentsContext);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
emitLifecycleEvent(component, 'render', out);
|
|
}
|
|
|
|
var componentDef = componentsContext.$__beginComponent(component);
|
|
componentDef.$__customEvents = customEvents;
|
|
componentDef.$__scope = scope;
|
|
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.$__rawState);
|
|
|
|
componentDef.$__end();
|
|
};
|
|
}
|
|
|
|
module.exports = createRendererFunc;
|
|
|
|
// exports used by the legacy renderer
|
|
createRendererFunc.$__resolveComponentRef = resolveComponentRef;
|
|
createRendererFunc.$__preserveComponentEls = preserveComponentEls;
|
|
createRendererFunc.$__handleBeginAsync = handleBeginAsync;
|