UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

1,030 lines (948 loc) 40.7 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"; /********************** * msgLookup grade * **********************/ fluid.defaults("fluid.prefs.msgLookup", { gradeNames: ["fluid.component"], members: { msgLookup: { expander: { funcName: "fluid.prefs.stringLookup", args: ["{msgResolver}", "{that}.options.stringArrayIndex"] } } }, stringArrayIndex: {} }); fluid.prefs.stringLookup = function (messageResolver, stringArrayIndex) { var that = {id: fluid.allocateGuid()}; that.singleLookup = function (value) { var looked = messageResolver.lookup([value]); return fluid.get(looked, "template"); }; that.multiLookup = function (values) { return fluid.transform(values, function (value) { return that.singleLookup(value); }); }; that.lookup = function (value) { var values = fluid.get(stringArrayIndex, value) || value; var lookupFn = fluid.isArrayable(values) ? "multiLookup" : "singleLookup"; return that[lookupFn](values); }; that.resolvePathSegment = that.lookup; return that; }; /*********************************************** * Base grade panel ***********************************************/ fluid.defaults("fluid.prefs.panel", { gradeNames: ["fluid.prefs.msgLookup", "fluid.rendererComponent"], // TODO: This event moved into fluid.viewComponent /* events: { onDomBind: null }, // Any listener that requires a DOM element, should be registered // to the onDomBind listener. By default it is fired by onCreate, but // when used as a subpanel, it will be triggered by the resetDomBinder invoker. listeners: { "onCreate.onDomBind": "{that}.events.onDomBind" },*/ components: { msgResolver: { type: "fluid.messageResolver" }, // TODO: note that rendererComponent now configures such a resolver named "messageResolver" messageResolver: { type: "fluid.emptySubcomponent" } }, rendererOptions: { messageLocator: "{msgResolver}.resolve" }, distributeOptions: { "panel.msgResolver.messageBase": { source: "{that}.options.messageBase", target: "{that > msgResolver}.options.messageBase" } } }); /*************************** * Base grade for subpanel * ***************************/ fluid.defaults("fluid.prefs.subPanel", { gradeNames: ["fluid.prefs.panel"], listeners: { "{compositePanel}.events.afterRender": { listener: "{that}.events.afterRender", args: ["{that}"], namespce: "boilAfterRender" }, // Changing the firing of onDomBind from the onCreate. // This is due to the fact that the rendering process, controlled by the // composite panel, will set/replace the DOM elements. "onCreate.onDomBind": null, // remove listener "afterRender.onDomBind": "{that}.resetDomBinder" }, rules: { expander: { func: "fluid.prefs.subPanel.generateRules", args: ["{that}.options.preferenceMap"] } }, invokers: { refreshView: "{compositePanel}.refreshView", // resetDomBinder must fire the onDomBind event resetDomBinder: { funcName: "fluid.prefs.subPanel.resetDomBinder", args: ["{that}"] } }, strings: {}, parentBundle: "{compositePanel}.messageResolver", renderOnInit: false }); /* * Since the composite panel manages the rendering of the subpanels * the markup used by subpanels needs to be completely replaced. * The subpanel's container is refereshed to point at the newly * rendered markup, and the domBinder is re-initialized. Once * this is all done, the onDomBind event is fired. */ fluid.prefs.subPanel.resetDomBinder = function (that) { // TODO: The line below to find the container jQuery instance was copied from the framework code - // https://github.com/fluid-project/infusion/blob/main/src/framework/core/js/FluidView.js#L145 // It can be be eliminated once we have the new renderer. var userJQuery = that.container.constructor; var context = that.container[0].ownerDocument; var selector = that.container.selector; that.container = userJQuery(selector, context); // To address FLUID-5966, manually adding back the selector and context properties that were removed from jQuery v3.0. // ( see: https://jquery.com/upgrade-guide/3.0/#breaking-change-deprecated-context-and-selector-properties-removed ) // In most cases the "selector" property will already be restored through the DOM binder or fluid.container. // However, in this case we are manually recreating the container to ensure that it is referencing an element currently added // to the correct Document ( e.g. iframe ) (also see: FLUID-4536). This manual recreation of the container requires us to // manually add back the selector and context from the original container. This code and fix parallels that in // FluidView.js fluid.container line 129 that.container.selector = selector; that.container.context = context; if (that.container.length === 0) { fluid.fail("resetDomBinder got no elements in DOM for container searching for selector " + that.container.selector); } that.dom.resetContainer(that.container); that.events.onDomBind.fire(that); }; fluid.prefs.subPanel.safePrefKey = function (prefKey) { return prefKey.replace(/[.]/g, "_"); }; /* * Generates the model relay rules for a subpanel. * Takes advantage of the fact that compositePanel * uses the preference key (with "." replaced by "_"), * as its model path. */ fluid.prefs.subPanel.generateRules = function (preferenceMap) { var rules = {}; fluid.each(preferenceMap, function (prefObj, prefKey) { fluid.each(prefObj, function (value, prefRule) { if (prefRule.indexOf("model.") === 0) { rules[fluid.prefs.subPanel.safePrefKey(prefKey)] = prefRule.slice("model.".length); } }); }); return rules; }; /********************************** * Base grade for composite panel * **********************************/ fluid.registerNamespace("fluid.prefs.compositePanel"); fluid.prefs.compositePanel.arrayMergePolicy = function (target, source) { target = fluid.makeArray(target); source = fluid.makeArray(source); fluid.each(source, function (selector) { if (target.indexOf(selector) < 0) { target.push(selector); } }); return target; }; fluid.prefs.appendTemplate = function (container, markup) { container.append(markup); }; fluid.defaults("fluid.prefs.compositePanel", { gradeNames: ["fluid.prefs.panel", "{that}.getDistributeOptionsGrade", "{that}.getSubPanelLifecycleBindings"], mergePolicy: { subPanelOverrides: "noexpand", selectorsToIgnore: fluid.prefs.compositePanel.arrayMergePolicy }, selectors: {}, // requires selectors into the template which will act as the containers for the subpanels selectorsToIgnore: [], // should match the selectors that are used to identify the containers for the subpanels repeatingSelectors: [], events: { initSubPanels: null }, listeners: { "onCreate.combineResources": "{that}.combineResources", "onCreate.appendTemplate": "fluid.prefs.appendTemplate({that}.container, {that}.resources.template.resourceText)", "onCreate.initSubPanels": "{that}.events.initSubPanels", "onCreate.hideInactive": { func: "{that}.hideInactive", priority: "after:fluid-componentConstruction" }, "afterRender.hideInactive": "{that}.hideInactive" }, invokers: { getDistributeOptionsGrade: { funcName: "fluid.prefs.compositePanel.assembleDistributeOptions", args: ["{that}.options.components"] }, getSubPanelLifecycleBindings: { funcName: "fluid.prefs.compositePanel.subPanelLifecycleBindings", args: ["{that}", "{that}.options.components"] }, combineResources: { funcName: "fluid.prefs.compositePanel.combineTemplates", args: ["{that}.resources", "{that}.options.selectors"] }, produceSubPanelTrees: { funcName: "fluid.prefs.compositePanel.produceSubPanelTrees", args: ["{that}"] }, expandProtoTree: { funcName: "fluid.prefs.compositePanel.expandProtoTree", args: ["{that}"] }, produceTree: { funcName: "fluid.prefs.compositePanel.produceTree", args: ["{that}"] }, hideInactive: { funcName: "fluid.prefs.compositePanel.hideInactive", args: ["{that}"] }, handleRenderOnPreference: { funcName: "fluid.prefs.compositePanel.handleRenderOnPreference", args: ["{that}", "{that}.refreshView", "{that}.conditionalCreateEvent", "{arguments}.0", "{arguments}.1", "{arguments}.2"] }, conditionalCreateEvent: { funcName: "fluid.prefs.compositePanel.conditionalCreateEvent" } }, subPanelOverrides: { gradeNames: ["fluid.prefs.subPanel"] }, rendererFnOptions: { noexpand: true, cutpointGenerator: "fluid.prefs.compositePanel.cutpointGenerator", subPanelRepeatingSelectors: { expander: { funcName: "fluid.prefs.compositePanel.surfaceRepeatingSelectors", args: ["{that}.options.components"] } } }, components: {}, resources: {} // template is reserved for the compositePanel's template, the subpanel template should have same key as the selector for its container. }); /* * Attempts to prefetch a components options before it is instantiated. * Only use in cases where the instantiated component cannot be used. */ fluid.prefs.compositePanel.prefetchComponentOptions = function (type, options) { var baseOptions = fluid.getMergedDefaults(type, fluid.get(options, "gradeNames")); // TODO: awkwardly, fluid.merge is destructive on each argument! return baseOptions && fluid.merge(baseOptions.mergePolicy, fluid.copy(baseOptions), options); }; /* * Should only be used when fluid.prefs.compositePanel.isActivatePanel cannot. * While this implementation doesn't require an instantiated component, it may in * the process miss some configuration provided by distribute options and demands. */ fluid.prefs.compositePanel.isPanel = function (type, options) { var opts = fluid.prefs.compositePanel.prefetchComponentOptions(type, options); return fluid.hasGrade(opts, "fluid.prefs.panel"); }; fluid.prefs.compositePanel.isActivePanel = function (comp) { return comp && fluid.hasGrade(comp.options, "fluid.prefs.panel"); }; /* * Creates a grade containing the distributeOptions rules needed for the subcomponents */ fluid.prefs.compositePanel.assembleDistributeOptions = function (components) { var gradeName = "fluid.prefs.compositePanel.distributeOptions_" + fluid.allocateGuid(); var distributeOptions = {}; var relayOption = {}; fluid.each(components, function (componentEntry, componentName) { var componentOptions = componentEntry[0]; if (fluid.prefs.compositePanel.isPanel(componentOptions.type, componentOptions.options)) { distributeOptions[componentName + ".subPanelOverrides"] = { source: "{that}.options.subPanelOverrides", target: "{that > " + componentName + "}.options" }; } // Construct the model relay btw the composite panel and its subpanels var componentRelayRules = {}; var definedOptions = fluid.prefs.compositePanel.prefetchComponentOptions(componentOptions.type, componentOptions.options); var preferenceMap = fluid.get(definedOptions, ["preferenceMap"]); fluid.each(preferenceMap, function (prefObj, prefKey) { fluid.each(prefObj, function (value, prefRule) { if (prefRule.indexOf("model.") === 0) { fluid.set(componentRelayRules, prefRule.slice("model.".length), "{compositePanel}.model." + fluid.prefs.subPanel.safePrefKey(prefKey)); } }); }); relayOption[componentName] = componentRelayRules; distributeOptions[componentName + ".modelRelay"] = { source: "{that}.options.relayOption." + componentName, target: "{that > " + componentName + "}.options.model" }; }); fluid.defaults(gradeName, { relayOption: relayOption, distributeOptions: distributeOptions }); return gradeName; }; fluid.prefs.compositePanel.conditionalCreateEvent = function (value, createEvent) { if (value) { createEvent(); } }; fluid.prefs.compositePanel.handleRenderOnPreference = function (that, refreshViewFunc, conditionalCreateEventFunc, value, createEvent, componentNames) { componentNames = fluid.makeArray(componentNames); conditionalCreateEventFunc(value, createEvent); fluid.each(componentNames, function (componentName) { var comp = that[componentName]; if (!value && comp) { comp.destroy(); } }); refreshViewFunc(); }; fluid.prefs.compositePanel.creationEventName = function (pref) { return "initOn_" + pref; }; fluid.prefs.compositePanel.generateModelListeners = function (conditionals) { return fluid.transform(conditionals, function (componentNames, pref) { var eventName = fluid.prefs.compositePanel.creationEventName(pref); return { func: "{that}.handleRenderOnPreference", args: ["{change}.value", "{that}.events." + eventName + ".fire", componentNames], excludeSource: "init", namespace: "handleRenderOnPreference_" + pref }; }); }; fluid.prefs.compositePanel.rebaseSelectorName = function (memberName, selectorName) { return memberName + "_" + selectorName; }; fluid.prefs.compositePanel.rebaseSelector = function (compositePanelSelector, selector) { return compositePanelSelector + " " + selector; }; /* * Creates a grade containing all of the lifecycle binding configuration needed for the subpanels. * This includes the following: * - adding events used to trigger the initialization of the subpanels * - adding the createOnEvent configuration for the subpanels * - binding handlers to model changed events * - binding handlers to afterRender and onCreate * - surfacing selectors from the subpanels to the composite panel */ fluid.prefs.compositePanel.subPanelLifecycleBindings = function (that, components) { var gradeName = "fluid.prefs.compositePanel.subPanelCreationTimingDistibution_" + fluid.allocateGuid(); var distributeOptions = {}; var subPanelCreationOpts = { "default": "initSubPanels" }; var conditionals = {}; var listeners = {}; var events = {}; var selectors = {}; // TODO: All of this old-style options introspection is deprecated and should be replaced by workflow functions - // but in practice the entirety of this prefs framework should be deprecated and replaced by the new renderer fluid.each(components, function (componentEntry, componentName) { var componentOptions = componentEntry[0]; // Compensate for wrapping caused by FLUID-5614 fix if (fluid.prefs.compositePanel.isPanel(componentOptions.type, componentOptions.options)) { var creationEventOpt = "default"; // would have had renderOnPreference directly sourced from the componentOptions // however, the set of configuration specified there is restricted. var renderOnPreference = fluid.get(componentOptions, "options.renderOnPreference"); if (renderOnPreference) { var pref = fluid.prefs.subPanel.safePrefKey(renderOnPreference); var onCreateListener = "onCreate." + pref; creationEventOpt = fluid.prefs.compositePanel.creationEventName(pref); subPanelCreationOpts[creationEventOpt] = creationEventOpt; events[creationEventOpt] = null; conditionals[pref] = conditionals[pref] || []; conditionals[pref].push(componentName); listeners[onCreateListener] = { listener: "{that}.conditionalCreateEvent", args: ["{that}.model." + pref, "{that}.events." + creationEventOpt + ".fire"] }; } distributeOptions[componentName + ".subPanelCreationOpts"] = { source: "{that}.options.subPanelCreationOpts." + creationEventOpt, target: "{that}.options.components." + componentName + ".createOnEvent" }; var opts = fluid.prefs.compositePanel.prefetchComponentOptions(componentOptions.type, componentOptions.options); fluid.each(opts.selectors, function (selector, selName) { if (!opts.selectorsToIgnore || opts.selectorsToIgnore.indexOf(selName) < 0) { // Sets an expander for each surfaced selector because we need to prepend the the subpanel's own // container selector to ensure that the dom binder scopes those selectors to the appropriate // component's container. Other options for obtaining the composite panel's container required // either modifying the options after resolution or would trigger the resolution of composite // panel's selectors and prevent accepting any more options merged on top. selectors[fluid.prefs.compositePanel.rebaseSelectorName(componentName, selName)] = { expander: { funcName: "fluid.prefs.compositePanel.rebaseSelector", args: ["{that}.options.selectors." + componentName, selector] } }; } }); } }); fluid.defaults(gradeName, { events: events, listeners: listeners, modelListeners: fluid.prefs.compositePanel.generateModelListeners(conditionals), subPanelCreationOpts: subPanelCreationOpts, distributeOptions: distributeOptions, selectors: selectors }); return gradeName; }; /* * Used to hide the containers of inactive sub panels. * This is necessary as the composite panel's template is the one that has their containers and * it would be undesirable to have them visible when their associated panel has not been created. * Also, hiding them allows for the subpanel to initialize, as it requires their container to be present. * The subpanels need to be initialized before rendering, for the produce function to source the rendering * information from it. */ fluid.prefs.compositePanel.hideInactive = function (that) { fluid.each(that.options.components, function (componentEntry, componentName) { var componentOpts = componentEntry[0]; // See above for FLUID-5614 if (fluid.prefs.compositePanel.isPanel(componentOpts.type, componentOpts.options) && !fluid.prefs.compositePanel.isActivePanel(that[componentName])) { that.locate(componentName).hide(); } }); }; /* * Use the renderer directly to combine the templates into a single * template to be used by the components actual rendering. */ fluid.prefs.compositePanel.combineTemplates = function (resources, selectors) { var cutpoints = []; var tree = {children: []}; fluid.each(resources, function (resource, resourceName) { if (resourceName !== "template") { tree.children.push({ ID: resourceName, markup: resource.resourceText }); cutpoints.push({ id: resourceName, selector: selectors[resourceName] }); } }); var resourceSpec = { base: { resourceText: resources.template.resourceText, url: ".", resourceKey: ".", cutpoints: cutpoints } }; var templates = fluid.parseTemplates(resourceSpec, ["base"]); var renderer = fluid.renderer(templates, tree, {cutpoints: cutpoints, debugMode: true}); resources.template.resourceText = renderer.renderTemplates(); }; fluid.prefs.compositePanel.surfaceRepeatingSelectors = function (components) { var repeatingSelectors = []; fluid.each(components, function (compEntry, compName) { var compOpts = compEntry[0]; if (fluid.prefs.compositePanel.isPanel(compOpts.type, compOpts.options)) { var opts = fluid.prefs.compositePanel.prefetchComponentOptions(compOpts.type, compOpts.options); var rebasedRepeatingSelectors = fluid.transform(opts.repeatingSelectors, function (selector) { return fluid.prefs.compositePanel.rebaseSelectorName(compName, selector); }); repeatingSelectors = repeatingSelectors.concat(rebasedRepeatingSelectors); } }); return repeatingSelectors; }; fluid.prefs.compositePanel.cutpointGenerator = function (selectors, options) { var opts = { selectorsToIgnore: options.selectorsToIgnore, repeatingSelectors: options.repeatingSelectors.concat(options.subPanelRepeatingSelectors) }; return fluid.renderer.selectorsToCutpoints(selectors, opts); }; fluid.prefs.compositePanel.rebaseID = function (value, memberName) { return memberName + "_" + value; }; fluid.prefs.compositePanel.rebaseParentRelativeID = function (val, memberName) { var slicePos = "..::".length; // ..:: refers to the parentRelativeID prefix used in the renderer return val.slice(0, slicePos) + fluid.prefs.compositePanel.rebaseID(val.slice(slicePos), memberName); }; fluid.prefs.compositePanel.rebaseValueBinding = function (value, modelRelayRules) { return fluid.find(modelRelayRules, function (oldModelPath, newModelPath) { if (value === oldModelPath) { return newModelPath; } else if (value.indexOf(oldModelPath) === 0) { return value.replace(oldModelPath, newModelPath); } }) || value; }; fluid.prefs.compositePanel.rebaseTreeComp = function (msgResolver, model, treeComp, memberName, modelRelayRules) { var rebased = fluid.copy(treeComp); if (rebased.ID) { rebased.ID = fluid.prefs.compositePanel.rebaseID(rebased.ID, memberName); } if (rebased.children) { rebased.children = fluid.prefs.compositePanel.rebaseTree(msgResolver, model, rebased.children, memberName, modelRelayRules); } else if (rebased.selection) { rebased.selection = fluid.prefs.compositePanel.rebaseTreeComp(msgResolver, model, rebased.selection, memberName, modelRelayRules); } else if (rebased.messagekey) { // converts the "UIMessage" renderer component into a "UIBound" // and passes in the resolved message as the value. rebased.componentType = "UIBound"; rebased.value = msgResolver.resolve(rebased.messagekey.value, rebased.messagekey.args); delete rebased.messagekey; } else if (rebased.parentRelativeID) { rebased.parentRelativeID = fluid.prefs.compositePanel.rebaseParentRelativeID(rebased.parentRelativeID, memberName); } else if (rebased.valuebinding) { rebased.valuebinding = fluid.prefs.compositePanel.rebaseValueBinding(rebased.valuebinding, modelRelayRules); if (rebased.value) { var modelValue = fluid.get(model, rebased.valuebinding); rebased.value = modelValue !== undefined ? modelValue : rebased.value; } } return rebased; }; fluid.prefs.compositePanel.rebaseTree = function (msgResolver, model, tree, memberName, modelRelayRules) { var rebased; if (fluid.isArrayable(tree)) { rebased = fluid.transform(tree, function (treeComp) { return fluid.prefs.compositePanel.rebaseTreeComp(msgResolver, model, treeComp, memberName, modelRelayRules); }); } else { rebased = fluid.prefs.compositePanel.rebaseTreeComp(msgResolver, model, tree, memberName, modelRelayRules); } return rebased; }; fluid.prefs.compositePanel.produceTree = function (that) { var produceTreeOption = that.options.produceTree; var ownTree = produceTreeOption ? (typeof(produceTreeOption) === "string" ? fluid.getGlobalValue(produceTreeOption) : produceTreeOption)(that) : that.expandProtoTree(); var subPanelTree = that.produceSubPanelTrees(); var tree = { children: ownTree.children.concat(subPanelTree.children) }; return tree; }; fluid.prefs.compositePanel.expandProtoTree = function (that) { var expanderOptions = fluid.renderer.modeliseOptions(that.options.expanderOptions, {ELstyle: "${}"}, that); var expander = fluid.renderer.makeProtoExpander(expanderOptions, that); return expander(that.options.protoTree || {}); }; fluid.prefs.compositePanel.produceSubPanelTrees = function (that) { var tree = {children: []}; fluid.each(that.options.components, function (options, componentName) { var subPanel = that[componentName]; if (fluid.prefs.compositePanel.isActivePanel(subPanel)) { var expanderOptions = fluid.renderer.modeliseOptions(subPanel.options.expanderOptions, {ELstyle: "${}"}, subPanel); var expander = fluid.renderer.makeProtoExpander(expanderOptions, subPanel); var subTree = subPanel.produceTree(); subTree = fluid.get(subPanel.options, "rendererFnOptions.noexpand") ? subTree : expander(subTree); var rebasedTree = fluid.prefs.compositePanel.rebaseTree(subPanel.msgResolver, that.model, subTree, componentName, subPanel.options.rules); tree.children = tree.children.concat(rebasedTree.children); } }); return tree; }; /******************************************************************************** * The grade that contains the connections between a panel and the prefs editor * ********************************************************************************/ fluid.defaults("fluid.prefs.prefsEditorConnections", { gradeNames: ["fluid.component"], listeners: { // No namespace supplied because this grade is added to every panel. Suppling a // namespace would mean that only one panel's refreshView method was bound to the // onPrefsEditorRefresh event. "{fluid.prefs.prefsEditor}.events.onPrefsEditorRefresh": "{fluid.prefs.panel}.refreshView" }, strings: {}, parentBundle: "{fluid.prefs.prefsEditorLoader}.msgResolver" }); /******************************************* * A base grade for switch adjuster panels * *******************************************/ fluid.defaults("fluid.prefs.panel.switchAdjuster", { gradeNames: ["fluid.prefs.panel"], // preferences maps should map model values to "model.value" // model: {value: ""} selectors: { header: ".flc-prefsEditor-header", switchContainer: ".flc-prefsEditor-switch", label: ".flc-prefsEditor-label", description: ".flc-prefsEditor-description" }, selectorsToIgnore: ["header", "switchContainer"], components: { switchUI: { type: "fluid.switchUI", container: "{that}.dom.switchContainer", createOnEvent: "afterRender", options: { strings: { on: "{fluid.prefs.panel.switchAdjuster}.msgLookup.switchOn", off: "{fluid.prefs.panel.switchAdjuster}.msgLookup.switchOff" }, model: { enabled: "{fluid.prefs.panel.switchAdjuster}.model.value" }, attrs: { "aria-labelledby": { expander: { funcName: "fluid.allocateSimpleId", args: ["{fluid.prefs.panel.switchAdjuster}.dom.description"] } } } } } }, protoTree: { label: {messagekey: "label"}, description: {messagekey: "description"} } }); /************************************************ * A base grade for themePicker adjuster panels * ************************************************/ fluid.defaults("fluid.prefs.panel.themePicker", { gradeNames: ["fluid.prefs.panel"], mergePolicy: { "controlValues.theme": "replace", "stringArrayIndex.theme": "replace" }, // The controlValues are the ordered set of possible modelValues corresponding to each theme option. // The order in which they are listed will determine the order they are presented in the UI. // The stringArrayIndex contains the ordered set of namespaced strings in the message bundle. // The order must match the controlValues in order to provide the proper labels to the theme options. controlValues: { theme: [] // must be supplied by the integrator }, stringArrayIndex: { theme: [] // must be supplied by the integrator }, selectID: "{that}.id", // used for the name attribute to group the selection options listeners: { "afterRender.style": "{that}.style" }, selectors: { themeRow: ".flc-prefsEditor-themeRow", themeLabel: ".flc-prefsEditor-theme-label", themeInput: ".flc-prefsEditor-themeInput", label: ".flc-prefsEditor-themePicker-label", description: ".flc-prefsEditor-themePicker-descr" }, styles: { defaultThemeLabel: "fl-prefsEditor-themePicker-defaultThemeLabel" }, repeatingSelectors: ["themeRow"], protoTree: { label: {messagekey: "label"}, description: {messagekey: "description"}, expander: { type: "fluid.renderer.selection.inputs", rowID: "themeRow", labelID: "themeLabel", inputID: "themeInput", selectID: "{that}.options.selectID", tree: { optionnames: "${{that}.msgLookup.theme}", optionlist: "${{that}.options.controlValues.theme}", selection: "${value}" } } }, markup: { // Aria-hidden needed on fl-preview-A and Display 'a' created as pseudo-content in css to prevent AT from reading out display 'a' on IE, Chrome, and Safari // Aria-hidden needed on fl-crossout to prevent AT from trying to read crossout symbol in Safari label: "<span class=\"fl-preview-A\" aria-hidden=\"true\"></span><span class=\"fl-hidden-accessible\">%theme</span><div class=\"fl-crossout\" aria-hidden=\"true\"></div>" }, invokers: { style: { funcName: "fluid.prefs.panel.themePicker.style", args: [ "{that}.dom.themeLabel", "{that}.msgLookup.theme", "{that}.options.markup.label", "{that}.options.controlValues.theme", "default", "{that}.options.classnameMap.theme", "{that}.options.styles.defaultThemeLabel" ] } } }); fluid.prefs.panel.themePicker.style = function (labels, strings, markup, theme, defaultThemeName, style, defaultLabelStyle) { fluid.each(labels, function (label, index) { label = $(label); var themeValue = strings[index]; label.html(fluid.stringTemplate(markup, { theme: themeValue })); var labelTheme = theme[index]; if (labelTheme === defaultThemeName) { label.addClass(defaultLabelStyle); } label.addClass(style[labelTheme]); }); }; /****************************************************** * A base grade for textfield stepper adjuster panels * ******************************************************/ fluid.defaults("fluid.prefs.panel.stepperAdjuster", { gradeNames: ["fluid.prefs.panel"], // preferences maps should map model values to "model.value" // model: {value: ""} selectors: { header: ".flc-prefsEditor-header", textfieldStepperContainer: ".flc-prefsEditor-textfieldStepper", label: ".flc-prefsEditor-label", descr: ".flc-prefsEditor-descr" }, selectorsToIgnore: ["header", "textfieldStepperContainer"], components: { textfieldStepper: { type: "fluid.textfieldStepper", container: "{that}.dom.textfieldStepperContainer", createOnEvent: "afterRender", options: { model: { value: "{fluid.prefs.panel.stepperAdjuster}.model.value", range: { min: "{fluid.prefs.panel.stepperAdjuster}.options.range.min", max: "{fluid.prefs.panel.stepperAdjuster}.options.range.max" }, step: "{fluid.prefs.panel.stepperAdjuster}.options.step" }, scale: 1, strings: { increaseLabel: "{fluid.prefs.panel.stepperAdjuster}.msgLookup.increaseLabel", decreaseLabel: "{fluid.prefs.panel.stepperAdjuster}.msgLookup.decreaseLabel" }, attrs: { "aria-labelledby": "{fluid.prefs.panel.stepperAdjuster}.options.panelOptions.labelId" } } } }, protoTree: { label: { messagekey: "label", decorators: { attrs: {id: "{that}.options.panelOptions.labelId"} } }, descr: {messagekey: "description"} }, panelOptions: { labelIdTemplate: "%guid", labelId: { expander: { funcName: "fluid.prefs.panel.stepperAdjuster.setLabelID", args: ["{that}.options.panelOptions.labelIdTemplate"] } } } }); /** * @param {String} template - take string template with a token "%guid" to be replaced by the a unique ID. * @return {String} - the resolved templated with the injected unique ID. */ fluid.prefs.panel.stepperAdjuster.setLabelID = function (template) { return fluid.stringTemplate(template, { guid: fluid.allocateGuid() }); }; /******************************** * Preferences Editor Text Size * ********************************/ /** * A sub-component of fluid.prefs that renders the "text size" panel of the user preferences interface. */ fluid.defaults("fluid.prefs.panel.textSize", { gradeNames: ["fluid.prefs.panel.stepperAdjuster"], preferenceMap: { "fluid.prefs.textSize": { "model.value": "value", "range.min": "minimum", "range.max": "maximum", "step": "multipleOf" } }, panelOptions: { labelIdTemplate: "textSize-label-%guid" } }); /******************************** * Preferences Editor Text Font * ********************************/ /** * A sub-component of fluid.prefs that renders the "text font" panel of the user preferences interface. */ fluid.defaults("fluid.prefs.panel.textFont", { gradeNames: ["fluid.prefs.panel"], preferenceMap: { "fluid.prefs.textFont": { "model.value": "value", "controlValues.textFont": "enum", "stringArrayIndex.textFont": "enumLabels" } }, mergePolicy: { "controlValues.textFont": "replace", "stringArrayIndex.textFont": "replace" }, selectors: { header: ".flc-prefsEditor-text-font-header", textFont: ".flc-prefsEditor-text-font", label: ".flc-prefsEditor-text-font-label", textFontDescr: ".flc-prefsEditor-text-font-descr" }, selectorsToIgnore: ["header"], protoTree: { label: {messagekey: "textFontLabel"}, textFontDescr: {messagekey: "textFontDescr"}, textFont: { optionnames: "${{that}.msgLookup.textFont}", optionlist: "${{that}.options.controlValues.textFont}", selection: "${value}", decorators: { type: "fluid", func: "fluid.prefs.selectDecorator", options: { styles: "{that}.options.classnameMap.textFont" } } } }, classnameMap: null // must be supplied by implementors }); /********************************* * Preferences Editor Line Space * *********************************/ /** * A sub-component of fluid.prefs that renders the "line space" panel of the user preferences interface. */ fluid.defaults("fluid.prefs.panel.lineSpace", { gradeNames: ["fluid.prefs.panel.stepperAdjuster"], preferenceMap: { "fluid.prefs.lineSpace": { "model.value": "value", "range.min": "minimum", "range.max": "maximum", "step": "multipleOf" } }, panelOptions: { labelIdTemplate: "lineSpace-label-%guid" } }); /******************************* * Preferences Editor Contrast * *******************************/ /** * A sub-component of fluid.prefs that renders the "contrast" panel of the user preferences interface. */ fluid.defaults("fluid.prefs.panel.contrast", { gradeNames: ["fluid.prefs.panel.themePicker"], preferenceMap: { "fluid.prefs.contrast": { "model.value": "value", "controlValues.theme": "enum", "stringArrayIndex.theme": "enumLabels" } }, listeners: { "afterRender.style": "{that}.style" }, selectors: { header: ".flc-prefsEditor-contrast-header", themeRow: ".flc-prefsEditor-themeRow", themeLabel: ".flc-prefsEditor-theme-label", themeInput: ".flc-prefsEditor-themeInput", label: ".flc-prefsEditor-themePicker-label", contrastDescr: ".flc-prefsEditor-themePicker-descr" }, selectorsToIgnore: ["header"], styles: { defaultThemeLabel: "fl-prefsEditor-themePicker-defaultThemeLabel" } }); /************************************** * Preferences Editor Layout Controls * **************************************/ /** * A sub-component of fluid.prefs that renders the "layout and navigation" panel of the user preferences interface. */ fluid.defaults("fluid.prefs.panel.layoutControls", { gradeNames: ["fluid.prefs.panel.switchAdjuster"], preferenceMap: { "fluid.prefs.tableOfContents": { "model.value": "value" } } }); /************************************* * Preferences Editor Enhance Inputs * *************************************/ /** * A sub-component of fluid.prefs that renders the "enhance inputs" panel of the user preferences interface. */ fluid.defaults("fluid.prefs.panel.enhanceInputs", { gradeNames: ["fluid.prefs.panel.switchAdjuster"], preferenceMap: { "fluid.prefs.enhanceInputs": { "model.value": "value" } } }); /******************************************************** * Preferences Editor Select Dropdown Options Decorator * ********************************************************/ /** * A sub-component that decorates the options on the select dropdown list box with the css style */ fluid.defaults("fluid.prefs.selectDecorator", { gradeNames: ["fluid.viewComponent"], listeners: { "onCreate.decorateOptions": "fluid.prefs.selectDecorator.decorateOptions" }, styles: { preview: "fl-preview-theme" } }); fluid.prefs.selectDecorator.decorateOptions = function (that) { fluid.each($("option", that.container), function (option) { var styles = that.options.styles; $(option).addClass(styles.preview + " " + styles[fluid.value(option)]); }); };