infusion
Version:
Infusion is an application framework for developing flexible stuff with JavaScript
1,030 lines (948 loc) • 40.7 kB
JavaScript
/*
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
*/
;
/**********************
* 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)]);
});
};