@coon-js/extjs-comp-navport
Version:
NPM package providing enhanced viewport template for rapid, modular, package agnostic Sencha ExtJS application building.
489 lines (396 loc) • 16.5 kB
JavaScript
/**
* coon.js
* extjs-comp-navport
* Copyright (C) 2017-2021 Thorsten Suckow-Homberg https://github.com/coon-js/extjs-comp-navport
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* The View Controller for the NavigationViewport.
*
*/
Ext.define("coon.navport.view.controller.NavigationViewportController", {
extend: "Ext.app.ViewController",
requires: [
"coon.navport.view.pages.Page404",
"coon.navport.model.NavigationModel"
],
alias: "controller.cn_navport-ctrl",
/**
* Stores the current view of the viewport.
* @protected
*/
currentView: null,
control: {
"cn_navport-navtree": {
selectionchange: "onNavigationTreeSelectionChange"
},
"cn_navport-tbar > button[reference=cn_navport_ref_hidenavbtn]": Ext.isModern ? {
tap: "onHideNavigationClick"
} : {click: "onHideNavigationClick"}
},
/**
* Adds permanent navigation items to this view's NavigationToolbar.
*
* @param {Array} items
* @returns {Array} an array with the itemIds of the items that were added
* to the toolbar
*
* @throws bubbles all the exceptions thrown by
* coon.navport.view.NavigationToolbar#addPermanentNavigation
*
* @see coon.navport.view.NavigationToolbar#addPermanentNavigation
*/
addPermaNavItems: function (items) {
var me = this,
tbar = me.lookup("cn_navport_ref_tbar");
return tbar.addPermanentNavigation(items);
},
/**
* Creates the main navigation from the passes array and will consider the
* "nodeNav" entries to properly build up the node id associated navigation
* for the nodes.
*
* @param {Array} items
*
* @throws bubbles the exeptions from the referenced methods
*
* @see #createNavigationModelFrom
*/
addMainNavigationItems: function (items) {
var me = this,
navItem = null,
tbar = me.lookup("cn_navport_ref_tbar"),
view = me.getView(),
navTree = view.lookup("cn_navport_ref_conwrap").lookup("cn_navport_ref_navtree"),
store = navTree.getStore(),
navigationModel, nodeNavItems;
if (!Ext.isArray(items)) {
Ext.raise({
sourceClass: Ext.getClassName(this),
items: items,
msg: Ext.getClassName(this) + "#addNavigationItems needs items to be an array"
});
}
for (var i = 0, len = items.length; i < len; i++) {
navItem = items[i];
[navigationModel, nodeNavItems] = me.createNavigationModelFrom(navItem);
for (let [nodeId, nodeNav] of Object.entries(nodeNavItems)) {
tbar.addNodeNavigation(nodeNav, nodeId);
}
//if (navItem.nodeNav) {
// tbar.addNodeNavigation(navItem.nodeNav, navigationModel.getId());
//}
store.getRootNode().appendChild(navigationModel);
}
},
/**
* Hides the NavigationTree
*
* @param {Boolean} hide True to hide the NavigationTree, otherwise false.
*/
hideNavigation: function (hide) {
var me = this,
view = me.getView(),
contentWrap = view.lookup("cn_navport_ref_conwrap"),
navTree = contentWrap.lookup("cn_navport_ref_navtree");
navTree.setHidden(hide === true);
contentWrap.updateLayout({isRoot: true});
},
/**
* Adds the specified view associated with the hash to the content panel of
* the Viewport. The hash's associated view is looked up in the NavigationTree's
* store and its collection of {@link coon.navport.model.NavigationModel}s.
* Additionally, the NavigationToolbar is requested to show the id associated
* navigation for the view#s associated node, if any.
* If the view is instantiated and the corresponding navigation has a packageController
* configured with a valid PackageController loaded into the app, the controller's
* "configureView()"-method is called with the the following arguments in the following
* order:
* - view: The view either created or re-used for the view
* - created: true if the view was just created for the first time, otherwise false,
* e.g. when it becomes activated in its container
* - hash: The hash this method was called with
* - node: The {coon.navport.model.NavigationModel} associated with the hash.
*
* Flow:
* =====
*
* yes
* route -> view for route existing? -----> configureView([view], false, [hash], [node])
* | /|\ |
* | no | |
* \|/ | |
* [view] = Ext.create(...) | \|/
* | | setActiveItem([view])
* | |
* \|/ |
* configureView([view], true, [hash], [node])
*
*
* @param {String} hash
* @param {String} defaultToken
*
* @return {Ext.Component} The view associated with the hash, or null
*
* @see coon.navport.view.NavigationToolbar#showNavigationForNode
*/
addViewForHash: function (hash, defaultToken) {
const
me = this,
conwrap = me.lookup("cn_navport_ref_conwrap"),
navTree = conwrap.lookup("cn_navport_ref_navtree"),
store = navTree.getStore(),
contentPanel = conwrap.lookup("cn_navport_ref_conctr");
let newView,
node;
hash = (hash || "").toLowerCase();
if (hash === "") {
return null;
}
node = store.getRoot().findChildBy(function (record) {
var route = (record.get("route") || "").toLowerCase();
if (route === hash) {
return true;
}
}, null, true);
if (!node) {
me.nodeNotFound(hash, defaultToken);
return;
}
/**
* Suspend events of the nav tree here so selection of the node
* does not re-trigger the processRoute method in case this method
* is called from outside (deeplinking)
*/
navTree.suspendEvents();
navTree.setSelection(node);
navTree.resumeEvents();
// look up the nodeNavigation to use
me.activateNodeNavigation(node);
if (node.get("view")) {
let viewCfg = node.get("view"),
pctrl, configurable = false;
if (node.get("packageController")) {
pctrl = Ext.getApplication().getController(node.get("packageController"));
configurable = pctrl && Ext.isFunction(pctrl.configureView);
}
if (Ext.isString(viewCfg)) {
viewCfg = {xclass: viewCfg};
}
// try to find an existing view first, given the injected
// cn_routeId-property or a unique id
if (viewCfg.id) {
// identify with id
newView = Ext.ComponentManager.get(viewCfg.id);
} else {
// identify by cn_routeId
// apply the routeId if the view is not identifiable
// via a given id
viewCfg.cn_routeId = hash;
newView = contentPanel.down("component[cn_routeId=" + hash + "]");
}
if (newView) {
// call template method "configureView"
// if view is existing and gets set as activeItem for
// the mainView again, with "created"-arg set to false
if (configurable) {
newView = pctrl.configureView(newView, false, hash, node);
}
contentPanel.setActiveItem(newView);
} else {
newView = Ext.create(viewCfg);
// call template method "configureView"
// if view was newly created, "created"-arg set to true
if (configurable) {
newView = pctrl.configureView(newView, true, hash, node);
}
if (!(newView instanceof Ext.Window)) {
Ext.suspendLayouts();
// configureView, "created"-arg set to false
if (configurable) {
newView = pctrl.configureView(newView, false, hash, node);
}
contentPanel.setActiveItem(contentPanel.add(newView));
Ext.resumeLayouts(true);
}
}
if (newView.isFocusable(true)) {
newView.focus();
}
me.currentView = newView;
return newView;
}
return null;
},
/**
* Shows a notification that the node for the requested route was not found.
*
* @param {String} hash The has that could not be matched
* @param{String} defaultToken a token that can be used to privde a working url
* to return to
*
* see {@link coon.navport.view.NavigationViewport#showUnmatchedRouteNotification}
*/
nodeNotFound: function (hash, defaultToken) {
var me = this,
view = me.getView();
me.currentView = view.showUnmatchedRouteNotification(hash, defaultToken);
},
/**
* Callback for the {@link coon.navport.view.NavigationTree#selectionchange}
* event. Will pass the model to {@link #redirectTo} which uses the model's
* {@link Ext.data.Model#toUrl} method to compute the route.
*
* @param {coon.navport.view.NavigationTree} tree
* @param {coon.navport.model.NavigationModel} node
*
* see {@link #redirectTo}
* see {@link coon.navport.model.NavigationModel#toUrl}.
*/
onNavigationTreeSelectionChange: function (tree, node) {
const me = this;
// Modern Toolkit will remove all items in the tree when
// involved in a destroy()-process, sending "null"-nodes
// to this observer. check here if that is the case
// and exit silently.
// Additionally, selection might sometimes be null - if
// no node is available, we will exit here.
if (node === null) {
return;
}
me.redirectTo(node);
},
/**
* Callback for the {@link coon.navport.view.NavigationToolbar}'s
* hideNavigation Button.
*
* @param {Ext.Button} btn
*
* see {@link #hideNavigation}
*/
onHideNavigationClick: function (btn) {
var me = this,
view = me.getView(),
navTree = view.lookup("cn_navport_ref_conwrap").lookup("cn_navport_ref_navtree");
me.hideNavigation(navTree.isVisible() === true);
},
privates: {
/**
* Looks up information in the passed node to decide which node-navigation
* should be displayed. Displays the passed node's node-navigation if
* available, otherwise it searches its parents if the inheritNodeNav-field
* is set to true and returns the first node-navigation found.
* @param {coon.navport.model.NavigationModel} node
*
* @returns {null|String} Returns null if there is no node-navigation available for
* the specified node.
*/
activateNodeNavigation: function (node) {
const
me = this,
tbar = me.lookup("cn_navport_ref_tbar");
let idForNodeNav, pNode = node;
while (pNode) {
idForNodeNav = pNode.getId();
if (!pNode.get("inheritNodeNav")) {
break;
}
pNode = pNode.parentNode;
}
tbar.showNavigationForNode(idForNodeNav);
if (!tbar.hasNodeNavigation(idForNodeNav)) {
idForNodeNav = null;
}
return idForNodeNav;
},
/**
* Returns true if the NavigationViewport can safely manage to close the
* currentView.
*
* @returns {boolean}
*/
isCurrentViewClosable: function () {
var me = this;
if (me.currentView && (Ext.isFunction(me.currentView.canNavigationViewportCloseView))) {
return me.currentView.canNavigationViewportCloseView();
}
return false;
},
/**
* Helper for closin the current view.
*/
closeCurrentView: function () {
var me = this;
me.currentView.close();
me.currentView = null;
},
/**
* Creates a coon.navport.model.NavigationModel from the
* passed config by making sure the created model is set up properly.
*
* @param {Object} config
*
* @returns {Array} The first index representing the {coon.navport.model.NavigationModel},
* the second index holding an object mapping all NavigationModel-ids with their node-navigation,
* if available.
*
* @throws if the configuration was not valid
*/
createNavigationModelFrom: function (config) {
var me = this,
mandatoryFields = ["text", "route"],
manField, navCon, nodeNavItems = {};
for (var a = 0, lena = mandatoryFields.length; a < lena; a++) {
manField = mandatoryFields[a];
if (!Object.prototype.hasOwnProperty.call(config, manField) ||
!config[manField]) {
Ext.raise({
sourceClass: Ext.getClassName(this),
config: config,
msg: Ext.getClassName(this) + "#createNavigationModelFrom found an invalid configuration for a navigation item"
});
}
}
if (config.leaf === undefined) {
if (config.children === undefined) {
config.leaf = true;
} else if (Ext.isArray(config.children)) {
config.leaf = false;
}
}
navCon = Ext.copy({}, config, "leaf,route,view,id,text,iconCls,packageController,inheritNodeNav");
let node = Ext.create(
"coon.navport.model.NavigationModel", navCon
);
if (Ext.isArray(config.nodeNav) && config.nodeNav.length) {
nodeNavItems[node.getId()] = config.nodeNav;
}
if (config.children) {
config.children.forEach(function (child) {
let [subNode, subNodeNav] = me.createNavigationModelFrom(child);
Ext.apply(nodeNavItems, subNodeNav);
node.appendChild(subNode);
});
}
return [node, nodeNavItems];
}
}
});