devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
511 lines (509 loc) • 24.1 kB
JavaScript
/**
* DevExtreme (cjs/integration/angular/component_registrator.js)
* Version: 23.2.6
* Build date: Wed May 01 2024
*
* Copyright (c) 2012 - 2024 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var _renderer = _interopRequireDefault(require("../../core/renderer"));
var _angular = _interopRequireDefault(require("angular"));
var _events_engine = _interopRequireDefault(require("../../events/core/events_engine"));
var _config = _interopRequireDefault(require("../../core/config"));
var _component_registrator_callbacks = _interopRequireDefault(require("../../core/component_registrator_callbacks"));
var _class = _interopRequireDefault(require("../../core/class"));
var _callbacks = _interopRequireDefault(require("../../core/utils/callbacks"));
var _type = require("../../core/utils/type");
var _iterator = require("../../core/utils/iterator");
var _locker = _interopRequireDefault(require("../../core/utils/locker"));
var _editor = _interopRequireDefault(require("../../ui/editor/editor"));
var _template = require("./template");
var _module = _interopRequireDefault(require("./module"));
var _uiCollection_widget = _interopRequireDefault(require("../../ui/collection/ui.collection_widget.edit"));
var _data = require("../../core/utils/data");
var _extend = require("../../core/utils/extend");
var _comparator = require("../../core/utils/comparator");
var _inflector = require("../../core/utils/inflector");
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
}
}
const ITEM_ALIAS_ATTRIBUTE_NAME = "dxItemAlias";
const SKIP_APPLY_ACTION_CATEGORY = "rendering";
const NG_MODEL_OPTION = "value";
if (_angular.default) {
const safeApply = (func, scope) => {
if (scope.$root.$$phase) {
return func(scope)
} else {
return scope.$apply(() => func(scope))
}
};
const getClassMethod = (initClass, methodName) => {
const hasParentProperty = Object.prototype.hasOwnProperty.bind(initClass)("parent");
const isES6Class = !hasParentProperty && initClass.parent;
if (isES6Class) {
const baseClass = Object.getPrototypeOf(initClass);
return baseClass.prototype[methodName] ? () => baseClass.prototype[methodName]() : getClassMethod(baseClass, methodName)
} else {
const method = initClass.parent.prototype[methodName];
if (method) {
return () => method()
}
if (!method || !initClass.parent.subclassOf) {
return () => {}
}
return getClassMethod(initClass.parent, methodName)
}
};
let ComponentBuilder = _class.default.inherit({
ctor(options) {
this._componentDisposing = (0, _callbacks.default)();
this._optionChangedCallbacks = (0, _callbacks.default)();
this._ngLocker = new _locker.default;
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(optionsString) {
const clearOptionsStringWatcher = this._scope.$watch(optionsString, newOptions => {
if (!newOptions) {
return
}
clearOptionsStringWatcher();
this._normalizeOptions(newOptions);
this._initComponentBindings();
this._component.option(this._evalOptions(this._scope))
});
this._componentDisposing.add(clearOptionsStringWatcher)
},
_normalizeOptions(options) {
this._ngOptions = (0, _extend.extendFromObject)({}, options);
if (!options) {
return
}
if (!Object.prototype.hasOwnProperty.call(options, "bindingOptions") && options.bindingOptions) {
this._ngOptions.bindingOptions = options.bindingOptions
}
if (options.bindingOptions) {
(0, _iterator.each)(options.bindingOptions, (key, value) => {
if ("string" === (0, _type.type)(value)) {
this._ngOptions.bindingOptions[key] = {
dataPath: value
}
}
})
}
},
_initComponent(scope) {
this._component = new this._componentClass(this._$element, this._evalOptions(scope));
this._component._isHidden = true;
this._handleDigestPhase()
},
_handleDigestPhase() {
const beginUpdate = () => {
this._component.beginUpdate()
};
const endUpdate = () => {
this._component.endUpdate()
};
this._digestCallbacks.begin.add(beginUpdate);
this._digestCallbacks.end.add(endUpdate);
this._componentDisposing.add(() => {
this._digestCallbacks.begin.remove(beginUpdate);
this._digestCallbacks.end.remove(endUpdate)
})
},
_initComponentBindings() {
const optionDependencies = {};
if (!this._ngOptions.bindingOptions) {
return
}(0, _iterator.each)(this._ngOptions.bindingOptions, (optionPath, value) => {
const separatorIndex = optionPath.search(/\[|\./);
const optionForSubscribe = separatorIndex > -1 ? optionPath.substring(0, separatorIndex) : optionPath;
let prevWatchMethod;
let clearWatcher;
const valuePath = value.dataPath;
let deepWatch = true;
let forcePlainWatchMethod = false;
if (void 0 !== value.deep) {
forcePlainWatchMethod = deepWatch = !!value.deep
}
if (!optionDependencies[optionForSubscribe]) {
optionDependencies[optionForSubscribe] = {}
}
optionDependencies[optionForSubscribe][optionPath] = valuePath;
const updateWatcher = () => {
const watchCallback = (newValue, oldValue) => {
if (this._ngLocker.locked(optionPath)) {
return
}
this._ngLocker.obtain(optionPath);
this._component.option(optionPath, newValue);
updateWatcher();
if ((0, _comparator.equals)(oldValue, newValue) && this._ngLocker.locked(optionPath)) {
this._ngLocker.release(optionPath)
}
};
const watchMethod = Array.isArray(this._scope.$eval(valuePath)) && !forcePlainWatchMethod ? "$watchCollection" : "$watch";
if (prevWatchMethod !== watchMethod) {
if (clearWatcher) {
clearWatcher()
}
clearWatcher = this._scope[watchMethod](valuePath, watchCallback, deepWatch);
prevWatchMethod = watchMethod
}
};
updateWatcher();
this._componentDisposing.add(clearWatcher)
});
this._optionChangedCallbacks.add(args => {
const optionName = args.name;
const fullName = args.fullName;
const component = args.component;
if (this._ngLocker.locked(fullName)) {
this._ngLocker.release(fullName);
return
}
if (!optionDependencies || !optionDependencies[optionName]) {
return
}
const isActivePhase = this._scope.$root.$$phase;
const obtainOption = () => {
this._ngLocker.obtain(fullName)
};
if (isActivePhase) {
this._digestCallbacks.begin.add(obtainOption)
} else {
obtainOption()
}
safeApply(() => {
(0, _iterator.each)(optionDependencies[optionName], (optionPath, valuePath) => {
if (!this._optionsAreLinked(fullName, optionPath)) {
return
}
const value = component.option(optionPath);
this._parse(valuePath).assign(this._scope, value);
const scopeValue = this._parse(valuePath)(this._scope);
if (scopeValue !== value) {
args.component.option(optionPath, scopeValue)
}
})
}, this._scope);
const releaseOption = () => {
if (this._ngLocker.locked(fullName)) {
this._ngLocker.release(fullName)
}
this._digestCallbacks.begin.remove(obtainOption);
this._digestCallbacks.end.remove(releaseOption)
};
if (isActivePhase) {
this._digestCallbacks.end.addPrioritized(releaseOption)
} else {
releaseOption()
}
})
},
_optionsAreNested(optionPath1, optionPath2) {
const parentSeparator = optionPath1[optionPath2.length];
return 0 === optionPath1.indexOf(optionPath2) && ("." === parentSeparator || "[" === parentSeparator)
},
_optionsAreLinked(optionPath1, optionPath2) {
if (optionPath1 === optionPath2) {
return true
}
return optionPath1.length > optionPath2.length ? this._optionsAreNested(optionPath1, optionPath2) : this._optionsAreNested(optionPath2, optionPath1)
},
_compilerByTemplate(template) {
const scopeItemsPath = this._getScopeItemsPath();
return options => {
const $resultMarkup = (0, _renderer.default)(template).clone();
const dataIsScope = options.model && options.model.constructor === this._scope.$root.constructor;
const templateScope = dataIsScope ? options.model : options.noModel ? this._scope : this._createScopeWithData(options);
if (scopeItemsPath) {
this._synchronizeScopes(templateScope, scopeItemsPath, options.index)
}
$resultMarkup.appendTo(options.container);
if (!options.noModel) {
_events_engine.default.on($resultMarkup, "$destroy", () => {
const destroyAlreadyCalled = !templateScope.$parent;
if (destroyAlreadyCalled) {
return
}
templateScope.$destroy()
})
}
const ngTemplate = this._compile($resultMarkup, this._transcludeFn);
this._applyAsync(scope => {
ngTemplate(scope, null, {
parentBoundTranscludeFn: this._transcludeFn
})
}, templateScope);
return $resultMarkup
}
},
_applyAsync(func, scope) {
func(scope);
if (!scope.$root.$$phase) {
if (!this._renderingTimer) {
const clearRenderingTimer = () => {
clearTimeout(this._renderingTimer)
};
this._renderingTimer = setTimeout(() => {
scope.$apply();
this._renderingTimer = null;
this._componentDisposing.remove(clearRenderingTimer)
});
this._componentDisposing.add(clearRenderingTimer)
}
}
},
_getScopeItemsPath() {
if (this._componentClass.subclassOf(_uiCollection_widget.default) && this._ngOptions.bindingOptions && this._ngOptions.bindingOptions.items) {
return this._ngOptions.bindingOptions.items.dataPath
}
},
_createScopeWithData(options) {
const newScope = this._scope.$new();
if (this._itemAlias) {
newScope[this._itemAlias] = options.model
}
if ((0, _type.isDefined)(options.index)) {
newScope.$index = options.index
}
return newScope
},
_synchronizeScopes(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(args) {
const parentScope = args.parentScope;
const childScope = args.childScope;
const fieldPath = args.fieldPath;
const parentPrefix = args.parentPrefix;
const itemIndex = args.itemIndex;
const innerPathSuffix = fieldPath === this._itemAlias ? "" : "." + fieldPath;
const collectionField = void 0 !== itemIndex;
const optionOuterBag = [parentPrefix];
if (collectionField) {
if (!(0, _type.isNumeric)(itemIndex)) {
return
}
optionOuterBag.push("[", itemIndex, "]")
}
optionOuterBag.push(innerPathSuffix);
const optionOuterPath = optionOuterBag.join("");
const clearParentWatcher = parentScope.$watch(optionOuterPath, (newValue, oldValue) => {
if (newValue !== oldValue) {
(0, _data.compileSetter)(fieldPath)(childScope, newValue)
}
});
const clearItemWatcher = childScope.$watch(fieldPath, (newValue, oldValue) => {
if (newValue !== oldValue) {
if (collectionField && !(0, _data.compileGetter)(parentPrefix)(parentScope)[itemIndex]) {
clearItemWatcher();
return
}(0, _data.compileSetter)(optionOuterPath)(parentScope, newValue)
}
});
this._componentDisposing.add([clearParentWatcher, clearItemWatcher])
},
_evalOptions(scope) {
const result = (0, _extend.extendFromObject)({}, this._ngOptions);
delete result.bindingOptions;
if (this._ngOptions.bindingOptions) {
(0, _iterator.each)(this._ngOptions.bindingOptions, (key, value) => {
result[key] = scope.$eval(value.dataPath)
})
}
result._optionChangedCallbacks = this._optionChangedCallbacks;
result._disposingCallbacks = this._componentDisposing;
result.onActionCreated = (component, action, config) => {
if (config && "rendering" === config.category) {
return action
}
return function() {
const args = arguments;
if (!scope || !scope.$root || scope.$root.$$phase) {
return action.apply(this, args)
}
return safeApply(() => action.apply(this, args), scope)
}
};
result.beforeActionExecute = result.onActionCreated;
result.nestedComponentOptions = component => ({
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 ((0, _config.default)().wrapActionsBeforeExecute) {
result.forceApplyBindings = () => {
safeApply(() => {}, scope)
}
}
result.integrationOptions = {
createTemplate: element => new _template.NgTemplate(element, this._compilerByTemplate.bind(this)),
watchMethod: (fn, callback, options) => {
options = options || {};
let immediateValue;
let skipCallback = options.skipImmediate;
const disposeWatcher = scope.$watch(() => {
let value = fn();
if (value instanceof Date) {
value = value.valueOf()
}
return value
}, newValue => {
const isSameValue = immediateValue === newValue;
if (!skipCallback && (!isSameValue || isSameValue && options.deep)) {
callback(newValue)
}
skipCallback = false
}, options.deep);
if (!skipCallback) {
immediateValue = fn();
callback(immediateValue)
}
if ((0, _config.default)().wrapActionsBeforeExecute) {
this._applyAsync(() => {}, scope)
}
return disposeWatcher
},
templates: {
"dx-polymorph-widget": {
render: options => {
const widgetName = options.model.widget;
if (!widgetName) {
return
}
const markup = (0, _renderer.default)("<div>").attr((0, _inflector.dasherize)(widgetName), "options").get(0);
const newScope = this._scope.$new();
newScope.options = options.model.options;
options.container.append(markup);
this._compile(markup)(newScope)
}
}
}
};
result.modelByElement = () => scope;
return result
}
});
ComponentBuilder = ComponentBuilder.inherit({
ctor(options) {
this._componentName = options.componentName;
this._ngModel = options.ngModel;
this._ngModelController = options.ngModelController;
this.callBase(...arguments)
},
_isNgModelRequired() {
return _editor.default.isEditor(this._componentClass.prototype) && this._ngModel
},
_initComponentBindings() {
this.callBase(...arguments);
this._initNgModelBinding()
},
_initNgModelBinding() {
if (!this._isNgModelRequired()) {
return
}
const clearNgModelWatcher = this._scope.$watch(this._ngModel, (newValue, oldValue) => {
if (this._ngLocker.locked("value")) {
return
}
if (newValue === oldValue) {
return
}
this._component.option("value", newValue)
});
this._optionChangedCallbacks.add(args => {
this._ngLocker.obtain("value");
try {
if ("value" !== args.name) {
return
}
this._ngModelController.$setViewValue(args.value)
} finally {
if (this._ngLocker.locked("value")) {
this._ngLocker.release("value")
}
}
});
this._componentDisposing.add(clearNgModelWatcher)
},
_evalOptions() {
if (!this._isNgModelRequired()) {
return this.callBase(...arguments)
}
const result = this.callBase(...arguments);
result.value = this._parse(this._ngModel)(this._scope);
return result
}
});
const registeredComponents = {};
const registerComponentDirective = name => {
const priority = "dxValidator" !== name ? 1 : 10;
_module.default.directive(name, ["$compile", "$parse", "dxDigestCallbacks", ($compile, $parse, dxDigestCallbacks) => ({
restrict: "A",
require: "^?ngModel",
priority: priority,
compile($element) {
const componentClass = registeredComponents[name];
const useTemplates = componentClass.prototype._useTemplates ? componentClass.prototype._useTemplates() : getClassMethod(componentClass, "_useTemplates")();
const $content = useTemplates ? $element.contents().detach() : null;
return (scope, $element, attrs, ngModelController, transcludeFn) => {
$element.append($content);
safeApply(() => {
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.dxItemAlias,
dxDigestCallbacks: dxDigestCallbacks
})
}, scope)
}
}
})])
};
_component_registrator_callbacks.default.add((name, componentClass) => {
if (!registeredComponents[name]) {
registerComponentDirective(name)
}
registeredComponents[name] = componentClass
})
}