devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
576 lines (575 loc) • 24.5 kB
JavaScript
/**
* DevExtreme (framework/html/layout_controller.js)
* Version: 18.2.18
* Build date: Tue Oct 18 2022
*
* Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
require("../../integration/jquery");
var $ = require("jquery"),
domAdapter = require("../../core/dom_adapter"),
eventsEngine = require("../../events/core/events_engine"),
Class = require("../../core/class"),
commonUtils = require("../../core/utils/common"),
iteratorUtils = require("../../core/utils/iterator"),
frameworkUtils = require("../utils"),
layoutSets = require("./presets").layoutSets,
EventsMixin = require("../../core/events_mixin"),
errors = require("../errors"),
domUtils = require("../../core/utils/dom"),
when = require("../../core/utils/deferred").when,
HIDDEN_BAG_ID = "__hidden-bag",
TRANSITION_SELECTOR = ".dx-transition",
CONTENT_SELECTOR = ".dx-content",
DEFAULT_COMMAND_RENDER_STAGE = "onViewShown",
CONTENT_RENDERED_EVENT_NAME = "dxcontentrendered.layoutController",
PENDING_RENDERING_SELECTOR = ".dx-pending-rendering",
PENDING_RENDERING_MANUAL_SELECTOR = ".dx-pending-rendering-manual",
TransitionExecutorModule = require("../../animation/transition_executor/transition_executor");
require("./command_container");
require("./view_engine_components");
var transitionSelector = function(transitionName) {
return ".dx-transition-" + transitionName
};
var DefaultLayoutController = Class.inherit({
ctor: function(options) {
options = options || {};
this.name = options.name || "";
this._layoutModel = options.layoutModel || {};
this._defaultPaneName = options.defaultPaneName || "content";
this._transitionDuration = void 0 === options.transitionDuration ? 400 : options.transitionDuration;
this._showViewFired = false
},
init: function(options) {
options = options || {};
this._visibleViews = {};
this._$viewPort = options.$viewPort || $("body");
this._commandManager = options.commandManager;
this._viewEngine = options.viewEngine;
this.transitionExecutor = new TransitionExecutorModule.TransitionExecutor;
this._prepareTemplates();
this._$viewPort.append(this.element());
this._hideElements(this.element());
if (options.templateContext) {
this._templateContext = options.templateContext;
this._proxiedTemplateContextChangedHandler = this._templateContextChangedHandler.bind(this)
}
},
ensureActive: function(targetNode) {
if (this._disabledState) {
return this.enable()
} else {
return this.activate(targetNode)
}
},
activate: function() {
this._showViewFired = false;
var $rootElement = this.element();
this._showElements($rootElement);
this._attachRefreshViewRequiredHandler();
return $.Deferred().resolve().promise()
},
deactivate: function() {
this._disabledState = false;
this._showViewFired = false;
this._releaseVisibleViews();
this._hideElements(this.element());
this._detachRefreshViewRequiredHandler();
return $.Deferred().resolve().promise()
},
enable: function() {
this._disabledState = false;
if (!this._showViewFired) {
this._notifyShowing()
}
this._showViewFired = false;
return $.Deferred().resolve().promise()
},
disable: function() {
this._disabledState = true;
this._showViewFired = false;
this._notifyHidden()
},
activeViewInfo: function() {
return this._visibleViews[this._defaultPaneName]
},
_fireViewEvents: function(eventName, views) {
var that = this;
views = views || this._visibleViews;
iteratorUtils.each(views, function(index, viewInfo) {
that.fireEvent(eventName, [viewInfo])
})
},
_notifyShowing: function(views) {
this._fireViewEvents("viewShowing", views)
},
_notifyShown: function(views) {
this._fireViewEvents("viewShown", views)
},
_notifyHidden: function(views) {
this._fireViewEvents("viewHidden", views)
},
_applyTemplate: function($elements, model) {
$elements.each(function(i, element) {
frameworkUtils.templateProvider.applyTemplate(element, model)
})
},
_releaseVisibleViews: function() {
var that = this;
iteratorUtils.each(this._visibleViews, function(index, viewInfo) {
that._hideView(viewInfo);
that._releaseView(viewInfo)
});
this._visibleViews = {}
},
_templateContextChangedHandler: function() {
var that = this,
viewsToShow = [];
iteratorUtils.each(that._visibleViews, function(index, viewInfo) {
if (viewInfo.currentViewTemplateId !== that._getViewTemplateId(viewInfo)) {
viewsToShow.push(viewInfo)
}
});
when.apply($, iteratorUtils.map(viewsToShow, function(viewInfo) {
return that.showView(viewInfo)
})).done(function() {
that._notifyShown(viewsToShow)
})
},
_attachRefreshViewRequiredHandler: function() {
if (this._templateContext) {
this._templateContext.on("optionChanged", this._proxiedTemplateContextChangedHandler)
}
},
_detachRefreshViewRequiredHandler: function() {
if (this._templateContextChanged) {
this._templateContext.off("optionChanged", this._proxiedTemplateContextChangedHandler)
}
},
_getPreviousViewInfo: function(viewInfo) {
return this._visibleViews[this._getViewPaneName(viewInfo.viewTemplateInfo)]
},
_prepareTemplates: function() {
var that = this;
var $layoutTemplate = that._viewEngine.getLayoutTemplate(this._getLayoutTemplateName());
that._$layoutTemplate = $layoutTemplate;
that._$mainLayout = that._createEmptyLayout();
that._showElements(that._$mainLayout);
that._applyTemplate(that._$mainLayout, that._layoutModel);
that._$navigationWidget = that._createNavigationWidget()
},
renderNavigation: function(navigationCommands) {
this._clearNavigationWidget();
this._renderNavigationImpl(navigationCommands)
},
_renderNavigationImpl: function(navigationCommands) {
this._renderCommands(this._$mainLayout, navigationCommands)
},
_createNavigationWidget: function() {
var result, containers = this._findCommandContainers(this._$mainLayout);
iteratorUtils.each(containers, function(k, container) {
if ("global-navigation" === container.option("id")) {
result = container.element();
return false
}
});
return result
},
_clearNavigationWidget: function() {
if (this._$navigationWidget) {
this._commandManager.clearContainer(this._$navigationWidget.dxCommandContainer("instance"))
}
},
element: function() {
return this._$mainLayout
},
_getViewFrame: function(viewInfo) {
return this._$mainLayout
},
_getLayoutTemplateName: function() {
return this.name
},
_applyModelToTransitionElements: function($markup, model) {
var that = this;
this._getTransitionElements($markup).each(function(i, item) {
that._applyTemplate($(item).children(), model)
})
},
_createViewLayoutTemplate: function() {
var that = this;
var $viewLayoutTemplate = that._$layoutTemplate.clone();
this._hideElements($viewLayoutTemplate);
return $viewLayoutTemplate
},
_createEmptyLayout: function() {
var that = this;
var $result = that._$layoutTemplate.clone();
this._hideElements($result);
this._getTransitionElements($result).empty();
$result.children(CONTENT_SELECTOR).remove();
return $result
},
_getTransitionElements: function($markup) {
var $items = $markup.find(TRANSITION_SELECTOR).add($markup.filter(TRANSITION_SELECTOR)),
result = [];
for (var i = 0; i < $items.length; i++) {
var $item = $items.eq(i);
if (0 === $item.parents(TRANSITION_SELECTOR).length) {
result.push($item.get(0))
}
}
return $(result)
},
showView: function(viewInfo, direction) {
direction = direction || "forward";
var result, that = this,
previousViewInfo = that._getPreviousViewInfo(viewInfo),
previousViewTemplateId = previousViewInfo === viewInfo ? previousViewInfo.currentViewTemplateId : void 0;
this._showViewFired = true;
this._updateCurrentViewTemplateId(viewInfo);
if (previousViewTemplateId && previousViewTemplateId === viewInfo.currentViewTemplateId && viewInfo === previousViewInfo) {
that.fireEvent("viewShowing", [viewInfo, direction]);
result = $.Deferred().resolve().promise()
} else {
that._ensureViewRendered(viewInfo);
that.fireEvent("viewShowing", [viewInfo, direction]);
result = this._showViewImpl(viewInfo, direction, previousViewTemplateId).done(function() {
that._onViewShown(viewInfo)
})
}
return result
},
disposeView: function(viewInfo) {
this._clearRenderResult(viewInfo)
},
_clearRenderResult: function(viewInfo) {
if (viewInfo.renderResult) {
viewInfo.renderResult.$markup.remove();
viewInfo.renderResult.$viewItems.remove();
delete viewInfo.renderResult
}
},
_renderViewImpl: function($viewTemplate, viewInfo) {
var $viewItems, that = this,
allowedChildrenSelector = ".dx-command,.dx-content,script",
$layout = this._createViewLayoutTemplate(),
isSimplifiedMarkup = true,
outOfContentItems = $();
if (0 === $viewTemplate.children(allowedChildrenSelector).length) {
this._viewEngine._wrapViewDefaultContent($viewTemplate)
}
$viewItems = $viewTemplate.children();
this._applyModelToTransitionElements($layout, viewInfo.model);
this._viewEngine.applyLayout($viewTemplate, $layout);
$viewItems.each(function(i, item) {
var $item = $(item);
that._applyTemplate($item, viewInfo.model);
if ($item.is(allowedChildrenSelector)) {
isSimplifiedMarkup = false
} else {
outOfContentItems = outOfContentItems.add($item)
}
});
if (outOfContentItems.length && !isSimplifiedMarkup) {
throw errors.Error("E3014", outOfContentItems[0].outerHTML)
}
viewInfo.renderResult = viewInfo.renderResult || {};
viewInfo.renderResult.$viewItems = $viewItems;
viewInfo.renderResult.$markup = $layout
},
_renderCommands: function($markup, commands) {
var commandContainers = this._findCommandContainers($markup);
return this._commandManager.renderCommandsToContainers(commands, commandContainers)
},
_prepareViewCommands: function(viewInfo) {
var $viewItems = viewInfo.renderResult.$viewItems,
viewCommands = this._commandManager.findCommands($viewItems),
commandsToRenderMap = {};
viewInfo.commands = frameworkUtils.utils.mergeCommands(viewInfo.commands || [], viewCommands);
viewInfo.commandsToRenderMap = commandsToRenderMap;
iteratorUtils.each(viewInfo.commands, function(index, command) {
var renderStage = command.option("renderStage") || DEFAULT_COMMAND_RENDER_STAGE,
targetArray = commandsToRenderMap[renderStage] = commandsToRenderMap[renderStage] || [];
targetArray.push(command)
})
},
_applyViewCommands: function(viewInfo, renderStage) {
renderStage = renderStage || DEFAULT_COMMAND_RENDER_STAGE;
var result, commandsToRender = viewInfo.commandsToRenderMap[renderStage],
$markup = viewInfo.renderResult.$markup;
if (commandsToRender) {
result = this._renderCommands($markup, commandsToRender);
delete viewInfo.commandsToRenderMap[renderStage]
} else {
result = $.Deferred().resolve().promise()
}
return result
},
_findCommandContainers: function($markup) {
return domUtils.createComponents($markup, ["dxCommandContainer"])
},
_getViewTemplateId: function(viewInfo) {
var viewTemplateInstance = viewInfo.$viewTemplate ? viewInfo.$viewTemplate.dxView("instance") : this._viewEngine.getViewTemplateInfo(viewInfo.viewName);
return viewTemplateInstance.getId()
},
_updateCurrentViewTemplateId: function(viewInfo) {
viewInfo.currentViewTemplateId = this._getViewTemplateId(viewInfo)
},
_ensureViewRendered: function(viewInfo) {
var $cachedMarkup = viewInfo.renderResult && viewInfo.renderResult.markupCache[viewInfo.currentViewTemplateId];
if ($cachedMarkup) {
viewInfo.renderResult.$markup = $cachedMarkup
} else {
this._renderView(viewInfo);
viewInfo.renderResult.markupCache = viewInfo.renderResult.markupCache || {};
viewInfo.renderResult.markupCache[viewInfo.currentViewTemplateId] = viewInfo.renderResult.$markup
}
},
_renderView: function(viewInfo) {
var $viewTemplate = viewInfo.$viewTemplate || this._viewEngine.getViewTemplate(viewInfo.viewName);
this._renderViewImpl($viewTemplate, viewInfo);
this._prepareViewCommands(viewInfo);
this._applyViewCommands(viewInfo, "onViewRendering");
this._appendViewToLayout(viewInfo);
$viewTemplate.remove();
this._onRenderComplete(viewInfo);
this.fireEvent("viewRendered", [viewInfo])
},
_prepareTransition: function($element, targetPlaceholderName) {
if (0 === $element.children(".dx-content").length) {
$element.wrapInner("<div>");
$element.children().dxContent({
targetPlaceholder: targetPlaceholderName
})
}
},
_appendViewToLayout: function(viewInfo) {
var that = this,
$viewFrame = that._getViewFrame(viewInfo),
$markup = viewInfo.renderResult.$markup,
$transitionContentElements = $(),
animationItems = [];
iteratorUtils.each($markup.find(".dx-content-placeholder"), function(index, el) {
that._prepareTransition($(el), $(el).attr("data-dx-content-placeholder-name"))
});
iteratorUtils.each(that._getTransitionElements($viewFrame), function(index, transitionElement) {
var $transition = $(transitionElement),
$viewElement = $markup.find(transitionSelector($transition.attr("data-dx-transition-name"))).children(),
animationItem = {
$element: $viewElement,
animation: $transition.attr("data-dx-transition-type")
};
animationItems.push(animationItem);
$transition.append($viewElement);
that._showViewElements($viewElement);
domUtils.triggerShownEvent($viewElement);
$transitionContentElements = $transitionContentElements.add($viewElement)
});
that._$mainLayout.append(viewInfo.renderResult.$viewItems.filter(".dx-command"));
$markup.remove();
viewInfo.renderResult.$markup = $transitionContentElements;
viewInfo.renderResult.animationItems = animationItems
},
_onRenderComplete: function(viewInfo) {},
_onViewShown: function(viewInfo) {
eventsEngine.trigger(domAdapter.getDocument(), "dx.viewchanged")
},
_enter: function(animationItems, animationModifier) {
var transitionExecutor = this.transitionExecutor;
iteratorUtils.each(animationItems, function(index, item) {
transitionExecutor.enter(item.$element, item.animation, animationModifier)
})
},
_leave: function(animationItems, animationModifier) {
var transitionExecutor = this.transitionExecutor;
iteratorUtils.each(animationItems, function(index, item) {
transitionExecutor.leave(item.$element, item.animation, animationModifier)
})
},
_doTransition: function(oldViewInfo, newViewInfo, animationModifier) {
if (oldViewInfo) {
this._leave(oldViewInfo.renderResult.animationItems, animationModifier)
}
this._enter(newViewInfo.renderResult.animationItems, animationModifier);
this._showView(newViewInfo);
return this.transitionExecutor.start()
},
_showViewImpl: function(viewInfo, direction, previousViewTemplateId) {
var that = this,
previousViewInfo = this._getPreviousViewInfo(viewInfo),
animationModifier = {
direction: direction
};
if (previousViewInfo === viewInfo) {
previousViewInfo = void 0
}
if (!previousViewInfo) {
animationModifier.duration = 0;
animationModifier.delay = 0
}
var d = $.Deferred();
that._doTransition(previousViewInfo, viewInfo, animationModifier).done(function() {
that._changeView(viewInfo, previousViewTemplateId).done(function(result) {
d.resolve(result)
})
});
return d.promise()
},
_releaseView: function(viewInfo) {
this.fireEvent("viewReleased", [viewInfo])
},
_getReadyForRenderDeferredItems: function(viewInfo) {
return $.Deferred().resolve().promise()
},
_changeView: function(viewInfo, previousViewTemplateId) {
var that = this;
if (previousViewTemplateId) {
that._hideView(viewInfo, previousViewTemplateId)
} else {
var previousViewInfo = that._getPreviousViewInfo(viewInfo);
if (previousViewInfo && previousViewInfo !== viewInfo) {
that._hideView(previousViewInfo);
that._releaseView(previousViewInfo)
}
this._visibleViews[this._getViewPaneName(viewInfo.viewTemplateInfo)] = viewInfo
}
this._subscribeToDeferredItems(viewInfo);
var d = $.Deferred();
this._getReadyForRenderDeferredItems(viewInfo).done(function() {
that._applyViewCommands(viewInfo).done(function() {
that._renderDeferredItems(viewInfo.renderResult.$markup).done(function() {
d.resolve()
})
})
});
return d.promise()
},
_subscribeToDeferredItems: function(viewInfo) {
var that = this,
$markup = viewInfo.renderResult.$markup;
$markup.find(PENDING_RENDERING_SELECTOR).add($markup.filter(PENDING_RENDERING_SELECTOR)).each(function() {
var eventData = {
viewInfo: viewInfo,
context: that
};
$(this).on(CONTENT_RENDERED_EVENT_NAME, eventData, that._onDeferredContentRendered)
})
},
_onDeferredContentRendered: function(event) {
var $element = $(event.target),
viewInfo = event.data.viewInfo,
that = event.data.context;
$element.off(CONTENT_RENDERED_EVENT_NAME, that._onDeferredContentRendered);
that._renderCommands($element, viewInfo.commands)
},
_renderDeferredItems: function($items) {
var that = this,
result = $.Deferred();
var $pendingItem = $items.find(PENDING_RENDERING_MANUAL_SELECTOR).add($items.filter(PENDING_RENDERING_MANUAL_SELECTOR)).first();
if ($pendingItem.length) {
var render = $pendingItem.data("dx-render-delegate");
commonUtils.executeAsync(function() {
render().done(function() {
that._renderDeferredItems($items).done(function() {
result.resolve()
})
})
})
} else {
result.resolve()
}
return result.promise()
},
_getViewPaneName: function(viewTemplateInfo) {
return this._defaultPaneName
},
_hideElements: function($elements) {
$elements.addClass("dx-fast-hidden")
},
_showElements: function($elements) {
$elements.removeClass("dx-fast-hidden")
},
_hideViewElements: function($elements) {
this._patchIds($elements);
this._disableInputs($elements);
$elements.removeClass("dx-active-view").addClass("dx-inactive-view")
},
_hideView: function(viewInfo, templateId) {
if (viewInfo.renderResult) {
var $markupToHide = void 0 === templateId ? viewInfo.renderResult.$markup : viewInfo.renderResult.markupCache[templateId];
this._hideViewElements($markupToHide);
this.fireEvent("viewHidden", [viewInfo])
}
},
_showViewElements: function($elements) {
this._unPatchIds($elements);
this._enableInputs($elements);
$elements.removeClass("dx-inactive-view").addClass("dx-active-view");
this._skipAnimation($elements)
},
_showView: function(viewInfo) {
if (viewInfo.renderResult) {
this._showViewElements(viewInfo.renderResult.$markup)
}
},
_skipAnimation: function($elements) {
$elements.addClass("dx-skip-animation");
for (var i = 0; i < $elements.length; i++) {
$elements.eq(i).css("transform")
}
$elements.removeClass("dx-skip-animation")
},
_patchIds: function($markup) {
this._processIds($markup, function(id) {
var result = id;
if (id.indexOf(HIDDEN_BAG_ID) === -1) {
result = HIDDEN_BAG_ID + "-" + id
}
return result
})
},
_unPatchIds: function($markup) {
this._processIds($markup, function(id) {
var result = id;
if (0 === id.indexOf(HIDDEN_BAG_ID)) {
result = id.substr(HIDDEN_BAG_ID.length + 1)
}
return result
})
},
_processIds: function($markup, process) {
var elementsWithIds = $markup.find("[id]");
iteratorUtils.each(elementsWithIds, function(index, element) {
var $el = $(element),
id = $el.attr("id");
$el.attr("id", process(id))
})
},
_enableInputs: function($markup) {
var $inputs = this._getInputs($markup).filter("[data-disabled='true']");
iteratorUtils.each($inputs, function(index, input) {
$(input).removeAttr("disabled").removeAttr("data-disabled")
})
},
_disableInputs: function($markup) {
var $inputs = this._getInputs($markup);
$inputs = $inputs.filter(":not([disabled])").add($inputs.filter("[disabled=true]"));
iteratorUtils.each($inputs, function(index, input) {
$(input).attr({
disabled: true,
"data-disabled": true
})
})
},
_getInputs: function($markup) {
return $markup.find("input, button, select, textarea")
}
}).include(EventsMixin);
layoutSets.default = layoutSets.default || [];
layoutSets.default.push({
controller: new DefaultLayoutController
});
exports.DefaultLayoutController = DefaultLayoutController;
exports.layoutSets = layoutSets;