tao-test/app/tao/views/js/layout/generisRouter.js

198 lines
7.7 KiB
JavaScript

/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2017 (original work) Open Assessment Technologies SA;
*/
/**
* The purpose of this router is to allow navigation between Generis views entities (sections, tree items...).
* It does not dispatch any controller (that's the backoffice.js' job) but ensures that the browser history has
* a consistent state and URLs. On history move, it triggers event listened by the section manager and the tree.
* Those module actually do the job of restoring the route state.
**
* @author Christophe Noël <christophe@taotesting.com>
*/
define([
'jquery',
'lodash',
'core/eventifier',
'util/url'
], function(
$,
_,
eventifier,
urlUtil
) {
'use strict';
/**
* Keep track of the latest known state
*/
var topState;
/**
* The router instance
*/
var generisRouter = eventifier({
/**
* To be called on section initial loading or section change.
* This method create a new history state or replace the current one. It might be called as a convenient way
* to add the sectionId to the current browser Url. In that case, history.replaceState() will be used.
* Otherwise, history.pushState().
*
* @param {String} baseUrl - a base on which to build the stateUrl. Most of the time, it is the current URL from the call point.
* @param {String} sectionId - to be saved in the state and added to the Url
* @param {('activate'|'show')} restoreWith - the method needed to restore the section
*/
pushSectionState: function pushSectionState(baseUrl, sectionId, restoreWith) {
var parsedUrl = urlUtil.parse(baseUrl);
var currentQuery = _.mapValues(parsedUrl.query, function(value, key) {
return (key === 'uri') ? decodeURIComponent(value) : value;
});
var newQuery = _.clone(currentQuery);
var baseUrlHasSection = currentQuery.section;
var stateUrl;
var newState = {
sectionId: sectionId,
restoreWith : restoreWith || 'activate',
nodeUri: currentQuery.uri
};
if (!baseUrlHasSection) {
// adding missing section parameter
newQuery.section = sectionId;
} else if (sectionId !== currentQuery.section) {
// changing section, we need to remove any uri
newQuery.section = sectionId;
delete newQuery.uri;
delete newState.nodeUri;
}
if (sectionId && !_.isEqual(currentQuery, newQuery)) {
stateUrl = urlUtil.build(parsedUrl.path, newQuery);
if (baseUrlHasSection) {
window.history.pushState(newState, null, stateUrl);
this.trigger('pushsectionstate', stateUrl);
} else {
window.history.replaceState(newState, null, stateUrl);
this.trigger('replacesectionstate', stateUrl);
}
topState = newState;
}
},
/**
* To be called on node selection in the tree.
* This method create a new history state or replace the current one. It might be called as a convenient way
* to add the Uri parameter to the current browser Url. In that case, history.replaceState() will be used.
* Otherwise, history.pushState().
*
* @param {String} baseUrl - a base on which to build the stateUrl. Most of the time, it is the current URL from the call point.
* @param {String} nodeUri - to be saved in the state and added to the Url. Should be given as a plain non-encoded URI (ex: http://tao/mytao.rdf#i151378052813779)
*/
pushNodeState: function pushNodeState(baseUrl, nodeUri) {
var parsedUrl = urlUtil.parse(baseUrl);
var currentQuery = _.mapValues(parsedUrl.query, function(value, key) {
return (key === 'uri') ? decodeURIComponent(value) : value;
});
var newQuery = _.clone(currentQuery);
var baseUrlHasUri = currentQuery.uri;
var currentState = window.history.state || {};
var newState = {
sectionId: currentState.sectionId || currentQuery.section || '',
restoreWith : currentState.restoreWith || 'activate',
nodeUri: nodeUri
};
var stateUrl;
if (nodeUri !== currentQuery.uri) {
newQuery.uri = nodeUri;
}
if (nodeUri && !_.isEqual(currentQuery, newQuery)) {
stateUrl = urlUtil.build(parsedUrl.path, newQuery);
if (baseUrlHasUri) {
window.history.pushState(newState, null, stateUrl);
this.trigger('pushnodestate', stateUrl);
} else {
window.history.replaceState(newState, null, stateUrl);
this.trigger('replacenodestate', stateUrl);
}
topState = newState;
}
},
/**
* Restore a state from the history, by triggering events relevant to the retrieved state.
* @param {Boolean} fromPopState - if this method has been called following a popState event
*/
restoreState: function restoreState(fromPopState) {
var state = window.history.state || {};
if(this.hasRestorableState()){
// generisRouter has already been used
if (fromPopState) {
topState = topState || {};
// changing section
if (topState.sectionId !== state.sectionId) {
this.trigger('section' + state.restoreWith, state.sectionId);
// changing uri
} else if (state.nodeUri) {
this.trigger('urichange', state.nodeUri, state.sectionId);
}
// we are restoring in section initialisation: we only need to deal with the section,
// as uri will be read and set during tree initialisation
} else {
this.trigger('section' + state.restoreWith, state.sectionId);
}
topState = state;
}
},
/**
* Check that the current state contains the minimum information to restore a state
*/
hasRestorableState: function hasRestorableState() {
var state = window.history.state;
return state && state.restoreWith && state.sectionId;
},
/**
* Add the listener that triggers the actual routing events
*/
init: function init() {
$(window).on('popstate.generisRouter', function () {
generisRouter.restoreState(true);
});
},
/**
* Removes the popstate listener
*/
destroy: function destroy() {
$(window).off('.generisRouter');
}
});
return generisRouter;
});