UNPKG

marko-widgets

Version:

Module to support binding of behavior to rendered UI components rendered on the server or client

180 lines (151 loc) 7.51 kB
/* * Copyright 2011 eBay Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var marko = require('marko'); var raptorRenderer = require('raptor-renderer'); var extend = require('raptor-util/extend'); module.exports = function defineRenderer(def) { var template = def.template; var getInitialProps = def.getInitialProps; var getTemplateData = def.getTemplateData; var getInitialState = def.getInitialState; var getWidgetConfig = def.getWidgetConfig; var getInitialBody = def.getInitialBody; var extendWidget = def.extendWidget; var renderer = def.renderer; var loadedTemplate; if (!renderer) { // Create a renderer function that takes care of translating // the input properties to a view state. Also, this renderer // takes care of re-using existing widgets. renderer = function renderer(input, out) { var global = out.global; var newProps = input; if (!newProps) { // Make sure we always have a non-null input object newProps = {}; } if (!loadedTemplate) { // Lazily load the template on first render to avoid potential problems // with circular dependencies loadedTemplate = template.render ? template : marko.load(template); } var widgetState; if (getInitialState) { // This is a state-ful widget. If this is a rerender then the "input" // will be the new state. If we have state then we should use the input // as the widget state and skip the steps of converting the input // to a widget state. if (global.__rerenderWidget && global.__rerenderState) { var isFirstWidget = !global.__firstWidgetFound; if (!isFirstWidget || extendWidget) { // We are the not first top-level widget or we are being extended // so use the merged rerender state as defaults for the input // and use that to rebuild the new state. This is kind of a hack // but extending widgets requires this hack since there is no // single state since the widget state is split between the // widget being extended and the widget doing the extending. for (var k in global.__rerenderState) { if (global.__rerenderState.hasOwnProperty(k) && !input.hasOwnProperty(k)) { newProps[k] = global.__rerenderState[k]; } } } else { // We are the first widget and we are not being extended // and we are not extending so use the input as the state widgetState = input; newProps = null; } } } if (!widgetState) { // If we do not have state then we need to go through the process // of converting the input to a widget state, or simply normalizing // the input using getInitialProps if (getInitialProps) { // This optional method is used to normalize input state newProps = getInitialProps(newProps, out) || {}; } if (getInitialState) { // This optional method is used to derive the widget state // from the input properties widgetState = getInitialState(newProps, out); } } global.__firstWidgetFound = true; // Use getTemplateData(state, props, out) to get the template // data. If that method is not provided then just use the // the state (if provided) or the input data. var templateData = getTemplateData ? getTemplateData(widgetState, newProps, out) : widgetState || newProps; if (templateData) { // We are going to be modifying the template data so we need to // make a shallow clone of the object so that we don't // mutate user provided data. templateData = extend({}, templateData); } else { // We always should have some template data templateData = {}; } if (widgetState) { // If we have widget state then pass it to the template // so that it is available to the widget tag templateData.widgetState = widgetState; } if (newProps) { // If we have widget props then pass it to the template // so that it is available to the widget tag. The widget props // are only needed so that we can call widget.shouldUpdate(newProps) templateData.widgetProps = newProps; if (getInitialBody) { // If we have widget a widget body then pass it to the template // so that it is available to the widget tag and can be inserted // at the w-body marker templateData.widgetBody = getInitialBody(newProps, out); } else { // Default to using the nested content as the widget body // getInitialBody was not implemented templateData.widgetBody = newProps.renderBody; } if (getWidgetConfig) { // If getWidgetConfig() was implemented then use that to // get the widget config. The widget config will be passed // to the widget constructor. If rendered on the server the // widget config will be serialized to a JSON-like data // structure and stored in a "data-w-config" attribute. templateData.widgetConfig = getWidgetConfig(newProps, out); } } // Render the template associated with the component using the final template // data that we constructed loadedTemplate.render(templateData, out); }; } renderer.render = function(data, callback) { if(!callback) { require('./deprecate').warn( 'Calling `render` synchronously is deprecated. '+ 'Use `renderSync` instead.' ); return raptorRenderer.render(renderer, data); } raptorRenderer.render(renderer, data, callback); }; renderer.renderSync = function(data) { return raptorRenderer.render(renderer, data); }; return renderer; };