UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

742 lines (657 loc) • 24.7 kB
"use strict"; require("../../integration/jquery"); var $ = require("jquery"), commonUtils = require("../../core/utils/common"), window = require("../../core/utils/window").getWindow(), domAdapter = require("../../core/dom_adapter"), Component = require("../../core/component"), extendUtils = require("../../core/utils/extend"), each = require("../../core/utils/iterator").each, errors = require("../errors"), Application = require("../application").Application, ConditionalViewCacheDecorator = require("../view_cache").ConditionalViewCacheDecorator, html = require("./presets"), CommandManager = require("./command_manager"), ViewEngine = require("./view_engine").ViewEngine, messageLocalization = require("../../localization/message"), viewPort = require("../../core/utils/view_port").value, initMobileViewportModule = require("../../mobile/init_mobile_viewport/init_mobile_viewport"), devices = require("../../core/devices"), feedbackEvents = require("../../events/core/emitter.feedback"), TransitionExecutorModule = require("../../animation/transition_executor/transition_executor"), animationPresetsModule = require("../../animation/presets/presets"), when = require("../../core/utils/deferred").when; require("./layout_controller"); require("../../ui/themes"); var VIEW_PORT_CLASSNAME = "dx-viewport", LAYOUT_CHANGE_ANIMATION_NAME = "layout-change"; /** * @name HtmlApplication * @publicName HtmlApplication * @type object * @inherits EventsMixin * @namespace DevExpress.framework.html * @module framework/html/html_application * @export default */ var HtmlApplication = Application.inherit({ /** * @name HtmlApplicationoptions.namespace * @publicName namespace * @type object */ /** * @name HtmlApplicationOptions.router * @publicName router * @type object */ /** * @name HtmlApplicationOptions.stateManager * @publicName stateManager * @type object */ /** * @name HtmlApplicationOptions.stateStorage * @publicName stateStorage * @type object */ /** * @name HtmlApplicationoptions.navigation * @publicName navigation * @type Array<dxCommand,dxCommandOptions> */ /** * @name HtmlApplicationoptions.mode * @publicName mode * @type string * @default "mobileApp" * @acceptValues 'mobileApp'|'webSite' */ /** * @name HtmlApplicationoptions.layoutSet * @publicName layoutSet * @type string|Array<Object> * @default undefined */ /** * @name HtmlApplicationoptions.animationSet * @publicName animationSet * @type object * @default undefined */ /** * @name HtmlApplicationoptions.disableViewCache * @publicName disableViewCache * @type boolean */ /** * @name HtmlApplicationoptions.useViewTitleAsBackText * @publicName useViewTitleAsBackText * @type boolean * @default false */ /** * @name HtmlApplicationoptions.viewPort * @publicName viewPort * @type object * @type_object_field1 allowZoom:Boolean * @type_object_field2 allowPan:Boolean * @type_object_field3 allowSelection:Boolean */ /** * @name HtmlApplicationoptions.navigateToRootViewMode * @publicName navigateToRootViewMode * @type string * @default "resetHistory" * @acceptValues 'keepHistory'|'resetHistory' */ /** * @name HtmlApplicationoptions.commandMapping * @publicName commandMapping * @type object * @default DevExpress.framework.CommandMapping.defaultMapping */ /** * @name HtmlApplicationoptions.viewCache * @publicName viewCache * @type object */ /** * @name HtmlApplicationoptions.viewCacheSize * @publicName viewCacheSize * @type Number * @default 5 */ /** * @name HtmlApplicationoptions.templatesVersion * @publicName templatesVersion * @type String * @default undefined */ ctor: function ctor(options) { options = options || {}; this.callBase(options); this._$root = $(options.rootNode || domAdapter.getBody()); this._initViewport(options.viewPort); if (this._applicationMode === "mobileApp") { initMobileViewportModule.initMobileViewport(options.viewPort); } this.device = options.device || devices.current(); this.commandManager = options.commandManager || new CommandManager({ commandMapping: this.commandMapping }); this._initTemplateContext(); this.viewEngine = options.viewEngine || new ViewEngine({ $root: this._$root, device: this.device, templateCacheStorage: options.templateCacheStorage || window.localStorage, templatesVersion: options.templatesVersion, templateContext: this._templateContext }); this.components.push(this.viewEngine); this._initMarkupFilters(this.viewEngine); this._layoutSet = options.layoutSet || html.layoutSets["default"]; this._animationSet = options.animationSet || html.animationSets["default"]; this._availableLayoutControllers = []; this._activeLayoutControllersStack = []; this.transitionExecutor = new TransitionExecutorModule.TransitionExecutor(); this._initAnimations(this._animationSet); }, _initAnimations: function _initAnimations(animationSet) { if (!animationSet) { return; } each(animationSet, function (name, configs) { each(configs, function (index, config) { animationPresetsModule.presets.registerPreset(name, config); }); }); animationPresetsModule.presets.applyChanges(); }, _localizeMarkup: function _localizeMarkup($markup) { messageLocalization.localizeNode($markup); }, _notifyIfBadMarkup: function _notifyIfBadMarkup($markup) { $markup.each(function () { var html = $(this).html(); if (/href="#/.test(html)) { errors.log("W3005", html); } }); }, _initMarkupFilters: function _initMarkupFilters(viewEngine) { var filters = []; filters.push(this._localizeMarkup); ///#DEBUG if (this._applicationMode === "mobileApp") { filters.push(this._notifyIfBadMarkup); } ///#ENDDEBUG if (viewEngine.markupLoaded) { viewEngine.markupLoaded.add(function (args) { each(filters, function (_, filter) { filter(args.markup); }); }); } }, _createViewCache: function _createViewCache(options) { var result = this.callBase(options); if (!options.viewCache) { result = new ConditionalViewCacheDecorator({ filter: function filter(key, viewInfo) { return !viewInfo.viewTemplateInfo.disableCache; }, viewCache: result }); } return result; }, /** * @name HtmlApplicationmethods.createNavigation * @publicName createNavigation(navigationConfig) * @param1 navigationConfig:Array<Object> */ /** * @name HtmlApplicationmethods.navigate * @publicName navigate(uri) * @param1 uri:string|object|undefined */ /** * @name HtmlApplicationmethods.navigate * @publicName navigate(uri, options) * @param1 uri:string|object * @param2 options:object * @param2_field1 root:Boolean * @param2_field2 target:string * @param2_field3 direction:string * @param2_field5 modal:Boolean */ /** * @name HtmlApplicationmethods.canBack * @publicName canBack() * @return boolean */ /** * @name HtmlApplicationmethods.back * @publicName back() */ /** * @name HtmlApplicationmethods.saveState * @publicName saveState() */ /** * @name HtmlApplicationmethods.restoreState * @publicName restoreState() */ /** * @name HtmlApplicationmethods.clearState * @publicName clearState() */ /** * @name HtmlApplicationevents.initialized * @publicName initialized * @type classEventType */ /** * @name HtmlApplicationevents.navigatingBack * @publicName navigatingBack * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 cancel:Boolean * @type_function_param1_field2 isHardwareButton:Boolean */ /** * @name HtmlApplicationevents.navigating * @publicName navigating * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 currentUri:string * @type_function_param1_field2 uri:string * @type_function_param1_field3 cancel:Boolean * @type_function_param1_field4 options:object */ /** * @name HtmlApplicationevents.beforeViewSetup * @publicName beforeViewSetup * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 viewInfo:object */ /** * @name HtmlApplicationevents.afterViewSetup * @publicName afterViewSetup * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 viewInfo:object */ /** * @name HtmlApplicationevents.viewRendered * @publicName viewRendered * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 viewInfo:object */ /** * @name HtmlApplicationevents.viewShowing * @publicName viewShowing * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 viewInfo:object * @type_function_param1_field2 direction:string */ /** * @name HtmlApplicationevents.viewShown * @publicName viewShown * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 viewInfo:object * @type_function_param1_field2 direction:string */ /** * @name HtmlApplicationevents.viewHidden * @publicName viewHidden * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 viewInfo:object */ /** * @name HtmlApplicationevents.viewDisposing * @publicName viewDisposing * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 viewInfo:object */ /** * @name HtmlApplicationevents.viewDisposed * @publicName viewDisposed * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 viewInfo:object */ /** * @name HtmlApplicationevents.resolveLayoutController * @publicName resolveLayoutController * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 viewInfo:object * @type_function_param1_field2 layoutController:object * @type_function_param1_field3 availableLayoutControllers:Array<Object> */ /** * @name HtmlApplicationevents.resolveViewCacheKey * @publicName resolveViewCacheKey * @type classEventType * @type_function_param1 e:object * @type_function_param1_field1 key:string * @type_function_param1_field2 navigationItem:object * @type_function_param1_field3 routeData:object */ /** * @name HtmlApplicationfields.viewCache * @publicName viewCache * @type object */ /** * @name HtmlApplicationFields.router * @publicName router * @type object */ /** * @name HtmlApplicationfields.navigation * @publicName navigation * @type Array<dxCommand> */ /** * @name HtmlApplicationFields.stateManager * @publicName stateManager * @type object */ _initViewport: function _initViewport() { this._$viewPort = this._getViewPort(); viewPort(this._$viewPort); }, _getViewPort: function _getViewPort() { var $viewPort = $("." + VIEW_PORT_CLASSNAME); if (!$viewPort.length) { $viewPort = $("<div>").addClass(VIEW_PORT_CLASSNAME).appendTo(this._$root); } return $viewPort; }, _initTemplateContext: function _initTemplateContext() { this._templateContext = new Component({ orientation: devices.orientation() }); devices.on("orientationChanged", function (args) { this._templateContext.option("orientation", args.orientation); }.bind(this)); }, _showViewImpl: function _showViewImpl(viewInfo, direction) { var that = this, deferred = $.Deferred(), result = deferred.promise(), layoutController = viewInfo.layoutController; that._obtainViewLink(viewInfo); layoutController.showView(viewInfo, direction).done(function () { that._activateLayoutController(layoutController, that._getTargetNode(viewInfo), direction).done(function () { deferred.resolve(); }); }); feedbackEvents.lock(result); // prevents UI feedback from blinking during transition return result; }, _resolveLayoutController: function _resolveLayoutController(viewInfo) { var args = { viewInfo: viewInfo, layoutController: null, availableLayoutControllers: this._availableLayoutControllers }; this._processEvent("resolveLayoutController", args, viewInfo.model); ///#DEBUG this._checkLayoutControllerIsInitialized(args.layoutController); ///#ENDDEBUG return args.layoutController || this._resolveLayoutControllerImpl(viewInfo); }, _checkLayoutControllerIsInitialized: function _checkLayoutControllerIsInitialized(layoutController) { if (layoutController) { var isControllerInited = false; each(this._layoutSet, function (_, controllerInfo) { if (controllerInfo.controller === layoutController) { isControllerInited = true; return false; } }); if (!isControllerInited) { throw errors.Error("E3024"); } } }, _ensureOneLayoutControllerFound: function _ensureOneLayoutControllerFound(target, matches) { var toJSONInterceptor = function toJSONInterceptor(key, value) { if (key === "controller") { return "[controller]: { name:" + value.name + " }"; } return value; }; if (!matches.length) { errors.log("W3003", JSON.stringify(target, null, 4), JSON.stringify(this._availableLayoutControllers, toJSONInterceptor, 4)); throw errors.Error("E3011"); } if (matches.length > 1) { errors.log("W3004", JSON.stringify(target, null, 4), JSON.stringify(matches, toJSONInterceptor, 4)); throw errors.Error("E3012"); } }, _resolveLayoutControllerImpl: function _resolveLayoutControllerImpl(viewInfo) { var templateInfo = viewInfo.viewTemplateInfo || {}, navigateOptions = viewInfo.navigateOptions || {}, target = extendUtils.extend({ root: !viewInfo.canBack, customResolveRequired: false, pane: templateInfo.pane, modal: navigateOptions.modal !== undefined ? navigateOptions.modal : templateInfo.modal || false }, devices.current()); var matches = commonUtils.findBestMatches(target, this._availableLayoutControllers); this._ensureOneLayoutControllerFound(target, matches); return matches[0].controller; }, _onNavigatingBack: function _onNavigatingBack(args) { this.callBase.apply(this, arguments); if (!args.cancel && !this.canBack() && this._activeLayoutControllersStack.length > 1) { var previousActiveLayoutController = this._activeLayoutControllersStack[this._activeLayoutControllersStack.length - 2], previousViewInfo = previousActiveLayoutController.activeViewInfo(); args.cancel = true; this._activateLayoutController(previousActiveLayoutController, undefined, "backward"); this.navigationManager.currentItem(previousViewInfo.key); } }, _activeLayoutController: function _activeLayoutController() { return this._activeLayoutControllersStack.length ? this._activeLayoutControllersStack[this._activeLayoutControllersStack.length - 1] : undefined; }, _getTargetNode: function _getTargetNode(viewInfo) { var dxEvent = (viewInfo.navigateOptions || {}).event; return dxEvent ? $(dxEvent.target) : undefined; }, _activateLayoutController: function _activateLayoutController(layoutController, targetNode, direction) { var that = this, previousLayoutController = that._activeLayoutController(); if (previousLayoutController === layoutController) { return $.Deferred().resolve().promise(); } var d = $.Deferred(); layoutController.ensureActive(targetNode).done(function (result) { that._deactivatePreviousLayoutControllers(layoutController, direction, result).done(function () { that._activeLayoutControllersStack.push(layoutController); d.resolve(); }); }); return d.promise(); }, _deactivatePreviousLayoutControllers: function _deactivatePreviousLayoutControllers(layoutController, direction) { var that = this, tasks = [], controllerToDeactivate = that._activeLayoutControllersStack.pop(); if (!controllerToDeactivate) { return $.Deferred().resolve().promise(); } if (layoutController.isOverlay) { that._activeLayoutControllersStack.push(controllerToDeactivate); tasks.push(controllerToDeactivate.disable()); } else { var transitionDeferred = $.Deferred(), skipAnimation = false; // NOTE: It's needed to prevent creating a closure. Can't be declared in a loop because of JSHint check. var getControllerDeactivator = function getControllerDeactivator(controllerToDeactivate, d) { return function () { controllerToDeactivate.deactivate().done(function () { d.resolve(); }); }; }; while (controllerToDeactivate && controllerToDeactivate !== layoutController) { var d = $.Deferred(); if (controllerToDeactivate.isOverlay) { skipAnimation = true; } else { if (!skipAnimation) { that.transitionExecutor.leave(controllerToDeactivate.element(), LAYOUT_CHANGE_ANIMATION_NAME, { direction: direction }); } } transitionDeferred.promise().done(getControllerDeactivator(controllerToDeactivate, d)); tasks.push(d.promise()); controllerToDeactivate = that._activeLayoutControllersStack.pop(); } if (skipAnimation) { transitionDeferred.resolve(); } else { that.transitionExecutor.enter(layoutController.element(), LAYOUT_CHANGE_ANIMATION_NAME, { direction: direction }); that.transitionExecutor.start().done(function () { transitionDeferred.resolve(); }); } } return when.apply($, tasks); }, init: function init() { var that = this, result = this.callBase(); result.done(function () { that._initLayoutControllers(); that.renderNavigation(); }); return result; }, _disposeView: function _disposeView(viewInfo) { if (viewInfo.layoutController.disposeView) { viewInfo.layoutController.disposeView(viewInfo); } this.callBase(viewInfo); }, viewPort: function viewPort() { return this._$viewPort; }, _createViewInfo: function _createViewInfo() { var viewInfo = this.callBase.apply(this, arguments), templateInfo = this.getViewTemplateInfo(viewInfo.viewName); if (!templateInfo) { throw errors.Error("E3013", "dxView", viewInfo.viewName); } viewInfo.viewTemplateInfo = templateInfo; viewInfo.layoutController = this._resolveLayoutController(viewInfo); return viewInfo; }, _createViewModel: function _createViewModel(viewInfo) { this.callBase(viewInfo); extendUtils.extendFromObject(viewInfo.model, viewInfo.viewTemplateInfo); }, _initLayoutControllers: function _initLayoutControllers() { var that = this; each(that._layoutSet, function (index, controllerInfo) { var controller = controllerInfo.controller, target = devices.current(); if (commonUtils.findBestMatches(target, [controllerInfo]).length) { that._availableLayoutControllers.push(controllerInfo); if (controller.init) { controller.init({ app: that, $viewPort: that._$viewPort, navigationManager: that.navigationManager, viewEngine: that.viewEngine, templateContext: that._templateContext, // TODO: add layoutContext commandManager: that.commandManager }); } if (controller.on) { controller.on("viewReleased", function (viewInfo) { that._onViewReleased(viewInfo); }); controller.on("viewHidden", function (viewInfo) { that._onViewHidden(viewInfo); }); controller.on("viewRendered", function (viewInfo) { that._processEvent("viewRendered", { viewInfo: viewInfo }, viewInfo.model); }); controller.on("viewShowing", function (viewInfo, direction) { that._processEvent("viewShowing", { viewInfo: viewInfo, direction: direction, params: viewInfo.routeData }, viewInfo.model); }); controller.on("viewShown", function (viewInfo, direction) { that._processEvent("viewShown", { viewInfo: viewInfo, direction: direction, params: viewInfo.routeData }, viewInfo.model); }); } } }); }, _onViewReleased: function _onViewReleased(viewInfo) { this._releaseViewLink(viewInfo); }, /** * @name HtmlApplicationmethods.renderNavigation * @publicName renderNavigation() */ renderNavigation: function renderNavigation() { var that = this; each(that._availableLayoutControllers, function (index, controllerInfo) { var controller = controllerInfo.controller; if (controller.renderNavigation) { controller.renderNavigation(that.navigation); } }); }, /** * @name HtmlApplicationmethods.getViewTemplate * @publicName getViewTemplate(viewName) * @param1 viewName:string * @return jQuery */ getViewTemplate: function getViewTemplate(viewName) { return this.viewEngine.getViewTemplate(viewName); }, /** * @name HtmlApplicationmethods.getViewTemplateInfo * @publicName getViewTemplateInfo(viewName) * @param1 viewName:string * @return object */ getViewTemplateInfo: function getViewTemplateInfo(viewName) { var viewComponent = this.viewEngine.getViewTemplateInfo(viewName); return viewComponent && viewComponent.option(); }, /** * @name HtmlApplicationmethods.loadTemplates * @publicName loadTemplates(source) * @param1 source:string|jQuery * @return Promise<void> */ loadTemplates: function loadTemplates(source) { return this.viewEngine.loadTemplates(source); }, /** * @name HtmlApplicationmethods.templateContext * @publicName templateContext() * @return Object */ templateContext: function templateContext() { return this._templateContext; } }); module.exports = HtmlApplication;