raptor
Version:
RaptorJS provides an AMD module loader that works in Node, Rhino and the web browser. It also includes various sub-modules to support building optimized web applications.
392 lines (329 loc) • 14.2 kB
JavaScript
/*
* 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.
*/
/**
* @extension Browser
*
*/
define.extend('raptor/widgets', function(require, widgets) {
"use strict";
var logger = require('raptor/logging').logger('raptor/widgets'),
widgetsById = {},
raptor = require('raptor'),
isArray = Array.isArray,
createError = raptor.createError,
Widget = require('raptor/widgets/Widget'),
_convertEvents = function(events) {
var convertedEvents = {};
raptor.forEach(events, function(event) {
convertedEvents[event[0]] = {
target: event[1],
props: event[2]
};
}, this);
return convertedEvents;
};
/**
* The Documentation groups up all widgets rendered in the same template documentat.
*
* @class
* @anonymous
*
*/
var Document = function() {
};
/**
*
*/
Document.prototype = {
_remove: function(widget, id) {
var existing = this[id];
if (isArray(existing)) {
this[id] = existing.filter(function(cur) {
return cur !== widget;
});
}
else {
delete this[id];
}
},
/**
*
* @param widget
* @param id
*/
_add: function(widget, id, isTargetArray) {
var existing = this[id];
if (!existing) {
this[id] = isTargetArray ? [widget] : widget;
}
else {
if (isArray(existing)) {
existing.push(widget);
}
else {
this[id] = [existing, widget];
}
}
},
/**
*
* @param id
* @returns
*/
getWidget: function(id) {
return this[id];
},
/**
*
* @param id
* @returns {Boolean}
*/
getWidgets: function(id) {
var widgets = this[id];
return widgets ?
(isArray(widgets) ? widgets : [widgets]) :
[];
}
};
/**
* Creates and registers a widget without initializing it.
*
* @param {String} type The class type for the module (e.g. "some/namespace/MyWidget")
* @param {String} id The ID for the widget. This should typically be the ID of the widget's root DOM element
* @param {String} assignedId The assigned ID by the widget that this widget is scoped within
* @param {Object} config A user-provided configuration object for the widget being initialized
* @param {String} scope The widget ID of the widget that the new widget is scoped within
* @param {Object} events A mapping of widget events to pubsub messages/topics
* @param {Boolean} bubbleErrorsDisabled If true, then each widget initialization error will be caught and
* logged and other widgets will continue to be initialized. If false,
* then errors will bubble up to the calling code and any subsequent widgets
* will not be initialized.
*
* @return {Function} A function that can be used to complete the initialization of the widget
* @private
*/
var _registerWidget = function(type, id, assignedId, config, scope, events, parent, bubbleErrorsDisabled) {
if (!require.exists(type)) {
throw createError(new Error('Unable to initialize widget of type "' + type + '". The class for the widget was not found.'));
}
var widget, // This will be the newly created widget instance of the provided type
OriginalWidgetClass = require(type); // The user-provided constructor function
logger.debug('Creating widget of type "' + type + '" (' + id + ')');
if (OriginalWidgetClass.initWidget) { //Check if the Widget has an "initWidget" function that will do the initialization
/*
* Update the config with the information that
* the user "initWidget" function by need:
*/
config.elId = id;
config.events = events;
widget = OriginalWidgetClass; //Use the provided object as the widget
if (!OriginalWidgetClass.onReady) { //Add an onReady function that can be used to initialize the widget onReady
OriginalWidgetClass.onReady = widgets.onReady;
}
}
else {
/*
* We have to create a temporary constructor function because we want
* to delay the invocation of the user's constructor function until
* we have had a chance to add all of the required special
* properties (_id, _assignedId, _events, etc.)
*/
var WidgetClass = function() {},
proto; //This will be a reference to the original prorotype
WidgetClass.prototype = proto = OriginalWidgetClass.prototype;
widget = new WidgetClass();
Widget.makeWidget(widget, proto); //Will apply Widget mixins if the widget is not already a widget
// Register events that allow widgets support:
widget.registerMessages(['beforeDestroy', 'destroy'], false);
// Check if the user's widget has an additional events defined
var allowedEvents = proto.events || OriginalWidgetClass.events;
if (allowedEvents) {
widget.registerMessages(allowedEvents, false);
}
// Add required specified properties required by the Widget mixin methods
if (id) {
widget._id = id;
widget.el = widget.getEl();
}
if (!OriginalWidgetClass.getName) {
OriginalWidgetClass.getName = function() {
return type;
};
}
proto.constructor = OriginalWidgetClass;
if (Widget.legacy) {
widget._parentWidget = parent;
}
if (events) {
widget._events = _convertEvents(events);
}
widget.widgets = new Document(); //This widget might have other widgets scoped within it
widgetsById[id] = widget; // Register the widget in a global lookup
if (assignedId && scope) { // If the widget is scoped within another widget then register the widget in the scope
var isTargetArray;
if (assignedId.endsWith('[]')) { // When adding the widgets to a collection, an array can be forced by using a [] suffix for the assigned widget ID
assignedId = assignedId.slice(0, -2);
isTargetArray = true;
}
widget._assignedId = assignedId;
widget._scope = scope;
var containingWidget = widgetsById[scope];
if (!containingWidget) {
throw createError(new Error('Parent scope not found: ' + scope));
}
containingWidget.widgets._add(
widget,
assignedId,
isTargetArray);
if (Widget.legacy) {
containingWidget[assignedId] = widget;
}
}
}
return {
widget : widget,
init : function() {
var _doInitWidget = function() {
try
{
if (widget.initWidget) {
widget.initWidget(config);
}
else {
OriginalWidgetClass.call(widget, config);
}
}
catch(e) {
var message = 'Unable to initialize widget of type "' + type + "'. Exception: " + e;
// NOTE:
// For widgets rendered on the server we disable errors from bubbling to allow the page to possibly function
// in a partial state even if some of the widgets fail to initialize.
// For widgets rendered on the client we enable bubbling to make sure calling code is aware of the error.
if (bubbleErrorsDisabled) {
logger.error(message, e);
}
else {
throw e;
}
}
};
if (widget.initBeforeOnDomReady === true) {
_doInitWidget();
}
else {
widget.onReady(_doInitWidget);
}
}
};
};
require('raptor/pubsub').subscribe({
'dom/beforeRemove': function(eventArgs) {
/*jshint strict:false */
var el = eventArgs.el;
var widget = widgets.get(el.id);
if (widget) {
widget.destroy({
removeNode: false,
recursive: true
});
}
},
'raptor/renderer/renderedToDOM': function(eventArgs) {
/*jshint strict:false */
var context = eventArgs.context,
widgetsContext = widgets.getWidgetsContext(context);
widgetsContext.initWidgets();
}
});
return {
initWidget: function(widgetDef) {
var result = _registerWidget(
widgetDef.type,
widgetDef.id,
widgetDef.assignedId,
widgetDef.config,
widgetDef.scope ? widgetDef.scope.id : null,
widgetDef.events);
widgetDef.widget = result.widget;
if (widgetDef.children.length) {
widgetDef.children.forEach(this.initWidget, this);
}
// Complete the initialization of this widget after all of the children have been initialized
result.init();
},
/**
*
* @param {...widgets} widgets An array of widget definitions
* @returns {void}
*/
_serverInit: function(widgetDefs) {
var _initWidgets = function(widgetDefs, parent) {
if (!widgetDefs) {
return;
}
var i=0,
len = widgetDefs.length;
for (; i<len; i++) {
// Each widget def serialized from the server is encoded into a minimal
// array object that we need to decipher...
var widgetDef = widgetDefs[i],
type = widgetDef[0],
id = widgetDef[1],
config = widgetDef[2] || {},
scope = widgetDef[3],
assignedId = widgetDef[4],
events = widgetDef[5] || {},
children = widgetDef.slice(6);
if (scope === 0) {
scope = undefined;
}
if (assignedId === 0) {
assignedId = undefined;
}
if (config === 0) {
config = undefined;
}
// First register the widget and get back a function to complete the initialization.
// The widget should not be initialized until all of its children have first been
// initialized.
var result = _registerWidget(type, id, assignedId, config, scope, events, parent, 1);
// Initialize all of the children
if (children && children.length) {
_initWidgets(children, result.widget);
}
// Now finish the initialization of the current widget now that the children have been initialized
result.init();
}
};
_initWidgets(widgetDefs);
},
/**
* Gets a widget by widget ID
* @param {string} id The ID of the widget
* @returns {object} The widget instance
*/
get: function(id) {
return widgetsById[id];
},
_remove: function(id) {
delete widgetsById[id];
}
};
});
$rwidgets = function() {
/*jshint strict:false */
require('raptor/widgets')._serverInit(require('raptor').arrayFromArguments(arguments));
};