UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

551 lines (503 loc) 19 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/main/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/main/Infusion-LICENSE.txt */ "use strict"; /***************************** * Preferences Editor Loader * *****************************/ /* * An Preferences Editor top-level component that reflects the collaboration between prefsEditor, templateLoader and messageLoader. * This component is the only Preferences Editor component that is intended to be called by the outside world. */ fluid.defaults("fluid.prefs.prefsEditorLoader", { gradeNames: ["fluid.prefs.settingsGetter", "fluid.prefs.initialModel", "fluid.viewComponent"], defaultLocale: "en", components: { prefsEditor: { type: "fluid.prefs.prefsEditor", createOnEvent: "onCreatePrefsEditorReady", options: { members: { initialModel: "{prefsEditorLoader}.initialModel" }, invokers: { getSettings: "{prefsEditorLoader}.getSettings" }, listeners: { "onReady.boil": { listener: "{prefsEditorLoader}.events.onReady", args: ["{prefsEditorLoader}"] } } } }, templateLoader: { type: "fluid.resourceLoader", options: { events: { onResourcesLoaded: "{prefsEditorLoader}.events.onPrefsEditorTemplatesLoaded" } } }, messageLoader: { type: "fluid.resourceLoader", createOnEvent: "afterInitialSettingsFetched", options: { resourceOptions: { dataType: "json", defaultLocale: "{prefsEditorLoader}.options.defaultLocale", locale: "{prefsEditorLoader}.settings.preferences.locale" }, events: { onResourcesLoaded: "{prefsEditorLoader}.events.onPrefsEditorMessagesLoaded" } } } }, listeners: { "onCreate.getInitialSettings": { listener: "fluid.prefs.prefsEditorLoader.getInitialSettings", args: ["{that}"] } }, events: { afterInitialSettingsFetched: null, onPrefsEditorTemplatesLoaded: null, onPrefsEditorMessagesLoaded: null, onCreatePrefsEditorReady: { events: { templateLoaded: "onPrefsEditorTemplatesLoaded", prefsEditorMessagesLoaded: "onPrefsEditorMessagesLoaded" } }, onReady: null }, distributeOptions: { "prefsEditorLoader.templateLoader": { source: "{that}.options.templateLoader", removeSource: true, target: "{that > templateLoader}.options" }, "prefsEditorLoader.templateLoader.terms": { source: "{that}.options.terms", target: "{that > templateLoader}.options.resourceOptions.terms" }, "prefsEditorLoader.messageLoader": { source: "{that}.options.messageLoader", removeSource: true, target: "{that > messageLoader}.options" }, "prefsEditorLoader.messageLoader.terms": { source: "{that}.options.terms", target: "{that > messageLoader}.options.resourceOptions.terms" }, "prefsEditorLoader.prefsEditor": { source: "{that}.options.prefsEditor", removeSource: true, target: "{that > prefsEditor}.options" } } }); fluid.prefs.prefsEditorLoader.getInitialSettings = function (that) { var promise = fluid.promise(); var fetchPromise = that.getSettings(); fetchPromise.then(function (savedSettings) { that.settings = $.extend(true, {}, that.initialModel, savedSettings); that.events.afterInitialSettingsFetched.fire(that.settings); }, function (error) { fluid.log(fluid.logLevel.WARN, error); that.settings = that.initialModel; that.events.afterInitialSettingsFetched.fire(that.settings); }); fluid.promise.follow(fetchPromise, promise); return promise; }; // TODO: This mixin grade appears to be supplied manually by various test cases but no longer appears in // the main configuration. We should remove the need for users to supply this - also the use of "defaultPanels" in fact // refers to "starter panels" fluid.defaults("fluid.prefs.transformDefaultPanelsOptions", { // Do not supply "fluid.prefs.inline" here, since when this is used as a mixin for separatedPanel, it ends up displacing the // more refined type of the prefsEditorLoader gradeNames: ["fluid.viewComponent"], distributeOptions: { "transformDefaultPanelsOptions.textSize": { source: "{that}.options.textSize", removeSource: true, target: "{that textSize}.options" }, "transformDefaultPanelsOptions.lineSpace": { source: "{that}.options.lineSpace", removeSource: true, target: "{that lineSpace}.options" }, "transformDefaultPanelsOptions.textFont": { source: "{that}.options.textFont", removeSource: true, target: "{that textFont}.options" }, "transformDefaultPanelsOptions.contrast": { source: "{that}.options.contrast", removeSource: true, target: "{that contrast}.options" }, "transformDefaultPanelsOptions.layoutControls": { source: "{that}.options.layoutControls", removeSource: true, target: "{that layoutControls}.options" }, "transformDefaultPanelsOptions.enhanceInputs": { source: "{that}.options.enhanceInputs", removeSource: true, target: "{that enhanceInputs}.options" } } }); /********************** * Preferences Editor * **********************/ fluid.defaults("fluid.prefs.settingsGetter", { gradeNames: ["fluid.component"], members: { getSettings: "{fluid.prefs.store}.get" } }); fluid.defaults("fluid.prefs.settingsSetter", { gradeNames: ["fluid.component"], invokers: { setSettings: { funcName: "fluid.prefs.settingsSetter.setSettings", args: ["{arguments}.0", "{arguments}.1", "{fluid.prefs.store}.set"] } } }); fluid.prefs.settingsSetter.setSettings = function (model, directModel, set) { var userSettings = fluid.copy(model); return set(directModel, userSettings); }; fluid.defaults("fluid.prefs.uiEnhancerRelay", { gradeNames: ["fluid.modelComponent"], listeners: { "onCreate.addListener": "{that}.addListener", "onDestroy.removeListener": "{that}.removeListener" }, events: { updateEnhancerModel: "{fluid.prefs.prefsEditor}.events.onUpdateEnhancerModel" }, invokers: { addListener: { funcName: "fluid.prefs.uiEnhancerRelay.addListener", args: ["{that}.events.updateEnhancerModel", "{that}.updateEnhancerModel"] }, removeListener: { funcName: "fluid.prefs.uiEnhancerRelay.removeListener", args: ["{that}.events.updateEnhancerModel", "{that}.updateEnhancerModel"] }, updateEnhancerModel: { funcName: "fluid.prefs.uiEnhancerRelay.updateEnhancerModel", args: ["{uiEnhancer}", "{fluid.prefs.prefsEditor}.model.preferences"] } } }); fluid.prefs.uiEnhancerRelay.addListener = function (modelChanged, listener) { modelChanged.addListener(listener); }; fluid.prefs.uiEnhancerRelay.removeListener = function (modelChanged, listener) { modelChanged.removeListener(listener); }; fluid.prefs.uiEnhancerRelay.updateEnhancerModel = function (uiEnhancer, newModel) { uiEnhancer.updateModel(newModel); }; /* * A component that works in conjunction with the UI Enhancer component * to allow users to set personal user interface preferences. The Preferences Editor component provides a user * interface for setting and saving personal preferences, and the UI Enhancer component carries out the * work of applying those preferences to the user interface. */ fluid.defaults("fluid.prefs.prefsEditor", { gradeNames: ["fluid.prefs.settingsGetter", "fluid.prefs.settingsSetter", "fluid.prefs.initialModel", "fluid.remoteModelComponent", "fluid.viewComponent"], markup: { // only used when dynamically generating the panel containers. panel: "<li class=\"%className flc-prefsEditor-panel fl-prefsEditor-panel\"></li>" }, invokers: { // Updates the change applier and fires modelChanged on subcomponent fluid.prefs.controls fetchImpl: { funcName: "fluid.prefs.prefsEditor.fetchImpl", args: ["{that}"] }, writeImpl: { funcName: "fluid.prefs.prefsEditor.writeImpl", args: ["{that}", "{arguments}.0"] }, applyChanges: { funcName: "fluid.prefs.prefsEditor.applyChanges", args: ["{that}"] }, save: { funcName: "fluid.prefs.prefsEditor.save", args: ["{that}"] }, saveAndApply: { funcName: "fluid.prefs.prefsEditor.saveAndApply", args: ["{that}"] }, reset: { funcName: "fluid.prefs.prefsEditor.reset", args: ["{that}"] }, cancel: { funcName: "fluid.prefs.prefsEditor.cancel", args: ["{that}"] } }, selectors: { panels: ".flc-prefsEditor-panel", cancel: ".flc-prefsEditor-cancel", reset: ".flc-prefsEditor-reset", save: ".flc-prefsEditor-save", previewFrame : ".flc-prefsEditor-preview-frame" }, events: { onSave: null, onCancel: null, beforeReset: null, afterReset: null, onAutoSave: null, modelChanged: null, onPrefsEditorRefresh: null, onUpdateEnhancerModel: null, onPrefsEditorMarkupReady: null, onReady: null }, listeners: { "onCreate.init": "fluid.prefs.prefsEditor.init", "onAutoSave.save": "{that}.save" }, model: { local: { preferences: "{that}.model.preferences" } }, modelListeners: { "preferences": [{ listener: "fluid.prefs.prefsEditor.handleAutoSave", args: ["{that}"], namespace: "autoSave", excludeSource: ["init"] }, { listener: "{that}.events.modelChanged.fire", args: ["{change}.value"], namespace: "modelChange" }] }, members: { resources: { template: "{templateLoader}.resources.prefsEditor" } }, autoSave: false }); /* * Refresh PrefsEditor */ fluid.prefs.prefsEditor.applyChanges = function (that) { that.events.onUpdateEnhancerModel.fire(); }; fluid.prefs.prefsEditor.fetchImpl = function (that) { var promise = fluid.promise(), fetchPromise = that.getSettings(); fetchPromise.then(function (savedModel) { var completeModel = $.extend(true, {}, that.initialModel, savedModel); promise.resolve(completeModel); }, promise.reject); return promise; }; fluid.prefs.prefsEditor.writeImpl = function (that, modelToSave) { var promise = fluid.promise(), stats = {changes: 0, unchanged: 0, changeMap: {}}, changedPrefs = {}; modelToSave = fluid.copy(modelToSave); // To address https://issues.fluidproject.org/browse/FLUID-4686 fluid.model.diff(modelToSave.preferences, fluid.get(that.initialModel, ["preferences"]), stats); if (stats.changes === 0) { delete modelToSave.preferences; } else { fluid.each(stats.changeMap, function (state, pref) { fluid.set(changedPrefs, pref, modelToSave.preferences[pref]); }); modelToSave.preferences = changedPrefs; } that.events.onSave.fire(modelToSave); var setPromise = that.setSettings(modelToSave); fluid.promise.follow(setPromise, promise); return promise; }; /** * Sends the prefsEditor.model to the store and fires onSave * * @param {fluid.prefs.prefsEditor} that - A `fluid.prefs.prefsEditor` instance * @return {Promise} A promise that will be resolved with the saved model or rejected on error. */ fluid.prefs.prefsEditor.save = function (that) { var promise = fluid.promise(); if (!that.model || $.isEmptyObject(that.model)) { // Don't save a reset model promise.resolve({}); } else { var writePromise = that.write(); fluid.promise.follow(writePromise, promise); } return promise; }; fluid.prefs.prefsEditor.saveAndApply = function (that) { var promise = fluid.promise(); var prevSettingsPromise = that.getSettings(), savePromise = that.save(); prevSettingsPromise.then(function (prevSettings) { savePromise.then(function (changedSelections) { // Only when preferences are changed, re-render panels and trigger enactors to apply changes if (!fluid.model.diff(fluid.get(changedSelections, "preferences"), fluid.get(prevSettings, "preferences"))) { that.events.onPrefsEditorRefresh.fire(); that.applyChanges(); } }); fluid.promise.follow(savePromise, promise); }); return promise; }; fluid.prefs.prefsEditor.resetModel = function (that, newModel) { var transaction = that.applier.initiate(); transaction.fireChangeRequest({path: "preferences", type: "DELETE"}); transaction.change("", newModel); transaction.commit(); that.events.onPrefsEditorRefresh.fire(); }; /* * Resets the selections to the integrator's defaults and fires afterReset */ fluid.prefs.prefsEditor.reset = function (that) { that.events.beforeReset.fire(that); fluid.prefs.prefsEditor.resetModel(that, fluid.copy(that.initialModel)); that.events.afterReset.fire(that); }; /* * Fires onCancel and resets the selections to the last saved selections */ fluid.prefs.prefsEditor.cancel = function (that) { that.events.onCancel.fire(); var fetchPromise = that.fetch(); fetchPromise.then(function () { fluid.prefs.prefsEditor.resetModel(that, that.model.remote); }); }; // called once markup is applied to the document containing tab component roots fluid.prefs.prefsEditor.finishInit = function (that) { var bindHandlers = function (that) { var saveButton = that.locate("save"); if (saveButton.length > 0) { saveButton.on("click", that.saveAndApply); var form = fluid.findForm(saveButton); $(form).on("submit", function () { that.saveAndApply(); }); } that.locate("reset").on("click", that.reset); that.locate("cancel").on("click", that.cancel); }; var template = that.resources.template.resourceText; if (that.options.generatePanelContainers) { var sorted = fluid.parsePriorityRecords(that.options.preferences, "preferences"); // sort the preferences var sortedPrefs = fluid.getMembers(sorted, "namespace"); // retrieve just the preferences names // generate panel container markup var panels = sortedPrefs.map(function (pref) { var className = that.options.selectors[that.options.prefToMemberMap[pref]].slice(1); return fluid.stringTemplate(that.options.markup.panel, {className: className}); }).join(""); // interpolate panels into template. template = fluid.stringTemplate(template, {panels: panels}); } that.container.append(template); bindHandlers(that); var fetchPromise = that.fetch(); fetchPromise.then(function () { that.events.onPrefsEditorMarkupReady.fire(); that.events.onPrefsEditorRefresh.fire(); that.applyChanges(); that.events.onReady.fire(that); }); }; fluid.prefs.prefsEditor.handleAutoSave = function (that) { if (that.options.autoSave) { that.events.onAutoSave.fire(); } }; fluid.prefs.prefsEditor.init = function (that) { // This setTimeout is to ensure that fetching of resources is asynchronous, // and so that component construction does not run ahead of subcomponents for SeparatedPanel // (FLUID-4453 - this may be a replacement for a branch removed for a FLUID-2248 fix) setTimeout(function () { if (!fluid.isDestroyed(that)) { fluid.prefs.prefsEditor.finishInit(that); } }, 1); }; /****************************** * Preferences Editor Preview * ******************************/ fluid.defaults("fluid.prefs.preview", { gradeNames: ["fluid.viewComponent"], components: { enhancer: { type: "fluid.uiEnhancer", container: "{preview}.enhancerContainer", createOnEvent: "onReady" }, templateLoader: "{templateLoader}" }, invokers: { updateModel: { funcName: "fluid.prefs.preview.updateModel", args: [ "{preview}", "{prefsEditor}.model.preferences" ] } }, events: { onReady: null }, listeners: { "onCreate.startLoadingContainer": "fluid.prefs.preview.startLoadingContainer", "{prefsEditor}.events.modelChanged": { listener: "{that}.updateModel", namespace: "updateModel" }, "onReady.updateModel": "{that}.updateModel" }, templateUrl: "%templatePrefix/PrefsEditorPreview.html" }); fluid.prefs.preview.updateModel = function (that, preferences) { /** * SetTimeout is temp fix for http://issues.fluidproject.org/browse/FLUID-2248 */ setTimeout(function () { if (that.enhancer) { that.enhancer.updateModel(preferences); } }, 0); }; fluid.prefs.preview.startLoadingContainer = function (that) { // TODO: Don't reach into the impl in this way and make the head component a resourceLoader var templateUrl = that.templateLoader.transformResourceURL(that.options.templateUrl); that.container.on("load", function () { that.enhancerContainer = $("body", that.container.contents()); that.events.onReady.fire(); }); that.container.attr("src", templateUrl); };