UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

511 lines (510 loc) • 22.2 kB
/** * 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 });