define([
'core/js/api/Listenable',
'core/js/api/ListenerBuilder',
'core/js/api/utils/RandomUtils',
'core/js/api/utils/StringUtils',
'core/js/router/PageNavigationRouterStateObserver',
'core.dt/js/api/Utils',
'core.dt/js/api/application/ApplicationSupport',
'customcode.dt/js/CustomCodePageIntegration',
'layout/js/api/LayoutConstants',
'pagedesigner.dt/js/DesignerUtils',
'pagedesigner.dt/js/api/grid/GridUtils',
'pages.dt/js/ViewFactory',
'pages.dt/js/api/Navigation',
'pages.dt/js/api/PageType',
'pages.dt/js/api/ViewGeneratorSupport',
'translations/js/api/I18n',
'viewmodel/js/api/context/ContextualData',
'viewmodel.dt/js/api/ViewModelDefinition'
], function (
Listenable,
ListenerBuilder,
RandomUtils,
StringUtils,
pageNavigationRouterStateObserver,
CoreUtils,
ApplicationSupport,
CustomCodePageIntegration,
LayoutConstants,
DesignerUtils,
GridUtils,
ViewFactory,
navigation,
PageType,
ViewGeneratorSupport,
I18n,
ContextualData,
ViewModelDefinition
) {
'use strict';
/**
* Represents the Application's page object.
*
* <p>{@link pages.dt/js/api/Page Page} is one of the essential
* structural elements of the ABCS applications. The application consists
* of set of pages. Every {@link pages.dt/js/api/Page Page} holds created UI
* components in a tree structure - every page object has a top level
* {@link pages.dt/js/api/View View} as a root for the whole component hierarchy
* in the page.</p>
*
* <p>Every {@link pages.dt/js/api/Page Page} has defined {@link pages.dt/js/api/PageType PageType}.
* Types can be stated for free designing purposes, for CRUD scenarios etc.
* If the {@link pages.dt/js/api/Page page} is of {@link pages.dt/js/api/PageType.CREATE PageType.CREATE},
* {@link pages.dt/js/api/PageType.EDIT PageType.EDIT} or
* {@link pages.dt/js/api/PageType.DETAILS PageType.DETAILS} type, the page
* is always bound to the specific {@link entity/js/api/Entity entity} then.</p>
*
* @AbcsExtension stable
* @version 16.3.5
*
* @exports pages.dt/js/api/Page
* @private
* @constructor
*
* @param {string} id the page unique ID
* @param {string|Translatable} displayName the page displayName
* @param {boolean} isModalPage flag whether the page is modal page or not
* @param {PageType} type type of the page
* @param {object} viewModelDefinition
* @param {object} viewDefinition
* @param {Pages} Pages
*
* @see {@link pages.dt/js/api/Pages Pages} module for management of existing {@link pages.dt/js/api/Page page}s
* @see {@link pages.dt/js/api/PageType PageType} for description of supported page's types
* @see {@link pages.dt/js/api/View View} as the representation of UI components within page structure
*/
var Page = function (id, displayName, isModalPage, type, viewModelDefinition, viewDefinition, Pages) {
AbcsLib.checkThis(this);
Listenable.apply(this, arguments);
var self = this;
self._Pages = Pages; //need to pass & store Pages module since the constructor is called sync from within Pages constructor (require cycle)
self._id = id;
self.__updateFromDefinition(displayName, isModalPage, type, viewModelDefinition, viewDefinition);
self._refreshPageTask = CoreUtils.createTask(function (views) {
self.refresh(false, views); //refresh the page DOM and model
}, 0);
self._refreshPageTaskLazy = CoreUtils.createTask(function () {
self.refresh(); //refresh the page DOM and model
}, 500);
self._storePageTask = CoreUtils.createTask(function (pageDefinitionChange) {
self.fireEvent(Page.__EVENT_PAGE_CHANGED, self, pageDefinitionChange); //cause to be stored to backend
}, 0);
self.errors2notes = {
dom: {},
model: {},
binding: {},
misc: {}
};
};
AbcsLib.mixin(Page, Listenable);
Page._LOGGER = Logger.get('pages.dt/js/api/Page');
Page._GLOBAL_VIEW_MODEL_VERSION = 1;
//It's not deprecated to listen on the pageChanged event dirrectly.
// You should rather listen on Pages module.
Page.__EVENT_PAGE_CHANGED = 'pageChanged';
Page.EVENT_PAGE_UPDATED = 'pageUpdated';
Page.prototype.saveChanges = function (pageDefinitionChanged) {
var hasPageDefChanged = arguments.length === 0 || pageDefinitionChanged === true;
this._storePageTask.schedule(hasPageDefChanged);
};
Page.prototype._refreshAndStore = function (view) {
if (this.isActive()) {
this._refreshPageTask.schedule(view); //recreate the page is it is active (visible)
}
this._storePageTask.schedule(true);
};
/**
* Gets ID of the {@link pages.dt/js/api/Page Page}.
*
* <p>This ID is unique across the whole ABCS application. So the ID
* as such can be used for referencing, navigation and similar
* purposes.</p>
*
* @AbcsExtension stable
* @version 16.3.5
*
* @returns {string} page's ID
*
* @see {@link pages.dt/js/api/Pages Pages} module for management of existing {@link pages.dt/js/api/Page page}s
*
* @example <caption>Gets ID of the page and uses it for navigation purposes</caption>
* // we assume that you have the page instance referenced by 'page'
* var pageId = page.getId();
* // navigate to the particular page
* Abcs.Pages().navigateToPage(pageId);
*/
Page.prototype.getId = function () {
AbcsLib.checkParameterCount(arguments, 0);
return this._id;
};
/**
* Gets display name of the {@link pages.dt/js/api/Page Page}.
*
* <p>Page's display names needn't be unique. This naming is usually
* used for Designer purposes to address {@link pages.dt/js/api/Page Page}
* meaningfully to users.</p>
*
* @AbcsExtension stable
* @version 16.3.5
*
* @returns {Translatable} page's display name
*
* @example <caption>Gets display name of the active page</caption>
* // gets the active page
* var activePage = Navigation.getActivePage();
* // will yield display name
* activePage.getDisplayName();
*/
Page.prototype.getDisplayName = function () {
AbcsLib.checkParameterCount(arguments, 0);
return this._displayName;
};
/**
* Sets the page display name.
*
* @param {String|Translatable} displayName
*/
Page.prototype.setDisplayName = function (displayName) {
var wasTranslatable = I18n.isTranslatable(this._displayName);
this._displayName = wasTranslatable ? this._displayName :
I18n.createTranslatable(I18n.key(I18n.Type.Page, this._id, I18n.Suffix.DisplayName));
this._displayName.store(displayName.toString());
// refresh the viewModel instance since the display name is used in the PageViewModel
this._viewModelDefinition.refreshArchetype();
this.saveChanges(!wasTranslatable);
};
/**
* Gets the flag whether the page is a 'modal page' or not.
*
* @returns {boolean}
*/
Page.prototype.isModalPage = function () {
return this._isModalPage;
};
/**
* Gets type of the {@link pages.dt/js/api/Page Page} (like LANDING, CREATE, EDIT, DETAIL).
*
* <p>{@link pages.dt/js/api/PageType.CREATE PageType.CREATE},
* {@link pages.dt/js/api/PageType.EDIT PageType.EDIT},
* {@link pages.dt/js/api/PageType.DETAILS PageType.DETAILS}
* are the types of pages always bound to a specific BO record.
* {@link pages.dt/js/api/PageType.LANDING PageType.LANDING}
* type represents initially blank pages used for free designing.</p>
*
* @AbcsExtension stable
* @version 16.3.5
*
* @returns {pages.dt/js/api/PageType} type of the page
*
* @see {@link pages.dt/js/api/PageType PageType} for enumeration of all possible page types
*
* @example <caption>Gets type of the active page</caption>
* // gets the active page
* var activePage = Navigation.getActivePage();
* // will yield page's type
* activePage.getType();
*/
Page.prototype.getType = function () {
AbcsLib.checkParameterCount(arguments, 0);
return this._type;
};
/**
* Sets the type of the page (like LANDING, CREATE, EDIT, DETAIL).
*
* @param {PageType} type of the page
*/
Page.prototype.setType = function (type) {
this._type = type;
};
/**
* Sets the flag whether the page is a 'modal page' or not.
*
* @param {boolean} isModalPage
*/
Page.prototype.setIsModalPage = function (isModalPage) {
this._isModalPage = isModalPage;
this.saveChanges(true);
};
/**
* Gets the information whether the page is home (initial) page.
* @returns {Boolean}
*/
Page.prototype.isHomepage = function () {
return this._Pages.getHomepageId() === this.getId();
};
/**
* Returns true if the page is currently shown in the application content area.
* @returns {Boolean}
*/
Page.prototype.isActive = function () {
return navigation.getActivePage() === this;
};
/**
* Gets a ViewFactory which can create new Views in this page.
*
* @returns {ViewFactory}
*/
Page.prototype.getViewFactory = function () {
return this._viewFactory;
};
/**
* Gets the root View object of the page.
*
* @returns {View}
*/
Page.prototype.getView = function () {
return this._view;
};
/**
* Gets the page view model definition.
*
* @returns {ViewModelDefinition} the description of the view model
*/
Page.prototype.getViewModelDefinition = function () {
return this._viewModelDefinition;
};
/**
* Gets an instance of the page view model.
*
* @param {boolean} contextWillBeInitializedLater - TODO David add description
*/
Page.prototype.getViewModel = function (/*contextWillBeInitializedLater*/) {
// XXX: this is DT implementation of the method; in RT it should just
// load static JS file which represents the view model
return this._viewModelDefinition.getViewModel(/*contextWillBeInitializedLater*/);
};
Page.prototype.getViewModelCode = function () {
return this._viewModelDefinition.getViewModelCode();
};
/**
* Finds a View object by its ID.
*
* The IDs of the view objects are propagated to the top-most wrapping DOM elements.
* One may then go through the page DOM tree, get a particular DOM node and
* find the corresponding view object by passing its ID to this method.
*
* @param {type} viewId
* @returns {undefined}
*/
Page.prototype.findView = function (viewId) {
return this._viewIdToViewMap[viewId];
};
Page.prototype.findSameView = function (view) {
var result = null;
if (view === this.findView(view.getId())) {
result = view;
}
return result;
};
/**
* Finds all {@link View}s on the page with the given {@name archetypeType}.
*
* <p>
* Iterates through the all {@link View}s on the page and collects all that
* are using archetype type given by the parameter.
* </p>
*
* @param {ArchetypeType} archetypeType if ommited the method will return all views w/ any archetype
* @returns {View[]}
*/
Page.prototype.findViews = function (archetypeType) {
return this._findViews(this.getView(), archetypeType);
};
Page.prototype._findViews = function (view, archetypeType) {
var self = this;
var views = [];
// Iterates over all child views
if (view.hasChildren()) {
view.getChildren().forEach(function (childView) {
// And for each of them recursively collects results
var childViews = self._findViews(childView, archetypeType);
childViews.forEach(function (child) {
views.push(child);
});
});
}
var archetype = view.getArchetype();
if (archetype && (!archetypeType || (archetype.getType() === archetypeType))) {
views.push(view);
}
return views;
};
Page.prototype.getDefinition = function () {
var def = {};
//page id
def.id = this.getId();
//page display name
var dnValue = this.getDisplayName();
def.displayName = I18n.isTranslatable(dnValue) ? dnValue.toJSON() : dnValue;
//page is modal page flag
def.isModalPage = this.isModalPage();
//page type
def.type = this.getType();
//page view model
def.viewModel = this.getViewModelDefinition().getDefinition();
//views structure
def.view = this.getView().getDefinition();
return def;
};
Page.PAGE_CUSTOM_MODEL_INIT_LIMIT = 5 * 1000; //ms
/**
* Replaces the content of the <section> element in the main
* application template by content of the navigated page.
*
* Called upon navigation to this page.
*
* @param {ContextualData} contextualData
* @param {Boolean} returningToPreviousPage
* @param {View[]} views
*/
Page.prototype._createDynamicPage = function (contextualData, returningToPreviousPage, views) {
if (ApplicationSupport.isInGenerationMode()) {
throw new Error('can\'t call Page._createDynamicPage() in static app generation mode!');
}
Page._LOGGER.trace('_createDynamicPage( ' + JSON.stringify(contextualData) + ') called on ' + this.getId());
var self = this;
//registers all used ad-hoc web components from the page
return CustomCodePageIntegration.prepareCustomComponents(self).then(function (errors) {
self._updateErrorNotes(self.errors2notes.model, errors);
//check for modified page model. In this case the whole page needs to be rebound.
var currentPageModelInstanceVersion = self.getViewModelDefinition().getModelInstanceVersion();
//check if we've previously bound a different version of the page model se we can't incrementally rebind
//the modified views, we need to rebind the whole page.
//
//TBD we may make it more clever later - for example if somethhing is added to the viewmodel
//there's no need to rebind the already bound views etc... some sort of true incremental update
//
//The intended meaning of the model version is that it should be incremented
//whenever an incompatible change is done to the model
//
var forcePageModelRebind = self._lastBoundPageModelInstanceVersion !== currentPageModelInstanceVersion;
self._lastBoundPageModelInstanceVersion = currentPageModelInstanceVersion;
var viewsToRender;
//render whole page or individual views
if (forcePageModelRebind) {
Page._LOGGER.trace('Page view model version has changed to ' + currentPageModelInstanceVersion + ' from ' + self._lastBoundPageModelInstanceVersion + ' => forcing page view model rebind to the whole page.');
self._rebuildPageDOM();
} else {
var existingViews = self._filterOutDeletedViews(views);
var viewsWithOneDOMElement = Page._filterToOneDOMElementViews(existingViews);
viewsToRender = Page._filterToParents(viewsWithOneDOMElement);
if (viewsToRender && viewsToRender.length) {
for (var i = 0; i < viewsToRender.length; i++) {
var view = viewsToRender[i];
var viewId = view.getId();
if (viewId === LayoutConstants.PAGE_TOP_VIEW_ID) {
//page top view regeneration
viewsToRender = null;
self._rebuildPageDOM();
break;
} else {
var $viewElement = $('[data-view-id="' + viewId + '"]');
if (!AbcsLib.Test.isUnitTestsEnv() && $viewElement.length === 0) {
viewsToRender = null;
self._rebuildPageDOM();
break;
} else {
self._rebuildViewDOM(view, $viewElement);
}
}
}
} else {
self._rebuildPageDOM();
}
}
var dtModel = self.getViewModel();
//if self function had been called from the Page.activated and contextualData
//have been passed, lets initialize the view model
self._initializeGeneratedViewModel(dtModel, contextualData, returningToPreviousPage);
//apply knockout binding to the modified views
var viewsToRebind = self._prepareViewsToRebind(viewsToRender, forcePageModelRebind);
self._applyBindings(viewsToRebind, dtModel);
//fire page changes event
self.fireEvent(Page.EVENT_PAGE_UPDATED, self);
Page._LOGGER.trace('\'' + self.getId() + '\' dynamically created.');
return dtModel;
});
};
Page.prototype._updateErrorNotes = function(error2note, errors) {
require(['base/js/ui/UiUtils'], function (UiUtils) {
//add new notes
errors.forEach(function (error) {
var noteHandle = error2note[error];
if (!noteHandle) {
//new error => show note
noteHandle = UiUtils.showErrorNote(error);
error2note[error] = noteHandle;
}
});
//remove old ones
var keysToRemove = [];
for (var key in error2note) {
if (errors.indexOf(key) === -1) {
//removed
keysToRemove.push(key);
}
}
keysToRemove.forEach(function(keyToRemove) {
var noteHandle = error2note[keyToRemove];
delete error2note[keyToRemove];
noteHandle.close();
});
});
};
Page.prototype._showErrorNote = function (msg) {
require(['base/js/ui/UiUtils'], function (UiUtils) {
UiUtils.showErrorNote(msg);
});
};
/**
* @TODO - nowhere used - is this method required?
*/
Page.prototype._closeErrorNote = function () {
require(['base/js/ui/UiUtils'], function (UiUtils) {
UiUtils.closeAllErrorNotes();
});
};
//apply page model binding to all modified views
Page.prototype._applyBindings = function (viewsToRender, customDTModel) {
var self = this;
var errors = [];
viewsToRender.forEach(function (view) {
var viewId = view.getId();
var viewDOMElement = document.querySelector('[data-view-id="' + viewId + '"]');
if (!viewDOMElement && AbcsLib.Test.isUnitTestsEnv()) {
return ; //ignore
}
try {
ko.applyBindings(customDTModel, viewDOMElement);
Page._LOGGER.trace('Re-bound view model for view ' + viewId);
} catch (e) {
//binding error - can be either internal error or caused custom model being live edited
errors.push('Can\'t bind view: ' + e.message);
Page._LOGGER.exception(e, 'Failed to bind view model');
}
});
self._updateErrorNotes(self.errors2notes.binding, errors);
};
Page.prototype._prepareViewsToRebind = function (viewsToRender, forcePageModelRebind) {
var result = null;
if (!viewsToRender || !viewsToRender.length || forcePageModelRebind) {
result = [this.getView()];
} else {
result = viewsToRender;
}
return result;
};
Page.prototype._filterOutDeletedViews = function (views) {
var result = [];
if (views) {
if (this._deletedViews && this._deletedViews.length) {
for (var i = 0; i < views.length; i++) {
var view = views[i];
if (this._deletedViews.indexOf(view) === -1) {
result.push(view);
}
}
this._deletedViews = [];
} else {
result = views;
}
}
return result;
};
Page._filterToOneDOMElementViews = function (views) {
var result = [];
if (views && views.length) {
for (var i = 0; i < views.length; i++) {
var view = views[i];
var viewElements = document.querySelectorAll('[data-view-id="' + view.getId() + '"]');
var viewElementsLength = viewElements.length;
if (viewElementsLength > 0) {
if (viewElementsLength === 1) {
if (result.indexOf(view) === -1) {
result.push(view);
}
} else if (viewElementsLength > 1) {
// more elements probably generated by ko.foreach() in parent view template
var parent = Page._findParentWithOneElement(view);
if (parent && result.indexOf(parent) === -1) {
result.push(parent);
}
}
}
}
}
return result;
};
Page._findParentWithOneElement = function (view) {
var result = null;
var parent = view.getParent();
if (parent) {
var parentElements = document.querySelectorAll('[data-view-id="' + parent.getId() + '"]');
if (parentElements && parentElements.length > 1) {
result = Page._findParentWithOneElement(parent);
} else {
result = parent;
}
}
return result;
};
Page._filterToParents = function (views) {
var viewsToRender = [];
if (views) {
viewsToRender = views.slice();
for (var i = 0; i < viewsToRender.length; i++) {
var view = viewsToRender[i];
for (var j = 0; j < viewsToRender.length; j++) {
var possibleParent = viewsToRender[j];
if (view.isChildOf(possibleParent)) {
viewsToRender.splice(i, 1);
i--;
break;
}
}
}
}
return viewsToRender;
};
Page.prototype._rebuildPageDOM = function () {
var contentSelector = '#' + LayoutConstants.APP_CONTENT_ID;
var $content = $(contentSelector);
if (!AbcsLib.Test.isUnitTestsEnv() && $content.length === 0) {
throw new Error('FATAL: Can\'t find the \'' + contentSelector + '\' element where the page DOM should be generated!');
}
var prevContent = $content.children()[0];
if (prevContent) {
setTimeout(function() {
ko.cleanNode(prevContent);
}, 10);
}
// empty the content
while ($content[0].firstChild) {
$content[0].removeChild($content[0].firstChild);
}
//tabindex set to make the page content focusable - see BUFP-9351
var $pageElement = $('<div id="' + this.getId() + '" tabindex="-1"></div>');
$content.append($pageElement);
var pageMode = this._Pages.getMode();
var $element = ViewGeneratorSupport.buildElementInMode(this.getView(), pageMode, this);
$pageElement.append($element);
Page._LOGGER.trace('Whole page ' + this.getId() + ' DOM rebuilt.');
};
Page.prototype._rebuildViewDOM = function (view, $viewElement) {
var $container = $viewElement.parent();
var pageMode = this._Pages.getMode();
var $element = ViewGeneratorSupport.buildElementInMode(view, pageMode, this);
var $elementToReplace = $viewElement;
var $previousSibling;
var $nextSibling;
if ($container.is(DesignerUtils.DESIGN_WRAPPER_SELECTOR)) {
var $wrapper = $container;
$container = $wrapper.parent();
$elementToReplace = $wrapper;
}
GridUtils.moveLayoutClasses($elementToReplace[0], $element);
$previousSibling = $elementToReplace.prev();
$nextSibling = $elementToReplace.next();
ko.cleanNode($elementToReplace[0]);
$elementToReplace.remove();
if ($previousSibling && $previousSibling.length) {
$previousSibling.after($element);
} else if ($nextSibling && $nextSibling.length) {
$nextSibling.before($element);
} else {
$container.append($element);
}
Page._LOGGER.trace('View ' + view.getId() + ' of page ' + this.getId() + ' has been rebuilt.');
};
/**
* Called by the infrastructure when this page becomes active (shown).
*
* @param {ContextualData} contextualData
* @param {Boolean} returningToPreviousPage
* @return {Promise}
*/
Page.prototype.activated = function (contextualData, returningToPreviousPage) {
var self = this;
try {
return self._createDynamicPage(contextualData, returningToPreviousPage);
} catch (error) {
//do not allow the page binding and code errors to break the designer
Page._LOGGER.exception(error);
return Promise.reject(error);
}
};
/**
* Called by the infrastructure when this page is going to be deactivated (navigated
* to different page).
*
* @return {Promise} promise which waits for finished deactivation
*/
Page.prototype.deactivated = function () {
// there is no special page's deactivation opperation so far
Page._LOGGER.debug('End of the page\'s deactivation callback.');
return Promise.resolve();
};
// this is RT method hence it should stay in RT version of Page; and NOT for example in
// ViewModelDefinition in viewmodel.dt module (where I had it before):
/**
*
* @param {type} pageModel
* @param {ContextualData} contextualData
* @param {Boolean} returningToPreviousPage
*/
Page.prototype._initializeGeneratedViewModel = function (pageModel, contextualData, returningToPreviousPage) {
if (contextualData) {
// #BUFP-2423 - when returning to previous page which does not exist
// (because of browser reload or opening Breeze app via bookmark)
// the page model needs to be created first:
if (returningToPreviousPage && !pageModel.stateData) {
returningToPreviousPage = false;
}
// #BUFP-6896 - support for recurrent page openning
var previousPageState = pageNavigationRouterStateObserver.findPreviousPageState(
this._id,
pageModel[navigation.PAGE_VIEWMODEL_VERSION_PROPERTY],
returningToPreviousPage);
if (previousPageState !== undefined) {
contextualData = ContextualData.fromJSON(previousPageState.contextualData);
returningToPreviousPage = false;
pageNavigationRouterStateObserver.popState();
}
if (returningToPreviousPage) {
//backward navigation (returning to previous page)
pageModel.update(contextualData);
pageNavigationRouterStateObserver.popState();
} else {
//forward navigation
pageModel.create(contextualData);
pageModel[navigation.PAGE_VIEWMODEL_VERSION_PROPERTY] = Page._GLOBAL_VIEW_MODEL_VERSION++;
}
}
};
Page.prototype.refresh = function (lazy, views) {
if (!lazy) {
if (navigation.getActivePage() !== this) {
return;
}
//synchronous refresh
try {
return this._createDynamicPage(null, null, views);
} catch (error) {
//do not allow the page binding and code errors to break the designer
Page._LOGGER.exception(error);
}
} else {
//lazy page refresh
this._refreshPageTaskLazy.schedule();
}
};
Page.prototype.createViewId = function (viewType) {
AbcsLib.checkDefined(viewType, 'viewType');
var hash = Math.abs(StringUtils.hash(viewType + RandomUtils.randomString(10))) % 100000;
var newViewId = Page._composeViewId(viewType, hash);
while (this.findView(newViewId)) {
hash++;
newViewId = Page._composeViewId(viewType, hash);
}
return newViewId;
};
Page.prototype.__updateFromDefinition = function (displayName, isModalPage, type, viewModelDefinition, viewDefinition) {
var self = this;
var oldModelDefinition = self._viewModelDefinition ? self._viewModelDefinition.getDefinition() : {};
if (!I18n.isTranslatable(displayName)) {
Page._LOGGER.debug('displayName was not a translatable, was \'' + displayName + '\'');
}
self._displayName = displayName ? displayName : self._id;
self._isModalPage = isModalPage !== undefined ? isModalPage : false;
self._type = type !== undefined ? type : PageType.LANDING;
self._viewIdToViewMap = {};
self._viewFactory = new ViewFactory();
self._deletedViews = [];
self._viewFactory.addListener(new ListenerBuilder(ViewFactory.EVENT_VIEW_CHANGED, function (view) {
self._handleViewChangedEvent(view);
}).event(ViewFactory.EVENT_VIEW_CHANGED_SILENTLY, function () {
self.saveChanges(true);
}).event(ViewFactory.EVENT_VIEW_CREATED, function (view) {
self._viewIdToViewMap[view.getId()] = view;
}).event(ViewFactory.EVENT_VIEW_REMOVED, function (view) {
delete self._viewIdToViewMap[view.getId()];
self._deletedViews.push(view);
}).event(ViewFactory.EVENT_VIEW_ADDED, function (view) {
self._viewIdToViewMap[view.getId()] = view;
self._refreshAndStore(view.getParent());
}).build());
if (!self._viewModelDefinition || !AbcsLib.equals(JSON.parse(AbcsLib.stringify(viewModelDefinition)), JSON.parse(AbcsLib.stringify(oldModelDefinition)))) {
self._viewModelDefinition = new ViewModelDefinition(self, viewModelDefinition);
}
// Passing ViewModelDefinition to be able to restore original links between View-Archetype
if (viewDefinition) {
self._view = self._viewFactory.createViewFromDefinition(viewDefinition, self._viewModelDefinition);
}
};
Page.prototype._handleViewChangedEvent = function (view) {
if (!view.isBeingDeleted()) {
var viewToRerender = Page._findTopMostViewToRerender(view);
this._refreshAndStore(viewToRerender);
}
};
Page._findTopMostViewToRerender = function (view) {
var viewToRefreshAndStore = view;
while (viewToRefreshAndStore.isRerenderParent()) {
var parent = viewToRefreshAndStore.getParent();
if (parent) {
viewToRefreshAndStore = parent;
} else {
break;
}
}
return viewToRefreshAndStore;
};
Page._composeViewId = function (viewType, hash) {
return viewType + '-' + hash;
};
return Page;
});