UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

622 lines (501 loc) • 22.2 kB
"use strict"; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && 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 safeApply(func, scope) { if (scope.$root.$$phase) { return func(scope); } else { return scope.$apply(function () { return func(scope); }); } }; /** * @name domcomponentoptions.bindingOptions * @publicName bindingOptions * @type object * @default {} */ var ComponentBuilder = Class.inherit({ ctor: function ctor(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 _addOptionsStringWatcher(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 _normalizeOptions(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 (typeUtils.type(value) === 'string') { that._ngOptions.bindingOptions[key] = { dataPath: value }; } }); } }, _initComponent: function _initComponent(scope) { this._component = new this._componentClass(this._$element, this._evalOptions(scope)); this._component._isHidden = true; this._handleDigestPhase(); }, _handleDigestPhase: function _handleDigestPhase() { var that = this, beginUpdate = function beginUpdate() { that._component.beginUpdate(); }, endUpdate = function endUpdate() { 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 _initComponentBindings() { var that = this, optionDependencies = {}; if (!that._ngOptions.bindingOptions) { return; } each(that._ngOptions.bindingOptions, function (optionPath, value) { var separatorIndex = optionPath.search(/\[|\./), optionForSubscribe = separatorIndex > -1 ? optionPath.substring(0, separatorIndex) : optionPath, prevWatchMethod, clearWatcher, valuePath = value.dataPath, deepWatch = true, forcePlainWatchMethod = false; if (value.deep !== undefined) { forcePlainWatchMethod = deepWatch = !!value.deep; } if (!optionDependencies[optionForSubscribe]) { optionDependencies[optionForSubscribe] = {}; } optionDependencies[optionForSubscribe][optionPath] = valuePath; var watchCallback = function watchCallback(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 updateWatcher() { 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 obtainOption() { 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 _optionsAreNested(optionPath1, optionPath2) { var parentSeparator = optionPath1[optionPath2.length]; return optionPath1.indexOf(optionPath2) === 0 && (parentSeparator === "." || parentSeparator === "["); }, _optionsAreLinked: function _optionsAreLinked(optionPath1, optionPath2) { if (optionPath1 === optionPath2) return true; return optionPath1.length > optionPath2.length ? this._optionsAreNested(optionPath1, optionPath2) : this._optionsAreNested(optionPath2, optionPath1); }, _compilerByTemplate: function _compilerByTemplate(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 _applyAsync(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 _getScopeItemsPath() { if (this._componentClass.subclassOf(CollectionWidget) && this._ngOptions.bindingOptions && this._ngOptions.bindingOptions.items) { return this._ngOptions.bindingOptions.items.dataPath; } }, _createScopeWithData: function _createScopeWithData(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 _synchronizeScopes(itemScope, parentPrefix, itemIndex) { if (this._itemAlias && _typeof(itemScope[this._itemAlias]) !== "object") { this._synchronizeScopeField({ parentScope: this._scope, childScope: itemScope, fieldPath: this._itemAlias, parentPrefix: parentPrefix, itemIndex: itemIndex }); } }, _synchronizeScopeField: function _synchronizeScopeField(args) { var parentScope = args.parentScope, childScope = args.childScope, fieldPath = args.fieldPath, parentPrefix = args.parentPrefix, itemIndex = args.itemIndex; var innerPathSuffix = fieldPath === this._itemAlias ? "" : "." + fieldPath, collectionField = itemIndex !== undefined, optionOuterBag = [parentPrefix], optionOuterPath; 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]); // TODO: test }, _evalOptions: function _evalOptions(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 wrappedAction() { 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 (widgetName === "button" || widgetName === "tabs" || widgetName === "dropDownMenu") { 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 ctor(options) { this._componentName = options.componentName; this._ngModel = options.ngModel; this._ngModelController = options.ngModelController; this.callBase.apply(this, arguments); }, _isNgModelRequired: function _isNgModelRequired() { return this._componentClass.subclassOf(Editor) && this._ngModel; }, _initComponentBindings: function _initComponentBindings() { this.callBase.apply(this, arguments); this._initNgModelBinding(); }, _initNgModelBinding: function _initNgModelBinding() { 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 _evalOptions() { 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 registerComponentDirective(name) { var priority = name !== "dxValidator" ? 1 : 10; ngModule.directive(name, ["$compile", "$parse", "dxDigestCallbacks", function ($compile, $parse, dxDigestCallbacks) { return { restrict: "A", require: "^?ngModel", priority: priority, compile: function compile($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; });