define([
'core/js/api/Listenable',
'core/js/api/ListenerBuilder',
'core/js/api/StorageProvider',
'core/js/api/utils/RandomUtils',
'core.dt/js/api/BackendStorage',
'core.dt/js/api/application/ApplicationSupport',
'core.dt/js/api/undo/UndoRedoManager',
'layout/js/api/LayoutConstants',
'pages.dt/js/PageFactory',
'pages.dt/js/PagesManager',
'pages.dt/js/api/Navigation',
'pages.dt/js/api/Page',
'pages.dt/js/api/ViewGeneratorModes',
'translations/js/api/I18n'
], function(
Listenable,
ListenerBuilder,
StorageProvider,
RandomUtils,
BackendStorage,
ApplicationSupport,
undoRedoManager,
LayoutConstants,
pageFactory,
PagesManager,
navigation,
Page,
ViewGeneratorModes,
I18n) {
'use strict';
/**
* Basic application pages management support. This support is useful for obtaining
* all {@link pages.dt/js/api/Pages.getPages pages} or the particular {@link pages.dt/js/api/Page page}
* instance registered in the opened application.</p>
*
* <p>This is one of the base modules used in ABCS if you are going
* to work with pages.</p>
*
* @AbcsExtension stable
* @version 16.3.5
*
* @private
* @singleton
* @exports pages.dt/js/api/Pages
* @constructor
*/
/*
* You can use this object internally to:<br>
* 1) get a list of application pages: getPages()/getPageIds()/getPage()<br>
* 2) create a new page: createPage()<br>
* 3) remove an existing page: removePage()<br>
* 4) listen on the Pages object for following change events:
* pageCreated,
* pageRemoved,
* activePageUpdated,
* homepageSet.
*/
var Pages = function() {
AbcsLib.checkThis(this);
AbcsLib.checkSingleton(Pages);
Listenable.apply(this, arguments);
this._metadata = undefined;
this._metadataChangedListener = undefined;
this._pageChangedListener = null;
this._initializedListeners = false;
};
AbcsLib.mixin(Pages, Listenable);
// we needs to have reference to the singleton internally
var pages = AbcsLib.initSingleton(Pages);
Pages._LOGGER = Logger.get('pages.dt/js/api/Pages');
Pages._ERROR_FIND_PAGE_TOP_VIEW = 'Trying to find a page for PAGE TOP VIEW ID which is the same for every page. Use "activePage.getView()" instead.';
/**
* Active page changed event.
* @event pages.dt/js/api/Pages#EVENT_ACTIVE_PAGE_UPDATED
* @property {pages.dt/js/api/Page} page page to be activated
*/
Pages.EVENT_ACTIVE_PAGE_UPDATED = 'activePageUpdated';
/**
* @event pages.dt/js/api/Pages#EVENT_PAGE_CREATED
* @property {pages.dt/js/api/Page} page created page
*/
Pages.EVENT_PAGE_CREATED = 'pageCreated';
/**
* @event pages.dt/js/api/Pages#EVENT_PAGE_REMOVED
* @property {pages.dt/js/api/Page} page removed page
*/
Pages.EVENT_PAGE_REMOVED = 'pageRemoved';
/**
* @event pages.dt/js/api/Pages#EVENT_PAGE_CHANGED
* @property {pages.dt/js/api/Page} page changed page
*/
Pages.EVENT_PAGE_CHANGED = 'pageChanged';
/**
* @event pages.dt/js/api/Pages#EVENT_HOMEPAGE_SET
*/
Pages.EVENT_HOMEPAGE_SET = 'homepageSet';
/**
* @event pages.dt/js/api/Pages#EVENT_MODE_CHANGED
* @property {pages.dt/js/api/ViewGeneratorModes} mode the newly set mode
*/
Pages.EVENT_MODE_CHANGED = 'modeChanged';
/**
* Initializes listening on other modules.
*/
Pages.prototype._initListeners = function() {
// we need to guarantee that the listeners initialization will be called just once
if (this._initializedListeners) {
return;
}
var self = this;
this._initializedListeners = true;
// XXX - unfify these call with the rest of the class to invoke i.e. the same base methods
// handles PagesManager notifications from the backend
PagesManager.getInstance().addListener(new ListenerBuilder(PagesManager.EVENT_PAGE_REMOVED, function(event) {
var page = self.pages[event.pageId];
delete self.pages[event.pageId];
self.fireEvent(Pages.EVENT_PAGE_REMOVED, {
page: page,
userId: event.userId
});
// case of the removed active page
self._handleNavigationFromActivePage(event.pageId);
}).event(PagesManager.EVENT_PAGE_CHANGED, function(event) {
self._refreshChangedPage(event);
}).event(PagesManager.EVENT_PAGES_METADATA_CHANGED, function(definition) {
// reset currently stored metadata
self._metadata = self._createMetadata(definition);
}).build());
};
/**
* Gets pages specific metadata.
*
* @return {Pages.Metadata} pages's metadata
*/
Pages.prototype.getMetadata = function() {
if (this._metadata === undefined) {
throw new Error('Pages metadata can\'t be undefinded.');
}
return this._metadata;
};
/**
* Initializes the Pages singleton for the given application context.
* Do not use! Supposed to be called exclusively from ApplicationSupport.setActiveApplication()
*/
Pages.prototype.setContext = function(activateHomePage) {
var self = this;
self.pages = self._createPages();
self._metadata = self._createMetadata();
// initialize navigation object
navigation.__initialize(this);
if (ApplicationSupport.isInGenerationMode()) {
//special app-sources-generation mode (runs in node -- see the app-gen.js script)
self.mode = ViewGeneratorModes.MODE_DEPLOYMENT;
} else {
//normal designer mode
self.mode = ViewGeneratorModes.MODE_DESIGNER;
if (activateHomePage) {
navigation.setActivePageId(self.getHomepageId());
}
}
// initialize listeners on PagesManager changes
this._initListeners();
};
/**
* Loads definition from the PagesManager for particular pageID and creates
* corresponding page instance.
*
* @param {String} id page ID
* @returns {Page} created page
*/
Pages.prototype._createPage = function(id) {
var definition = PagesManager.getInstance().getPageDefinition(id);
var page = this._initializePage(definition.id, definition);
return page;
};
/**
* Initializes the Pages module since it loads all page definitions from the
* pages manager and creates instances of their pages.
*
* @returns {Page[]} full pages list
*/
Pages.prototype._createPages = function() {
var self = this;
var pages = {};
PagesManager.getInstance().getIds().forEach(function(id) {
var page = self._createPage(id);
pages[page.getId()] = page;
});
return pages;
};
/**
* Loads the metadata definition file from the pages manager and returns the created metadata instance.
*
* @returns {Pages.Metadata} metadata object
*/
Pages.prototype._createMetadata = function(metadataDefinition) {
// create metadata
metadataDefinition = metadataDefinition || PagesManager.getInstance().getMetadataDefinition();
var metadata = Pages.Metadata.fromDefintion(metadataDefinition);
// enable listening on them
metadata.addListener(this._getMetadataChangedListener());
return metadata;
};
Pages.prototype._checkInitialised = function() {
if (!this.isInitialised()) {
throw new Error('No Pages.setContext() has been called!');
}
};
/**
* Gets information whether the Pages is initialized or not
*
* @return {Boolean} true if Pages is initialized, false otherwise
*/
Pages.prototype.isInitialised = function() {
return this.pages !== undefined;
};
Pages.prototype.getPagesIds = function() {
return this.getPageIds();
};
/**
* Gets list of existing page IDs in the application.
*
* @AbcsExtension unstable
*
* @see {@link module:pages.dt/js/api/Pages.getPages Pages.getPages}
* @returns {String[]} list of all application's page IDs
*
* @example
* <caption>
* Get count of all available pages.
* </caption>
* // synchronous access used to make the sample more readable,
* // you should always use async define by getting Pages
* var Pages = require('pages.dt/js/api/Pages');
* var pageCount = Pages.getPageIds().length;
*
* @example
* <caption>
* Seeks and creates unique page ID across available pages.
* </caption>
* // synchronous access used to make the sample more readable,
* // you should always use async define by getting Pages
* var Pages = require('pages.dt/js/api/Pages');
*
* var count = 2;
* var customPageId = 'myUniqueId';
* // checks whether the customPageId is unique across all page IDs and
* // increases its suffix number if not until unique page ID is found
* while (Pages.getPageIds().indexOf(customPageId) !== -1) {
* customPageId = customPageId + '_' + count;
* count++;
* }
* console.log('This is unique page ID which can be used: ' + customPageId);
*/
Pages.prototype.getPageIds = function() {
AbcsLib.checkParameterCount(arguments, 0);
this._checkInitialised();
return Object.keys(this.pages);
};
Pages.prototype.pageExists = function (pageId) {
AbcsLib.checkParameterCount(arguments, 1);
AbcsLib.checkDefined(pageId, 'pageId');
var ids = this.getPageIds();
for (var i = 0; i < ids.length; i++) {
if (ids[i] === pageId) {
return true;
}
}
return false;
};
/**
* Gets the {@link pages.dt/js/api/Page Page} for the given ID.
* <p>
* If the ID is not available in the list of all application's pages,
* <code>undefined</code> value is returned.
*
* @AbcsExtension stable
* @version 16.3.5
*
* @param {String} pageId ID of the page to obtain
* @returns {pages.dt/js/api/Page} page instance for supplied ID if such
* {@link pages.dt/js/api/Page Page} exists, <code>undefined</code> otherwise
*
* @example <caption>
* Gets display name of the home page and shows it inside a notification.
* </caption>
* // gets page's ID of the homepage
* var homepageId = Pages.getHomepageId();
* // gets the page instance by its ID
* var page = Pages.getPage(homepageId);
* // show notification in the ABCS's UI
* Abcs.UI().showNotification(Abcs.UI().Notification.create({
* message: 'Your homepage is called \'' + page.getDisplayName() + '\'.'
* });
*/
Pages.prototype.getPage = function(pageId) {
AbcsLib.checkParameterCount(arguments, 1);
AbcsLib.checkDefined(pageId, 'pageId');
this._checkInitialised();
return this.pages[pageId];
};
/**
* Gets list of existing pages in the ABCS application.
*
* @AbcsExtension stable
* @version 16.3.5
*
* @returns {pages.dt/js/api/Page[]} list of all application's pages
*
* @example
* <caption>
* Gets IDs of all pages.
* </caption>
* // synchronous access used to make the sample more readable,
* // you should always use async define for getting Pages
* var Pages = require('pages.dt/js/api/Pages');
* var ids = [];
* // gets all pages
* var pages = Pages.getPages();
* // iterates through pages and collects their IDs
* pages.forEach(function(page) {
* ids.push(page.getId();
* });
* // prints the all pages IDs
* console.log('Pages of your application are: ' + JSON.stringify(ids));
*/
Pages.prototype.getPages = function() {
AbcsLib.checkParameterCount(arguments, 0);
var self = this;
var pages = [];
self.getPageIds().forEach(function(id) {
pages.push(self.getPage(id));
});
return pages;
};
/**
* Creates a new page
* @param {string} pageId
* @param {JSON} [definition] definition to be used by page creation
* @return {Page}
* @fires pages.dt/js/api/Pages#EVENT_PAGE_CREATED
*/
Pages.prototype.createPage = function(pageId, definition) {
this._checkInitialised();
var self = this;
if (!pageId) {
// make rand string appendix (a-z0-9)
pageId = 'newPage_' + RandomUtils.randomString(6);
}
var baseDefinition = StorageProvider.getStorage().getSync(BackendStorage.PATH_SYSTEM_TEMPLATES + '/newPageTemplate.json');
var page = self._initializePage(pageId, $.extend(definition || {}, baseDefinition));
self.pages[pageId] = page;
page.saveChanges();
self.fireEvent(Pages.EVENT_PAGE_CREATED, {page: page});
Pages._LOGGER.debug('Created page ' + page.getId() + '.');
return page;
};
/**
* Removes the given pages.
* <p>
* <b>If the given page is the active page, the client need to navigate to
* other page by himself.</b>
*
* @param {type} page page to be deleted
* @fires pages.dt/js/api/Pages#EVENT_PAGE_REMOVED
*/
Pages.prototype.removePage = function(page, cleanup) {
var self = this;
self._checkInitialised();
page.removeListener(self._getPageChangedListener());
var pageId = page.getId();
undoRedoManager.groupChanges({
run: function () {
// handle if the page would be homepage or active page
self._handleHomepageRemoval(pageId);
self._handleNavigationFromActivePage(pageId);
delete self.pages[pageId];
PagesManager.getInstance().removePage(page);
// Wipe out all page data if clean up was true (or omitted)
if (cleanup === true || cleanup === undefined) {
AbcsLib.Translations.deleteTranslatedString(I18n.key(I18n.Type.Page, page.getId()));
AbcsLib.Translations.deleteTranslatedString(I18n.key(I18n.Type.MainMenu, page.getId()));
}
self.fireEvent(Pages.EVENT_PAGE_REMOVED, {page: page});
Pages._LOGGER.debug('Removed page ' + page.getId() + '.');
},
description: AbcsLib.i18n('pagesDt.undoPageRemoveDescription', {
page: page.getDisplayName().toString()
}),
afterUndoAction: function (undoFinished) {
return undoFinished && Abcs.Pages().navigateToPage(pageId);
}
});
};
/**
* Handles navigation from the page if the given page is active at the moment.
*
* @param {String} pageId ID of the examined page
*/
Pages.prototype._handleNavigationFromActivePage = function(pageId) {
if (navigation.getActivePageId() === pageId) {
var nextPageId = this.getHomepageId() !== pageId ? this.getHomepageId() : this._getNextPageId(pageId);
if (nextPageId !== undefined) {
Abcs.Pages().navigateToPage(nextPageId);
} else {
Pages._LOGGER.warn('No other page we could switch as homepage exists.');
}
}
};
/**
* Handles homepage switch if the passed page is homepage at the moment.
*
* @param {String} pageId ID of the examined page
*/
Pages.prototype._handleHomepageRemoval = function(pageId) {
if (this.getHomepageId() === pageId) {
var nextPageId = this._getNextPageId(pageId);
if (nextPageId !== undefined) {
this.setHomepageId(nextPageId);
} else {
Pages._LOGGER.warn('No other page we could switch as active exists.');
}
}
};
Pages.prototype._navigateFromActivePage = function(pageId) {
if (navigation.getActivePageId() === pageId) {
var nextPageId = this._getNextPageId(pageId);
if (nextPageId !== undefined) {
Abcs.Pages().navigateToPage(nextPageId);
} else {
Pages._LOGGER.warn('No other page we could switch as active exists.');
}
}
};
Pages.prototype._getPageChangedListener = function () {
var self = this;
if (!self._pageChangedListener) {
self._pageChangedListener = new ListenerBuilder(Page.__EVENT_PAGE_CHANGED, function(page, pageDefinitionChanged) {
if (pageDefinitionChanged[0] === true) {
PagesManager.getInstance().storePage(page);
}
self.fireEvent(Pages.EVENT_PAGE_CHANGED, {
page: page,
origin: Pages.PageChangedOrigin.DESIGNER
});
}).event(Page.EVENT_PAGE_UPDATED, function (page) {
self.fireEvent(Pages.EVENT_ACTIVE_PAGE_UPDATED, page);
}).build();
}
return self._pageChangedListener;
};
Pages.prototype._getMetadataChangedListener = function () {
var self = this;
if (self._metadataChangedListener === undefined) {
self._metadataChangedListener = new ListenerBuilder(Pages.Metadata.EVENT_METADATA_CHANGED, function() {
PagesManager.getInstance().storeMetadata({
value: self._metadata.getDefinition()
});
}).build();
}
return self._metadataChangedListener;
};
/**
* Gets the {@link pages.dt/js/api/Page Page}'s ID of the homepage.
*
* <p>Every ABCS application has defined single {@link pages.dt/js/api/Page Page}
* which is used as the homepage. Homepage means that if the ABCS application is opened
* without route to specific page, the homepage is opened first then.</p>
*
* @AbcsExtension stable
* @version 16.3.5
*
* @return {String} homepage's ID
*
* @example <caption>Gets display name of the home page and shows it inside a notification.</caption>
* // gets page's ID of the homepage
* var homepageId = Pages.getHomepageId();
* // gets the page instance by its ID
* var page = Pages.getPage(homepageId);
* // show notification in the ABCS's UI
* Abcs.UI().showNotification(Abcs.UI().Notification.create({
* message: 'Your homepage is called \'' + page.getDisplayName() + '\'.'
* });
*/
Pages.prototype.getHomepageId = function() {
AbcsLib.checkParameterCount(arguments, 0);
return this.getMetadata().getHomepageId();
};
/**
* Sets new homepage ID.
*
* @param {String} homepageId page ID to be set as homepage
* @fires pages.dt/js/api/Pages#EVENT_HOMEPAGE_SET
*/
Pages.prototype.setHomepageId = function (homepageId) {
this._checkInitialised();
if (this.getHomepageId() !== homepageId) {
this._metadata.setHomepageId(homepageId);
this.fireEvent(Pages.EVENT_HOMEPAGE_SET);
}
};
/**
* Attemps to propose other page than the one obtained in the parameter.
*
* @param {String} pageIdToOmit page ID to be omitted from the choice
* @returns {String} another page ID to choose (switch to etc.)
*/
Pages.prototype._getNextPageId = function(pageIdToOmit) {
for (var key in this.pages) {
if (key !== pageIdToOmit) {
return key;
}
}
};
Pages.prototype._refreshChangedPage = function (event) {
var pageId = event.pageId;
var userId = event.userId;
var pageDefinition = event.pageDefinition;
var page = this.pages[pageId];
if (page) {
var displayName = I18n.deserializeIfTranslatable(pageDefinition.displayName);
var viewModelDefinition = page.getViewModelDefinition();
page.__updateFromDefinition(
displayName,
pageDefinition.isModalPage,
pageDefinition.type,
pageDefinition.viewModel,
pageDefinition.view);
if (pageId === navigation.getActivePageId()) {
// multi-user change requires page update
if (!event.localUpdate
// undo/redo change with changed page's view model
|| (viewModelDefinition !== page.getViewModelDefinition())) {
navigation.switchActivePage(page);
}
}
this.fireEvent(Pages.EVENT_PAGE_CHANGED, {
page: page,
userId: userId,
origin: event.localUpdate
? Pages.PageChangedOrigin.UNDO_REDO
: Pages.PageChangedOrigin.MULTI_USER
});
} else {
page = this._initializePage(pageId, pageDefinition);
this.pages[pageId] = page;
this.fireEvent(Pages.EVENT_PAGE_CREATED, {
page: page,
userId: userId
});
}
};
/**
* Switches application to different mode.
*
* @param {ViewGeneratorModes} mode to be switched to
* @fires pages.dt/js/api/Pages#EVENT_MODE_CHANGED
*/
Pages.prototype.setMode = function(mode) {
if (this.mode !== mode) {
this.mode = mode;
this.fireEvent(Pages.EVENT_MODE_CHANGED, mode);
navigation.getActivePage().refresh();
}
};
/**
* Gets currently chosen application mode.
*
* @return {ViewGeneratorModes} current mode
*/
Pages.prototype.getMode = function() {
return this.mode;
};
Pages.prototype._initializePage = function(pageId, definition) {
var page = pageFactory.createPageFromDefinition(pageId, definition, this);
//listen on the created page
page.addListener(this._getPageChangedListener());
return page;
};
Pages.prototype.findPageForView = function(view) {
if (view.getId() === LayoutConstants.PAGE_TOP_VIEW_ID) {
throw Pages._ERROR_FIND_PAGE_TOP_VIEW;
}
var result = null;
var pageIds = this.getPageIds();
for (var i = 0; i < pageIds.length; i++) {
var pageId = pageIds[i];
var page = this.pages[pageId];
if (page.findSameView(view)) {
result = page;
break;
}
}
return result;
};
/**
* Pages metadata.
*
* @param {JSON} definitionJson definition JSON
*/
Pages.Metadata = function(definitionJson) {
AbcsLib.checkThis(this);
AbcsLib.checkDefined(definitionJson, 'definitionJson');
AbcsLib.checkDefined(definitionJson.homepage, 'definitionJson.homepage');
Listenable.apply(this, arguments);
this._homepageId = definitionJson.homepage;
};
AbcsLib.mixin(Pages.Metadata, Listenable);
Pages.Metadata.EVENT_METADATA_CHANGED = 'metadataChanged';
Pages.Metadata.fromDefintion = function(def) {
return new Pages.Metadata(def);
};
Pages.Metadata.prototype.getDefinition = function() {
var def = {};
def.homepage = this._homepageId;
return def;
};
Pages.Metadata.prototype.getHomepageId = function() {
return this._homepageId;
};
Pages.Metadata.prototype.setHomepageId = function(homepage) {
this._homepageId = homepage;
this.fireEvent(Pages.Metadata.EVENT_METADATA_CHANGED);
};
Pages.PageChangedOrigin = function () {
AbcsLib.throwStaticClassError();
};
Pages.PageChangedOrigin.UNDO_REDO = 'undoRedo';
Pages.PageChangedOrigin.MULTI_USER = 'multiUser';
Pages.PageChangedOrigin.DESIGNER = 'designer';
return pages;
});