UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

465 lines (430 loc) 21 kB
/* Copyright The Infusion copyright holders See the AUTHORS.md file at the top-level directory of this distribution and at https://github.com/fluid-project/infusion/raw/master/AUTHORS.md. Licensed under the Educational Community License (ECL), Version 2.0 or the New BSD license. You may not use this file except in compliance with one these Licenses. You may obtain a copy of the ECL 2.0 License and BSD License at https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt */ var fluid_3_0_0 = fluid_3_0_0 || {}; (function ($, fluid) { "use strict"; fluid.registerNamespace("fluid.dom"); fluid.dom.getDocumentHeight = function (dokkument) { var body = $("body", dokkument)[0]; return body.offsetHeight; }; /******************************************************* * Separated Panel Preferences Editor Top Level Driver * *******************************************************/ fluid.defaults("fluid.prefs.separatedPanel", { gradeNames: ["fluid.prefs.prefsEditorLoader", "fluid.contextAware"], events: { afterRender: null, onReady: null, onCreateSlidingPanelReady: { events: { iframeRendered: "afterRender", onPrefsEditorMessagesLoaded: "onPrefsEditorMessagesLoaded" } }, templatesAndIframeReady: { events: { iframeReady: "afterRender", templatesLoaded: "onPrefsEditorTemplatesLoaded", messagesLoaded: "onPrefsEditorMessagesLoaded" } } }, lazyLoad: false, contextAwareness: { lazyLoad: { checks: { lazyLoad: { contextValue: "{fluid.prefs.separatedPanel}.options.lazyLoad", gradeNames: "fluid.prefs.separatedPanel.lazyLoad" } } } }, selectors: { reset: ".flc-prefsEditor-reset", iframe: ".flc-prefsEditor-iframe" }, listeners: { "onReady.bindEvents": { listener: "fluid.prefs.separatedPanel.bindEvents", args: ["{separatedPanel}.prefsEditor", "{iframeRenderer}.iframeEnhancer", "{separatedPanel}"] }, "onCreate.hideReset": { listener: "fluid.prefs.separatedPanel.hideReset", args: ["{separatedPanel}"] } }, invokers: { bindReset: { funcName: "fluid.bind", args: ["{separatedPanel}.dom.reset", "click", "{arguments}.0"] } }, components: { slidingPanel: { type: "fluid.slidingPanel", container: "{separatedPanel}.container", createOnEvent: "onCreateSlidingPanelReady", options: { gradeNames: ["fluid.prefs.msgLookup"], strings: { showText: "{that}.msgLookup.slidingPanelShowText", hideText: "{that}.msgLookup.slidingPanelHideText", showTextAriaLabel: "{that}.msgLookup.showTextAriaLabel", hideTextAriaLabel: "{that}.msgLookup.hideTextAriaLabel", panelLabel: "{that}.msgLookup.slidingPanelPanelLabel" }, invokers: { operateShow: { funcName: "fluid.prefs.separatedPanel.showPanel", args: ["{that}.dom.panel", "{that}.events.afterPanelShow.fire"], // override default implementation "this": null, "method": null }, operateHide: { funcName: "fluid.prefs.separatedPanel.hidePanel", args: ["{that}.dom.panel", "{iframeRenderer}.iframe", "{that}.events.afterPanelHide.fire"], // override default implementation "this": null, "method": null } }, components: { msgResolver: { type: "fluid.messageResolver", options: { messageBase: "{messageLoader}.resources.prefsEditor.resourceText" } } } } }, iframeRenderer: { type: "fluid.prefs.separatedPanel.renderIframe", container: "{separatedPanel}.dom.iframe", options: { events: { afterRender: "{separatedPanel}.events.afterRender" }, components: { iframeEnhancer: { type: "fluid.uiEnhancer", container: "{iframeRenderer}.renderPrefsEditorContainer", createOnEvent: "afterRender", options: { gradeNames: ["{pageEnhancer}.uiEnhancer.options.userGrades"], jQuery: "{iframeRenderer}.jQuery", tocTemplate: "{pageEnhancer}.uiEnhancer.options.tocTemplate", inSeparatedPanel: true } } } } }, prefsEditor: { createOnEvent: "templatesAndIframeReady", container: "{iframeRenderer}.renderPrefsEditorContainer", options: { gradeNames: ["fluid.prefs.uiEnhancerRelay", "fluid.prefs.arrowScrolling"], // ensure that model and applier are available to users at top level model: { preferences: "{separatedPanel}.model.preferences", panelIndex: "{separatedPanel}.model.panelIndex", panelMaxIndex: "{separatedPanel}.model.panelMaxIndex", // The `local` model path is used by the `fluid.remoteModelComponent` grade // for persisting and synchronizing model values with remotely stored data. // Below, the panelIndex is being tracked for such persistence and synchronization. local: { panelIndex: "{that}.model.panelIndex" } }, autoSave: true, events: { onSignificantDOMChange: null, updateEnhancerModel: "{that}.events.modelChanged" }, modelListeners: { "panelIndex": [{ listener: "fluid.prefs.prefsEditor.handleAutoSave", args: ["{that}"], namespace: "autoSavePanelIndex" }] }, listeners: { "onCreate.bindReset": { listener: "{separatedPanel}.bindReset", args: ["{that}.reset"] }, "afterReset.applyChanges": "{that}.applyChanges", // Scroll to active panel after opening the separate Panel. // This is when the panels are all rendered and the actual sizes are available. "{separatedPanel}.slidingPanel.events.afterPanelShow": { listener: "fluid.prefs.arrowScrolling.scrollToPanel", args: ["{that}", "{that}.model.panelIndex"], priority: "after:updateView", namespace: "scrollToPanel" } } } } }, outerEnhancerOptions: "{originalEnhancerOptions}.options.originalUserOptions", distributeOptions: { "separatedPanel.slidingPanel": { source: "{that}.options.slidingPanel", removeSource: true, target: "{that > slidingPanel}.options" }, "separatedPanel.iframeRenderer": { source: "{that}.options.iframeRenderer", removeSource: true, target: "{that > iframeRenderer}.options" }, "separatedPanel.iframeRendered.terms": { source: "{that}.options.terms", target: "{that > iframeRenderer}.options.terms" }, "separatedPanel.selectors.iframe": { source: "{that}.options.iframe", removeSource: true, target: "{that}.options.selectors.iframe" }, "separatedPanel.iframeEnhancer.outerEnhancerOptions": { source: "{that}.options.outerEnhancerOptions", removeSource: true, target: "{that iframeEnhancer}.options" } } }); fluid.prefs.separatedPanel.hideReset = function (separatedPanel) { separatedPanel.locate("reset").hide(); }; /***************************************** * fluid.prefs.separatedPanel.renderIframe * *****************************************/ fluid.defaults("fluid.prefs.separatedPanel.renderIframe", { gradeNames: ["fluid.viewComponent"], events: { afterRender: null }, styles: { container: "fl-prefsEditor-separatedPanel-iframe" }, terms: { templatePrefix: "." }, markupProps: { "class": "flc-iframe", src: "%templatePrefix/SeparatedPanelPrefsEditorFrame.html" }, listeners: { "onCreate.startLoadingIframe": "fluid.prefs.separatedPanel.renderIframe.startLoadingIframe" } }); fluid.prefs.separatedPanel.renderIframe.startLoadingIframe = function (that) { var styles = that.options.styles; // TODO: get earlier access to templateLoader, that.options.markupProps.src = fluid.stringTemplate(that.options.markupProps.src, that.options.terms); that.iframeSrc = that.options.markupProps.src; // Create iframe and append to container that.iframe = $("<iframe/>"); that.iframe.on("load", function () { var iframeWindow = that.iframe[0].contentWindow; that.iframeDocument = iframeWindow.document; // The iframe should prefer its own version of jQuery if a separate // one is loaded that.jQuery = iframeWindow.jQuery || $; that.renderPrefsEditorContainer = that.jQuery("body", that.iframeDocument); that.jQuery(that.iframeDocument).ready(that.events.afterRender.fire); }); that.iframe.attr(that.options.markupProps); that.iframe.addClass(styles.container); that.iframe.hide(); that.iframe.appendTo(that.container); }; fluid.prefs.separatedPanel.updateView = function (prefsEditor) { prefsEditor.events.onPrefsEditorRefresh.fire(); prefsEditor.events.onSignificantDOMChange.fire(); }; fluid.prefs.separatedPanel.bindEvents = function (prefsEditor, iframeEnhancer, separatedPanel) { // FLUID-5740: This binding should be done declaratively - needs ginger world in order to bind onto slidingPanel // which is a child of this component var separatedPanelId = separatedPanel.slidingPanel.panelId; separatedPanel.locate("reset").attr({ "aria-controls": separatedPanelId, "role": "button" }); separatedPanel.slidingPanel.events.afterPanelShow.addListener(function () { fluid.prefs.separatedPanel.updateView(prefsEditor); }, "updateView", "after:openPanel"); prefsEditor.events.onPrefsEditorRefresh.addListener(function () { iframeEnhancer.updateModel(prefsEditor.model.preferences); }, "updateModel"); prefsEditor.events.afterReset.addListener(function (prefsEditor) { fluid.prefs.separatedPanel.updateView(prefsEditor); }, "updateView"); prefsEditor.events.onSignificantDOMChange.addListener(function () { // ensure that the panel is open before trying to adjust its height if ( fluid.get(separatedPanel, "slidingPanel.model.isShowing") ) { var dokkument = prefsEditor.container[0].ownerDocument; var height = fluid.dom.getDocumentHeight(dokkument); var iframe = separatedPanel.iframeRenderer.iframe; var attrs = {height: height}; var panel = separatedPanel.slidingPanel.locate("panel"); panel.css({height: ""}); iframe.clearQueue(); iframe.animate(attrs, 400); } }, "adjustHeight"); separatedPanel.slidingPanel.events.afterPanelHide.addListener(function () { separatedPanel.iframeRenderer.iframe.height(0); // Prevent the hidden Preferences Editorpanel from being keyboard and screen reader accessible separatedPanel.iframeRenderer.iframe.hide(); }, "collapseFrame"); separatedPanel.slidingPanel.events.afterPanelShow.addListener(function () { separatedPanel.iframeRenderer.iframe.show(); // FLUID-6183: Required for bug in MS EDGE that clips off the bottom of adjusters // The height needs to be recalculated in order for the panel to show up completely separatedPanel.iframeRenderer.iframe.height(); separatedPanel.locate("reset").show(); }, "openPanel"); separatedPanel.slidingPanel.events.onPanelHide.addListener(function () { separatedPanel.locate("reset").hide(); }, "hideReset"); }; // Replace the standard animator since we don't want the panel to become hidden // (potential cause of jumping) fluid.prefs.separatedPanel.hidePanel = function (panel, iframe, callback) { iframe.clearQueue(); // FLUID-5334: clear the animation queue $(panel).animate({height: 0}, {duration: 400, complete: callback}); }; // no activity - the kickback to the updateView listener will automatically trigger the // DOMChangeListener above. This ordering is preferable to avoid causing the animation to // jump by refreshing the view inside the iframe fluid.prefs.separatedPanel.showPanel = function (panel, callback) { // A bizarre race condition has emerged under FF where the iframe held within the panel does not // react synchronously to being shown fluid.invokeLater(callback); }; /** * FLUID-5926: Some of our users have asked for ways to improve the initial page load * performance when using the separated panel prefs editor / UI Options. One option, * provided here, is to implement a scheme for lazy loading the instantiation of the * prefs editor, only instantiating enough of the workflow to allow display the * sliding panel tab. * * fluid.prefs.separatedPanel.lazyLoad modifies the typical separatedPanel workflow * by delaying the instantiation and loading of resources for the prefs editor until * the first time it is opened. * * Lazy Load Workflow: * * - On instantiation of the prefsEditorLoader only the messageLoader and slidingPanel are instantiated * - On instantiation, the messageLoader only loads preLoadResources, these are the messages required by * the slidingPanel. The remaining message bundles will not be loaded until the "onLazyLoad" event is fired. * - After the preLoadResources have been loaded, the onPrefsEditorMessagesPreloaded event is fired, and triggers the * sliding panel to instantiate. * - When a user opens the separated panel prefs editor / UI Options, it checks to see if the prefs editor has been * instantiated. If it hasn't, a listener is temporarily bound to the onReady event, which gets fired * after the prefs editor is ready. This is used to continue the process of opening the sliding panel for the first time. * Additionally the onLazyLoad event is fired, which kicks off the remainder of the instantiation process. * - onLazyLoad triggers the templateLoader to fetch all of the templates and the messageLoader to fetch the remaining * message bundles. From here the standard instantiation workflow takes place. */ fluid.defaults("fluid.prefs.separatedPanel.lazyLoad", { events: { onLazyLoad: null, onPrefsEditorMessagesPreloaded: null, onCreateSlidingPanelReady: { events: { onPrefsEditorMessagesLoaded: "onPrefsEditorMessagesPreloaded" } }, templatesAndIframeReady: { events: { onLazyLoad: "onLazyLoad" } } }, components: { templateLoader: { createOnEvent: "onLazyLoad" }, messageLoader: { options: { events: { onResourcesPreloaded: "{separatedPanel}.events.onPrefsEditorMessagesPreloaded" }, preloadResources: "prefsEditor", listeners: { "onCreate.loadResources": { listener: "fluid.prefs.separatedPanel.lazyLoad.preloadResources", args: ["{that}", {expander: {func: "{that}.resolveResources"}}, "{that}.options.preloadResources"] }, "{separatedPanel}.events.onLazyLoad": { listener: "fluid.resourceLoader.loadResources", args: ["{messageLoader}", {expander: {func: "{messageLoader}.resolveResources"}}], namespace: "loadResources" } } } }, slidingPanel: { options: { invokers: { operateShow: { funcName: "fluid.prefs.separatedPanel.lazyLoad.showPanel", args: ["{separatedPanel}", "{that}.events.afterPanelShow.fire"] } } } } } }); fluid.prefs.separatedPanel.lazyLoad.showPanel = function (separatedPanel, callback) { if (separatedPanel.prefsEditor) { fluid.invokeLater(callback); } else { separatedPanel.events.onReady.addListener(function (that) { that.events.onReady.removeListener("showPanelCallBack"); fluid.invokeLater(callback); }, "showPanelCallBack"); separatedPanel.events.onLazyLoad.fire(); } }; /** * Used to override the standard "onCreate.loadResources" listener for fluid.resourceLoader component, * allowing for pre-loading of a subset of resources. This is required for the lazyLoading workflow * for the "fluid.prefs.separatedPanel.lazyLoad". * * @param {Object} that - the component * @param {Object} resources - all of the resourceSpecs to load, including preload and others. * see: fluid.fetchResources * @param {Array|String} toPreload - a String or an String[]s corresponding to the names * of the resources, supplied in the resource argument, that * should be loaded. Only these resources will be loaded. */ fluid.prefs.separatedPanel.lazyLoad.preloadResources = function (that, resources, toPreload) { toPreload = fluid.makeArray(toPreload); var preloadResources = {}; fluid.each(toPreload, function (resourceName) { preloadResources[resourceName] = resources[resourceName]; }); // This portion of code was copied from fluid.resourceLoader.loadResources // and will likely need to track any changes made there. fluid.fetchResources(preloadResources, function () { that.resources = preloadResources; that.events.onResourcesPreloaded.fire(preloadResources); }); }; })(jQuery, fluid_3_0_0);