devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
511 lines (510 loc) • 22.2 kB
JavaScript
/**
* DevExtreme (integration/angular/component_registrator.js)
* Version: 18.1.3
* Build date: Tue May 15 2018
*
* Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var _typeof = "function" === typeof Symbol && "symbol" === typeof Symbol.iterator ? function(obj) {
return typeof obj
} : function(obj) {
return obj && "function" === typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj
};
var $ = require("../../core/renderer"),
eventsEngine = require("../../events/core/events_engine"),
Config = require("../../core/config"),
registerComponentCallbacks = require("../../core/component_registrator_callbacks"),
Class = require("../../core/class"),
Callbacks = require("../../core/utils/callbacks"),
typeUtils = require("../../core/utils/type"),
each = require("../../core/utils/iterator").each,
inArray = require("../../core/utils/array").inArray,
Locker = require("../../core/utils/locker"),
Widget = require("../../ui/widget/ui.widget"),
Editor = require("../../ui/editor/editor"),
NgTemplate = require("./template"),
ngModule = require("./module"),
CollectionWidget = require("../../ui/collection/ui.collection_widget.edit"),
compileSetter = require("../../core/utils/data").compileSetter,
compileGetter = require("../../core/utils/data").compileGetter,
extendFromObject = require("../../core/utils/extend").extendFromObject,
inflector = require("../../core/utils/inflector"),
errors = require("../../core/errors");
var ITEM_ALIAS_ATTRIBUTE_NAME = "dxItemAlias",
SKIP_APPLY_ACTION_CATEGORIES = ["rendering"],
NG_MODEL_OPTION = "value";
var safeApply = function(func, scope) {
if (scope.$root.$$phase) {
return func(scope)
} else {
return scope.$apply(function() {
return func(scope)
})
}
};
var ComponentBuilder = Class.inherit({
ctor: function(options) {
this._componentDisposing = Callbacks();
this._optionChangedCallbacks = Callbacks();
this._ngLocker = new Locker;
this._scope = options.scope;
this._$element = options.$element;
this._$templates = options.$templates;
this._componentClass = options.componentClass;
this._parse = options.parse;
this._compile = options.compile;
this._itemAlias = options.itemAlias;
this._transcludeFn = options.transcludeFn;
this._digestCallbacks = options.dxDigestCallbacks;
this._normalizeOptions(options.ngOptions);
this._initComponentBindings();
this._initComponent(this._scope);
if (!options.ngOptions) {
this._addOptionsStringWatcher(options.ngOptionsString)
}
},
_addOptionsStringWatcher: function(optionsString) {
var that = this;
var clearOptionsStringWatcher = that._scope.$watch(optionsString, function(newOptions) {
if (!newOptions) {
return
}
clearOptionsStringWatcher();
that._normalizeOptions(newOptions);
that._initComponentBindings();
that._component.option(that._evalOptions(that._scope))
});
that._componentDisposing.add(clearOptionsStringWatcher)
},
_normalizeOptions: function(options) {
var that = this;
that._ngOptions = extendFromObject({}, options);
if (!options) {
return
}
if (!options.hasOwnProperty("bindingOptions") && options.bindingOptions) {
that._ngOptions.bindingOptions = options.bindingOptions
}
if (options.bindingOptions) {
each(options.bindingOptions, function(key, value) {
if ("string" === typeUtils.type(value)) {
that._ngOptions.bindingOptions[key] = {
dataPath: value
}
}
})
}
},
_initComponent: function(scope) {
this._component = new this._componentClass(this._$element, this._evalOptions(scope));
this._component._isHidden = true;
this._handleDigestPhase()
},
_handleDigestPhase: function() {
var that = this,
beginUpdate = function() {
that._component.beginUpdate()
},
endUpdate = function() {
that._component.endUpdate()
};
that._digestCallbacks.begin.add(beginUpdate);
that._digestCallbacks.end.add(endUpdate);
that._componentDisposing.add(function() {
that._digestCallbacks.begin.remove(beginUpdate);
that._digestCallbacks.end.remove(endUpdate)
})
},
_initComponentBindings: function() {
var that = this,
optionDependencies = {};
if (!that._ngOptions.bindingOptions) {
return
}
each(that._ngOptions.bindingOptions, function(optionPath, value) {
var prevWatchMethod, clearWatcher, separatorIndex = optionPath.search(/\[|\./),
optionForSubscribe = separatorIndex > -1 ? optionPath.substring(0, separatorIndex) : optionPath,
valuePath = value.dataPath,
deepWatch = true,
forcePlainWatchMethod = false;
if (void 0 !== value.deep) {
forcePlainWatchMethod = deepWatch = !!value.deep
}
if (!optionDependencies[optionForSubscribe]) {
optionDependencies[optionForSubscribe] = {}
}
optionDependencies[optionForSubscribe][optionPath] = valuePath;
var watchCallback = function(newValue, oldValue) {
if (that._ngLocker.locked(optionPath)) {
return
}
that._ngLocker.obtain(optionPath);
that._component.option(optionPath, newValue);
updateWatcher();
if (that._component._optionValuesEqual(optionPath, oldValue, newValue) && that._ngLocker.locked(optionPath)) {
that._ngLocker.release(optionPath)
}
};
var updateWatcher = function() {
var watchMethod = Array.isArray(that._scope.$eval(valuePath)) && !forcePlainWatchMethod ? "$watchCollection" : "$watch";
if (prevWatchMethod !== watchMethod) {
if (clearWatcher) {
clearWatcher()
}
clearWatcher = that._scope[watchMethod](valuePath, watchCallback, deepWatch);
prevWatchMethod = watchMethod
}
};
updateWatcher();
that._componentDisposing.add(clearWatcher)
});
that._optionChangedCallbacks.add(function(args) {
var optionName = args.name,
fullName = args.fullName,
component = args.component;
if (that._ngLocker.locked(fullName)) {
that._ngLocker.release(fullName);
return
}
if (!optionDependencies || !optionDependencies[optionName]) {
return
}
var isActivePhase = that._scope.$root.$$phase;
var obtainOption = function() {
that._ngLocker.obtain(fullName)
};
if (isActivePhase) {
that._digestCallbacks.begin.add(obtainOption)
} else {
obtainOption()
}
safeApply(function() {
each(optionDependencies[optionName], function(optionPath, valuePath) {
if (!that._optionsAreLinked(fullName, optionPath)) {
return
}
var value = component.option(optionPath);
that._parse(valuePath).assign(that._scope, value);
var scopeValue = that._parse(valuePath)(that._scope);
if (scopeValue !== value) {
args.component.option(optionPath, scopeValue)
}
})
}, that._scope);
var releaseOption = function releaseOption() {
if (that._ngLocker.locked(fullName)) {
that._ngLocker.release(fullName)
}
that._digestCallbacks.begin.remove(obtainOption);
that._digestCallbacks.end.remove(releaseOption)
};
if (isActivePhase) {
that._digestCallbacks.end.addPrioritized(releaseOption)
} else {
releaseOption()
}
})
},
_optionsAreNested: function(optionPath1, optionPath2) {
var parentSeparator = optionPath1[optionPath2.length];
return 0 === optionPath1.indexOf(optionPath2) && ("." === parentSeparator || "[" === parentSeparator)
},
_optionsAreLinked: function(optionPath1, optionPath2) {
if (optionPath1 === optionPath2) {
return true
}
return optionPath1.length > optionPath2.length ? this._optionsAreNested(optionPath1, optionPath2) : this._optionsAreNested(optionPath2, optionPath1)
},
_compilerByTemplate: function(template) {
var that = this,
scopeItemsPath = this._getScopeItemsPath();
return function(options) {
var $resultMarkup = $(template).clone(),
dataIsScope = options.model && options.model.constructor === that._scope.$root.constructor,
templateScope = dataIsScope ? options.model : options.noModel ? that._scope : that._createScopeWithData(options);
if (scopeItemsPath) {
that._synchronizeScopes(templateScope, scopeItemsPath, options.index)
}
$resultMarkup.appendTo(options.container);
if (!options.noModel) {
eventsEngine.on($resultMarkup, "$destroy", function() {
var destroyAlreadyCalled = !templateScope.$parent;
if (destroyAlreadyCalled) {
return
}
templateScope.$destroy()
})
}
that._applyAsync(that._compile($resultMarkup, that._transcludeFn), templateScope);
return $resultMarkup
}
},
_applyAsync: function(func, scope) {
var that = this;
func(scope);
if (!scope.$root.$$phase) {
if (!that._renderingTimer) {
that._renderingTimer = setTimeout(function() {
scope.$apply();
that._renderingTimer = null
})
}
that._componentDisposing.add(function() {
clearTimeout(that._renderingTimer)
})
}
},
_getScopeItemsPath: function() {
if (this._componentClass.subclassOf(CollectionWidget) && this._ngOptions.bindingOptions && this._ngOptions.bindingOptions.items) {
return this._ngOptions.bindingOptions.items.dataPath
}
},
_createScopeWithData: function(options) {
var newScope = this._scope.$new();
if (this._itemAlias) {
newScope[this._itemAlias] = options.model
}
if (typeUtils.isDefined(options.index)) {
newScope.$index = options.index
}
return newScope
},
_synchronizeScopes: function(itemScope, parentPrefix, itemIndex) {
if (this._itemAlias && "object" !== _typeof(itemScope[this._itemAlias])) {
this._synchronizeScopeField({
parentScope: this._scope,
childScope: itemScope,
fieldPath: this._itemAlias,
parentPrefix: parentPrefix,
itemIndex: itemIndex
})
}
},
_synchronizeScopeField: function(args) {
var parentScope = args.parentScope,
childScope = args.childScope,
fieldPath = args.fieldPath,
parentPrefix = args.parentPrefix,
itemIndex = args.itemIndex;
var optionOuterPath, innerPathSuffix = fieldPath === this._itemAlias ? "" : "." + fieldPath,
collectionField = void 0 !== itemIndex,
optionOuterBag = [parentPrefix];
if (collectionField) {
if (!typeUtils.isNumeric(itemIndex)) {
return
}
optionOuterBag.push("[", itemIndex, "]")
}
optionOuterBag.push(innerPathSuffix);
optionOuterPath = optionOuterBag.join("");
var clearParentWatcher = parentScope.$watch(optionOuterPath, function(newValue, oldValue) {
if (newValue !== oldValue) {
compileSetter(fieldPath)(childScope, newValue)
}
});
var clearItemWatcher = childScope.$watch(fieldPath, function(newValue, oldValue) {
if (newValue !== oldValue) {
if (collectionField && !compileGetter(parentPrefix)(parentScope)[itemIndex]) {
clearItemWatcher();
return
}
compileSetter(optionOuterPath)(parentScope, newValue)
}
});
this._componentDisposing.add([clearParentWatcher, clearItemWatcher])
},
_evalOptions: function(scope) {
var result = extendFromObject({}, this._ngOptions);
delete result.bindingOptions;
if (this._ngOptions.bindingOptions) {
each(this._ngOptions.bindingOptions, function(key, value) {
result[key] = scope.$eval(value.dataPath)
})
}
result._optionChangedCallbacks = this._optionChangedCallbacks;
result._disposingCallbacks = this._componentDisposing;
result.onActionCreated = function(component, action, config) {
if (config && inArray(config.category, SKIP_APPLY_ACTION_CATEGORIES) > -1) {
return action
}
var wrappedAction = function() {
var that = this,
args = arguments;
if (!scope || !scope.$root || scope.$root.$$phase) {
return action.apply(that, args)
}
return safeApply(function() {
return action.apply(that, args)
}, scope)
};
return wrappedAction
};
result.beforeActionExecute = result.onActionCreated;
result.nestedComponentOptions = function(component) {
return {
templatesRenderAsynchronously: component.option("templatesRenderAsynchronously"),
forceApplyBindings: component.option("forceApplyBindings"),
modelByElement: component.option("modelByElement"),
onActionCreated: component.option("onActionCreated"),
beforeActionExecute: component.option("beforeActionExecute"),
nestedComponentOptions: component.option("nestedComponentOptions")
}
};
result.templatesRenderAsynchronously = true;
if (Config().wrapActionsBeforeExecute) {
result.forceApplyBindings = function() {
safeApply(function() {}, scope)
}
}
result.integrationOptions = {
createTemplate: function(element) {
return new NgTemplate(element, this._compilerByTemplate.bind(this))
}.bind(this),
watchMethod: function(fn, callback, options) {
options = options || {};
var immediateValue;
var skipCallback = options.skipImmediate;
var disposeWatcher = scope.$watch(function() {
var value = fn();
if (value instanceof Date) {
value = value.valueOf()
}
return value
}, function(newValue) {
var isSameValue = immediateValue === newValue;
if (!skipCallback && (!isSameValue || isSameValue && options.deep)) {
callback(newValue)
}
skipCallback = false
}, options.deep);
if (!skipCallback) {
immediateValue = fn();
callback(immediateValue)
}
if (Config().wrapActionsBeforeExecute) {
this._applyAsync(function() {}, scope)
}
return disposeWatcher
}.bind(this),
templates: {
"dx-polymorph-widget": {
render: function(options) {
var widgetName = options.model.widget;
if (!widgetName) {
return
}
if ("button" === widgetName || "tabs" === widgetName || "dropDownMenu" === widgetName) {
var deprecatedName = widgetName;
widgetName = inflector.camelize("dx-" + widgetName);
errors.log("W0001", "dxToolbar - 'widget' item field", deprecatedName, "16.1", "Use: '" + widgetName + "' instead")
}
var markup = $("<div>").attr(inflector.dasherize(widgetName), "options").get(0);
var newScope = this._scope.$new();
newScope.options = options.model.options;
options.container.append(markup);
this._compile(markup)(newScope)
}.bind(this)
}
}
};
result.modelByElement = function() {
return scope
};
return result
}
});
ComponentBuilder = ComponentBuilder.inherit({
ctor: function(options) {
this._componentName = options.componentName;
this._ngModel = options.ngModel;
this._ngModelController = options.ngModelController;
this.callBase.apply(this, arguments)
},
_isNgModelRequired: function() {
return this._componentClass.subclassOf(Editor) && this._ngModel
},
_initComponentBindings: function() {
this.callBase.apply(this, arguments);
this._initNgModelBinding()
},
_initNgModelBinding: function() {
if (!this._isNgModelRequired()) {
return
}
var that = this;
var clearNgModelWatcher = this._scope.$watch(this._ngModel, function(newValue, oldValue) {
if (that._ngLocker.locked(NG_MODEL_OPTION)) {
return
}
if (newValue === oldValue) {
return
}
that._component.option(NG_MODEL_OPTION, newValue)
});
that._optionChangedCallbacks.add(function(args) {
that._ngLocker.obtain(NG_MODEL_OPTION);
try {
if (args.name !== NG_MODEL_OPTION) {
return
}
that._ngModelController.$setViewValue(args.value)
} finally {
if (that._ngLocker.locked(NG_MODEL_OPTION)) {
that._ngLocker.release(NG_MODEL_OPTION)
}
}
});
this._componentDisposing.add(clearNgModelWatcher)
},
_evalOptions: function() {
if (!this._isNgModelRequired()) {
return this.callBase.apply(this, arguments)
}
var result = this.callBase.apply(this, arguments);
result[NG_MODEL_OPTION] = this._parse(this._ngModel)(this._scope);
return result
}
});
var registeredComponents = {};
var registerComponentDirective = function(name) {
var priority = "dxValidator" !== name ? 1 : 10;
ngModule.directive(name, ["$compile", "$parse", "dxDigestCallbacks", function($compile, $parse, dxDigestCallbacks) {
return {
restrict: "A",
require: "^?ngModel",
priority: priority,
compile: function($element) {
var componentClass = registeredComponents[name],
$content = componentClass.subclassOf(Widget) ? $element.contents().detach() : null;
return function(scope, $element, attrs, ngModelController, transcludeFn) {
$element.append($content);
safeApply(function() {
new ComponentBuilder({
componentClass: componentClass,
componentName: name,
compile: $compile,
parse: $parse,
$element: $element,
scope: scope,
ngOptionsString: attrs[name],
ngOptions: attrs[name] ? scope.$eval(attrs[name]) : {},
ngModel: attrs.ngModel,
ngModelController: ngModelController,
transcludeFn: transcludeFn,
itemAlias: attrs[ITEM_ALIAS_ATTRIBUTE_NAME],
dxDigestCallbacks: dxDigestCallbacks
})
}, scope)
}
}
}
}])
};
registerComponentCallbacks.add(function(name, componentClass) {
if (!registeredComponents[name]) {
registerComponentDirective(name)
}
registeredComponents[name] = componentClass
});