UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

508 lines (507 loc) • 18.6 kB
/** * DevExtreme (framework/navigation_manager.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"; require("../integration/jquery"); var $ = require("jquery"), Class = require("../core/class"), Callbacks = require("../core/utils/callbacks"), commonUtils = require("../core/utils/common"), iteratorUtils = require("../core/utils/iterator"), isPlainObject = require("../core/utils/type").isPlainObject, extend = require("../core/utils/extend").extend, navigationDevices = require("./navigation_devices"), EventsMixin = require("../core/events_mixin"), errors = require("./errors"), hardwareBackButton = require("../mobile/process_hardware_back_button").processCallback, hideTopOverlay = require("../mobile/hide_top_overlay"), when = require("../core/utils/deferred").when; var NAVIGATION_TARGETS = { current: "current", blank: "blank", back: "back" }, STORAGE_HISTORY_KEY = "__history"; var HistoryBasedNavigationManager = Class.inherit({ ctor: function(options) { options = options || {}; this._currentItem = void 0; this._previousItem = void 0; this._createNavigationDevice(options) }, _createNavigationDevice: function(options) { this._navigationDevice = options.navigationDevice || new navigationDevices.HistoryBasedNavigationDevice; this._navigationDevice.uriChanged.add(this._uriChangedHandler.bind(this)) }, _uriChangedHandler: function(uri) { while (hideTopOverlay()) {} this.navigate(uri) }, _syncUriWithCurrentNavigationItem: function() { var currentUri = this._currentItem && this._currentItem.uri; this._navigationDevice.setUri(currentUri, true) }, _cancelNavigation: function(args) { this._syncUriWithCurrentNavigationItem(); this.fireEvent("navigationCanceled", [args]) }, _getDefaultOptions: function() { return { direction: "none", target: NAVIGATION_TARGETS.blank } }, _updateHistory: function(uri, options) { this._previousItem = this._currentItem; this._currentItem = { uri: uri, key: uri }; this._navigationDevice.setUri(uri, options.target === NAVIGATION_TARGETS.current) }, _setCurrentItem: function(item) { this._currentItem = item }, navigate: function(uri, options) { options = options || {}; var args, that = this, isFirstNavigate = !that._currentItem, currentItem = that._currentItem || {}, targetItem = options.item || {}, currentUri = currentItem.uri, currentKey = currentItem.key, targetKey = targetItem.key; if (void 0 === uri) { uri = that._navigationDevice.getUri() } if (/^_back$/.test(uri)) { that.back(); return } options = extend(that._getDefaultOptions(), options || {}); if (isFirstNavigate) { options.target = NAVIGATION_TARGETS.current } args = { currentUri: currentUri, uri: uri, cancel: false, navigateWhen: [], options: options }; that.fireEvent("navigating", [args]); uri = args.uri; if (args.cancel || currentUri === uri && (void 0 === targetKey || targetKey === currentKey) && !that._forceNavigate) { that._cancelNavigation(args) } else { that._forceNavigate = false; when.apply($, args.navigateWhen).done(function() { commonUtils.executeAsync(function() { that._updateHistory(uri, options); that.fireEvent("navigated", [{ uri: uri, previousUri: currentUri, options: options, item: that._currentItem }]) }) }) } }, back: function() { return this._navigationDevice.back() }, previousItem: function() { return this._previousItem }, currentItem: function(item) { if (arguments.length > 0) { if (!item) { throw errors.Error("E3023") } this._setCurrentItem(item) } else { return this._currentItem } }, rootUri: function() { return this._currentItem && this._currentItem.uri }, canBack: function() { return true }, saveState: commonUtils.noop, restoreState: commonUtils.noop, removeState: commonUtils.noop }).include(EventsMixin); var StackBasedNavigationManager = HistoryBasedNavigationManager.inherit({ ctor: function(options) { options = options || {}; this.callBase(options); this._createNavigationStacks(options); hardwareBackButton.add(this._deviceBackInitiated.bind(this)); this._stateStorageKey = options.stateStorageKey || STORAGE_HISTORY_KEY }, init: function() { return this._navigationDevice.init() }, _createNavigationDevice: function(options) { if (!options.navigationDevice) { options.navigationDevice = new navigationDevices.StackBasedNavigationDevice } this.callBase(options); this._navigationDevice.backInitiated.add(this._deviceBackInitiated.bind(this)) }, _uriChangedHandler: function(uri) { this.navigate(uri) }, _createNavigationStacks: function(options) { this.navigationStacks = {}; this._keepPositionInStack = options.keepPositionInStack; this.currentStack = new NavigationStack }, _deviceBackInitiated: function() { if (!hideTopOverlay()) { this.back({ isHardwareButton: true }) } else { this._syncUriWithCurrentNavigationItem() } }, _getDefaultOptions: function() { return { target: NAVIGATION_TARGETS.blank } }, _createNavigationStack: function() { var result = new NavigationStack; result.itemsRemoved.add(this._removeItems.bind(this)); return result }, _setCurrentItem: function(item) { this._setCurrentStack(item.stack); this.currentStack.currentItem(item); this.callBase(item); this._syncUriWithCurrentNavigationItem() }, _setCurrentStack: function(stackOrStackKey) { var stack, stackKey; if ("string" === typeof stackOrStackKey) { stackKey = stackOrStackKey; if (!(stackKey in this.navigationStacks)) { this.navigationStacks[stackKey] = this._createNavigationStack() } stack = this.navigationStacks[stackKey] } else { stack = stackOrStackKey; stackKey = iteratorUtils.map(this.navigationStacks, function(stack, key) { if (stack === stackOrStackKey) { return key } return null })[0] } this.currentStack = stack; this.currentStackKey = stackKey }, _getViewTargetStackKey: function(uri, isRoot) { var result; if (isRoot) { if (void 0 !== this.navigationStacks[uri]) { result = uri } else { for (var stackKey in this.navigationStacks) { if (this.navigationStacks[stackKey].items[0].uri === uri) { result = stackKey; break } } result = result || uri } } else { result = this.currentStackKey || uri } return result }, _updateHistory: function(uri, options) { var isRoot = options.root, forceIsRoot = isRoot, forceToRoot = false, previousStack = this.currentStack, keepPositionInStack = void 0 !== options.keepPositionInStack ? options.keepPositionInStack : this._keepPositionInStack; options.stack = options.stack || this._getViewTargetStackKey(uri, isRoot); this._setCurrentStack(options.stack); if (isRoot || !this.currentStack.items.length) { forceToRoot = this.currentStack === previousStack; forceIsRoot = true } if (isRoot && this.currentStack.items.length) { if (!keepPositionInStack || forceToRoot) { this.currentStack.currentIndex = 0; if (this.currentItem().uri !== uri) { this.currentStack.navigate(uri, true) } } options.direction = options.direction || "none" } else { var prevIndex = this.currentStack.currentIndex, prevItem = this.currentItem() || {}; switch (options.target) { case NAVIGATION_TARGETS.blank: this.currentStack.navigate(uri); break; case NAVIGATION_TARGETS.current: this.currentStack.navigate(uri, true); break; case NAVIGATION_TARGETS.back: if (this.currentStack.currentIndex > 0) { this.currentStack.back(uri) } else { this.currentStack.navigate(uri, true) } break; default: throw errors.Error("E3006", options.target) } if (void 0 === options.direction) { var indexDelta = this.currentStack.currentIndex - prevIndex; if (indexDelta < 0) { options.direction = this.currentStack.currentItem().backDirection || "backward" } else { if (indexDelta > 0 && this.currentStack.currentIndex > 0) { options.direction = "forward" } else { options.direction = "none" } } } prevItem.backDirection = "forward" === options.direction ? "backward" : "none" } options.root = forceIsRoot; this._currentItem = this.currentStack.currentItem(); this._syncUriWithCurrentNavigationItem() }, _removeItems: function(items) { var that = this; iteratorUtils.each(items, function(index, item) { that.fireEvent("itemRemoved", [item]) }) }, back: function(options) { options = options || {}; var navigatingBackArgs = extend({ cancel: false }, options); this.fireEvent("navigatingBack", [navigatingBackArgs]); if (navigatingBackArgs.cancel) { this._syncUriWithCurrentNavigationItem(); return } var item = this.previousItem(navigatingBackArgs.stack); if (item) { this.navigate(item.uri, { stack: navigatingBackArgs.stack, target: NAVIGATION_TARGETS.back, item: item }) } else { this.callBase() } }, rootUri: function() { return this.currentStack.items.length ? this.currentStack.items[0].uri : this.callBase() }, canBack: function(stackKey) { var stack = stackKey ? this.navigationStacks[stackKey] : this.currentStack; return stack ? stack.canBack() : false }, saveState: function(storage) { if (this.currentStack.items.length) { var state = { navigationStacks: {}, currentStackKey: this.currentStackKey }; iteratorUtils.each(this.navigationStacks, function(stackKey, stack) { var stackState = {}; state.navigationStacks[stackKey] = stackState; stackState.currentIndex = stack.currentIndex; stackState.items = iteratorUtils.map(stack.items, function(item) { return { key: item.key, uri: item.uri } }) }); var json = JSON.stringify(state); storage.setItem(this._stateStorageKey, json) } else { this.removeState(storage) } }, restoreState: function(storage) { if (this.disableRestoreState) { return } var json = storage.getItem(this._stateStorageKey); if (json) { try { var that = this, state = JSON.parse(json); iteratorUtils.each(state.navigationStacks, function(stackKey, stackState) { var stack = that._createNavigationStack(); that.navigationStacks[stackKey] = stack; stack.currentIndex = stackState.currentIndex; stack.items = iteratorUtils.map(stackState.items, function(item) { item.stack = stack; return item }) }); this.currentStackKey = state.currentStackKey; this.currentStack = this.navigationStacks[this.currentStackKey]; this._currentItem = this.currentStack.currentItem(); this._navigationDevice.setUri(this.currentItem().uri); this._forceNavigate = true } catch (e) { this.removeState(storage); throw errors.Error("E3007") } } }, removeState: function(storage) { storage.removeItem(this._stateStorageKey) }, currentIndex: function() { return this.currentStack.currentIndex }, previousItem: function(stackKey) { var stack = this.navigationStacks[stackKey] || this.currentStack; return stack.previousItem() }, getItemByIndex: function(index) { return this.currentStack.items[index] }, clearHistory: function() { this._createNavigationStacks({ keepPositionInStack: this._keepPositionInStack }) }, itemByKey: function(itemKey) { var result; iteratorUtils.each(this.navigationStacks, function(stackKey, stack) { var item = stack.itemByKey(itemKey); if (item) { result = item; return false } }); return result }, currentItem: function(itemOrItemKey) { var item; if (arguments.length > 0) { if ("string" === typeof itemOrItemKey) { item = this.itemByKey(itemOrItemKey) } else { if (isPlainObject(itemOrItemKey)) { item = itemOrItemKey } } this.callBase(item) } else { return this.callBase() } } }); var NavigationStack = Class.inherit({ ctor: function(options) { options = options || {}; this.itemsRemoved = Callbacks(); this.clear() }, currentItem: function(item) { if (item) { for (var i = 0; i < this.items.length; i++) { if (item === this.items[i]) { this.currentIndex = i; break } } } else { return this.items[this.currentIndex] } }, previousItem: function() { return this.items.length > 1 ? this.items[this.currentIndex - 1] : void 0 }, canBack: function() { return this.currentIndex > 0 }, clear: function() { this._deleteItems(this.items); this.items = []; this.currentIndex = -1 }, back: function(uri) { this.currentIndex--; if (this.currentIndex < 0) { throw errors.Error("E3008") } var currentItem = this.currentItem(); if (currentItem.uri !== uri) { this._updateItem(this.currentIndex, uri) } }, forward: function() { this.currentIndex++; if (this.currentIndex >= this.items.length) { throw errors.Error("E3009") } }, navigate: function(uri, replaceCurrent) { if (this.currentIndex < this.items.length && this.currentIndex > -1 && this.items[this.currentIndex].uri === uri) { return } if (replaceCurrent && this.currentIndex > -1) { this.currentIndex-- } if (this.currentIndex + 1 < this.items.length && this.items[this.currentIndex + 1].uri === uri) { this.currentIndex++ } else { var toDelete = this.items.splice(this.currentIndex + 1, this.items.length - this.currentIndex - 1); this.items.push({ stack: this }); this.currentIndex++; this._updateItem(this.currentIndex, uri); this._deleteItems(toDelete) } return this.currentItem() }, itemByKey: function(key) { for (var i = 0; i < this.items.length; i++) { var item = this.items[i]; if (item.key === key) { return item } } }, _updateItem: function(index, uri) { var item = this.items[index]; item.uri = uri; item.key = this.items[0].uri + "_" + index + "_" + uri }, _deleteItems: function(items) { if (items) { this.itemsRemoved.fire(items) } } }); exports.HistoryBasedNavigationManager = HistoryBasedNavigationManager; exports.StackBasedNavigationManager = StackBasedNavigationManager; exports.NavigationStack = NavigationStack;