UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

580 lines (493 loc) • 18.6 kB
"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 ctor(options) { options = options || {}; this._currentItem = undefined; this._previousItem = undefined; this._createNavigationDevice(options); }, _createNavigationDevice: function _createNavigationDevice(options) { this._navigationDevice = options.navigationDevice || new navigationDevices.HistoryBasedNavigationDevice(); this._navigationDevice.uriChanged.add(this._uriChangedHandler.bind(this)); }, _uriChangedHandler: function _uriChangedHandler(uri) { while (hideTopOverlay()) {} this.navigate(uri); }, _syncUriWithCurrentNavigationItem: function _syncUriWithCurrentNavigationItem() { var currentUri = this._currentItem && this._currentItem.uri; this._navigationDevice.setUri(currentUri, true); }, _cancelNavigation: function _cancelNavigation(args) { this._syncUriWithCurrentNavigationItem(); this.fireEvent("navigationCanceled", [args]); }, _getDefaultOptions: function _getDefaultOptions() { return { direction: "none", target: NAVIGATION_TARGETS.blank }; }, _updateHistory: function _updateHistory(uri, options) { this._previousItem = this._currentItem; this._currentItem = { uri: uri, key: uri }; this._navigationDevice.setUri(uri, options.target === NAVIGATION_TARGETS.current); }, _setCurrentItem: function _setCurrentItem(item) { this._currentItem = item; }, navigate: function navigate(uri, options) { options = options || {}; var that = this, isFirstNavigate = !that._currentItem, currentItem = that._currentItem || {}, targetItem = options.item || {}, currentUri = currentItem.uri, currentKey = currentItem.key, targetKey = targetItem.key, args; if (uri === undefined) { 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 && (targetKey === undefined || 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 back() { return this._navigationDevice.back(); }, previousItem: function previousItem() { return this._previousItem; }, currentItem: function currentItem(item) { if (arguments.length > 0) { if (!item) { throw errors.Error("E3023"); } this._setCurrentItem(item); } else { return this._currentItem; } }, rootUri: function rootUri() { return this._currentItem && this._currentItem.uri; }, canBack: function canBack() { return true; }, saveState: commonUtils.noop, restoreState: commonUtils.noop, removeState: commonUtils.noop }).include(EventsMixin); var StackBasedNavigationManager = HistoryBasedNavigationManager.inherit({ ctor: function ctor(options) { options = options || {}; this.callBase(options); this._createNavigationStacks(options); hardwareBackButton.add(this._deviceBackInitiated.bind(this)); this._stateStorageKey = options.stateStorageKey || STORAGE_HISTORY_KEY; }, init: function init() { return this._navigationDevice.init(); }, _createNavigationDevice: function _createNavigationDevice(options) { if (!options.navigationDevice) { options.navigationDevice = new navigationDevices.StackBasedNavigationDevice(); } this.callBase(options); this._navigationDevice.backInitiated.add(this._deviceBackInitiated.bind(this)); }, _uriChangedHandler: function _uriChangedHandler(uri) { this.navigate(uri); }, _createNavigationStacks: function _createNavigationStacks(options) { this.navigationStacks = {}; this._keepPositionInStack = options.keepPositionInStack; this.currentStack = new NavigationStack(); }, _deviceBackInitiated: function _deviceBackInitiated() { if (!hideTopOverlay()) { this.back({ isHardwareButton: true }); } else { this._syncUriWithCurrentNavigationItem(); } }, _getDefaultOptions: function _getDefaultOptions() { return { target: NAVIGATION_TARGETS.blank }; }, _createNavigationStack: function _createNavigationStack() { var result = new NavigationStack(); result.itemsRemoved.add(this._removeItems.bind(this)); return result; }, _setCurrentItem: function _setCurrentItem(item) { this._setCurrentStack(item.stack); this.currentStack.currentItem(item); this.callBase(item); this._syncUriWithCurrentNavigationItem(); }, _setCurrentStack: function _setCurrentStack(stackOrStackKey) { var stack, stackKey; if (typeof stackOrStackKey === "string") { 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 _getViewTargetStackKey(uri, isRoot) { var result; if (isRoot) { if (this.navigationStacks[uri] !== undefined) { 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 _updateHistory(uri, options) { var isRoot = options.root, forceIsRoot = isRoot, forceToRoot = false, previousStack = this.currentStack, keepPositionInStack = options.keepPositionInStack !== undefined ? 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 (options.direction === undefined) { 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 = options.direction === "forward" ? "backward" : "none"; } options.root = forceIsRoot; this._currentItem = this.currentStack.currentItem(); this._syncUriWithCurrentNavigationItem(); }, _removeItems: function _removeItems(items) { var that = this; iteratorUtils.each(items, function (index, item) { that.fireEvent("itemRemoved", [item]); }); }, back: function back(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 rootUri() { return this.currentStack.items.length ? this.currentStack.items[0].uri : this.callBase(); }, canBack: function canBack(stackKey) { var stack = stackKey ? this.navigationStacks[stackKey] : this.currentStack; return stack ? stack.canBack() : false; }, saveState: function saveState(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 restoreState(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 removeState(storage) { storage.removeItem(this._stateStorageKey); }, currentIndex: function currentIndex() { return this.currentStack.currentIndex; }, previousItem: function previousItem(stackKey) { var stack = this.navigationStacks[stackKey] || this.currentStack; return stack.previousItem(); }, getItemByIndex: function getItemByIndex(index) { return this.currentStack.items[index]; }, clearHistory: function clearHistory() { this._createNavigationStacks({ keepPositionInStack: this._keepPositionInStack }); }, itemByKey: function itemByKey(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 currentItem(itemOrItemKey) { var item; if (arguments.length > 0) { if (typeof itemOrItemKey === "string") { item = this.itemByKey(itemOrItemKey); } else if (isPlainObject(itemOrItemKey)) { item = itemOrItemKey; } this.callBase(item); } else { return this.callBase(); } } }); var NavigationStack = Class.inherit({ ctor: function ctor(options) { options = options || {}; this.itemsRemoved = Callbacks(); this.clear(); }, currentItem: function currentItem(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 previousItem() { return this.items.length > 1 ? this.items[this.currentIndex - 1] : undefined; }, canBack: function canBack() { return this.currentIndex > 0; }, clear: function clear() { this._deleteItems(this.items); this.items = []; this.currentIndex = -1; }, back: function back(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 forward() { this.currentIndex++; if (this.currentIndex >= this.items.length) { throw errors.Error("E3009"); } }, navigate: function navigate(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 itemByKey(key) { for (var i = 0; i < this.items.length; i++) { var item = this.items[i]; if (item.key === key) return item; } }, _updateItem: function _updateItem(index, uri) { var item = this.items[index]; item.uri = uri; item.key = this.items[0].uri + "_" + index + "_" + uri; }, _deleteItems: function _deleteItems(items) { if (items) { this.itemsRemoved.fire(items); } } }); ///#DEBUG HistoryBasedNavigationManager.NAVIGATION_TARGETS = NAVIGATION_TARGETS; ///#ENDDEBUG exports.HistoryBasedNavigationManager = HistoryBasedNavigationManager; exports.StackBasedNavigationManager = StackBasedNavigationManager; exports.NavigationStack = NavigationStack;