UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

534 lines (454 loc) 23.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/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.prefs"); /******************************************************************************* * Base auxiliary schema grade *******************************************************************************/ fluid.defaults("fluid.prefs.auxSchema", { gradeNames: ["fluid.component"], auxiliarySchema: { "loaderGrades": ["fluid.prefs.separatedPanel"] } }); /** * Look up the value on the given source object by using the path. * Takes a template string containing tokens in the form of "@source-path-to-value". * Returns a value (any type) or undefined if the path is not found. * * Example: * 1. Parameters: * source: * { * path1: { * path2: "here" * } * } * * template: "@path1.path2" * * 2. Return: "here" * * @param {Object} root - An object to retrieve the returned value from. * @param {String} pathRef - A string that the path to the requested value is embedded into. * @return {Any} - Returns a value (any type) or undefined if the path is not found. * */ fluid.prefs.expandSchemaValue = function (root, pathRef) { if (pathRef.charAt(0) !== "@") { return pathRef; } return fluid.get(root, pathRef.substring(1)); }; fluid.prefs.addAtPath = function (root, path, object) { var existingObject = fluid.get(root, path); fluid.set(root, path, $.extend(true, {}, existingObject, object)); return root; }; // only works with top level elements fluid.prefs.removeKey = function (root, key) { var value = root[key]; delete root[key]; return value; }; fluid.prefs.rearrangeDirect = function (root, toPath, sourcePath) { var result = {}; var sourceValue = fluid.prefs.removeKey(root, sourcePath); if (sourceValue) { fluid.set(result, toPath, sourceValue); } return result; }; fluid.prefs.addCommonOptions = function (root, path, commonOptions, templateValues) { templateValues = templateValues || {}; var existingValue = fluid.get(root, path); if (!existingValue) { return root; } var opts = {}, mergePolicy = {}; fluid.each(commonOptions, function (value, key) { // Adds "container" option only for view and renderer components if (key === "container") { var componentType = fluid.get(root, [path, "type"]); var componentOptions = fluid.defaults(componentType); // Note that this approach is not completely reliable, although it has been reviewed as "good enough" - // a grade which modifies the creation signature of its principal type would cause numerous other problems. // We can review this awkward kind of "anticipatory logic" when the new renderer arrives. if (fluid.get(componentOptions, ["argumentMap", "container"]) === undefined) { return false; } } // Merge grade names defined in aux schema and system default grades if (key.indexOf("gradeNames") !== -1) { mergePolicy[key] = fluid.arrayConcatPolicy; } key = fluid.stringTemplate(key, templateValues); value = typeof (value) === "string" ? fluid.stringTemplate(value, templateValues) : value; fluid.set(opts, key, value); }); fluid.set(root, path, fluid.merge(mergePolicy, existingValue, opts)); return root; }; fluid.prefs.containerNeeded = function (root, path) { var componentType = fluid.get(root, [path, "type"]); var componentOptions = fluid.defaults(componentType); return (fluid.hasGrade(componentOptions, "fluid.viewComponent") || fluid.hasGrade(componentOptions, "fluid.rendererComponent")); }; fluid.prefs.checkPrimarySchema = function (primarySchema, prefKey) { if (!primarySchema) { fluid.fail("The primary schema for " + prefKey + " is not defined."); } return !!primarySchema; }; fluid.prefs.flattenName = function (name) { var regexp = new RegExp("\\.", "g"); return name.replace(regexp, "_"); }; fluid.prefs.constructAliases = function (auxSchema, flattenedPrefKey, aliases) { aliases = fluid.makeArray(aliases); var prefsEditorModel = {}; var enhancerModel = {}; fluid.each(aliases, function (alias) { prefsEditorModel[alias] = "{that}.model.preferences." + flattenedPrefKey; enhancerModel[alias] = "{that}.model." + flattenedPrefKey; }); fluid.prefs.addAtPath(auxSchema, ["aliases_prefsEditor", "model", "preferences"], prefsEditorModel); fluid.prefs.addAtPath(auxSchema, ["aliases_enhancer", "model"], enhancerModel); }; fluid.prefs.expandSchemaComponents = function (auxSchema, type, prefKey, alias, componentConfig, index, commonOptions, modelCommonOptions, mappedDefaults) { var componentOptions = fluid.copy(componentConfig) || {}; var components = {}; var initialModel = {}; var componentName = fluid.prefs.removeKey(componentOptions, "type"); var memberName = fluid.prefs.flattenName(componentName); var flattenedPrefKey = fluid.prefs.flattenName(prefKey); if (componentName) { components[memberName] = { type: componentName, options: componentOptions }; var selectors = fluid.prefs.rearrangeDirect(componentOptions, memberName, "container"); var templates = fluid.prefs.rearrangeDirect(componentOptions, memberName, "template"); var messages = fluid.prefs.rearrangeDirect(componentOptions, memberName, "message"); var preferenceMap = fluid.defaults(componentName).preferenceMap; var map = preferenceMap[prefKey]; var prefSchema = mappedDefaults[prefKey]; fluid.each(map, function (primaryPath, internalPath) { if (fluid.prefs.checkPrimarySchema(prefSchema, prefKey)) { var opts = {}; if (internalPath.indexOf("model.") === 0 && primaryPath === "value") { var internalModelName = internalPath.slice(6); // Set up the binding in "rules" accepted by the modelRelay base grade of every panel fluid.set(opts, "model", fluid.get(opts, "model") || {}); fluid.prefs.addCommonOptions(opts, "model", modelCommonOptions, { internalModelName: internalModelName, externalModelName: flattenedPrefKey }); fluid.set(initialModel, ["members", "initialModel", "preferences", flattenedPrefKey], prefSchema["default"]); if (alias) { fluid.set(initialModel, ["members", "initialModel", "preferences", alias], prefSchema["default"]); } } else { fluid.set(opts, internalPath, prefSchema[primaryPath]); } $.extend(true, componentOptions, opts); } }); fluid.prefs.addCommonOptions(components, memberName, commonOptions, { prefKey: memberName }); fluid.prefs.addAtPath(auxSchema, [type, "components"], components); fluid.prefs.addAtPath(auxSchema, [type, "selectors"], selectors); fluid.prefs.addAtPath(auxSchema, ["templateLoader", "resources"], templates); fluid.prefs.addAtPath(auxSchema, ["messageLoader", "resources"], messages); fluid.prefs.addAtPath(auxSchema, "initialModel", initialModel); fluid.prefs.constructAliases(auxSchema, flattenedPrefKey, alias); } return auxSchema; }; /** * Expands a all "@" path references from an auxiliary schema. * Note that you cannot chain "@" paths. * * @param {Object} schemaToExpand - the schema which will be expanded * @param {Object} altSource - an alternative look up object. This is primarily used for the internal recursive call. * @return {Object} an expanded version of the schema. */ fluid.prefs.expandSchemaImpl = function (schemaToExpand, altSource) { var expandedSchema = fluid.copy(schemaToExpand); altSource = altSource || expandedSchema; fluid.each(expandedSchema, function (value, key) { if (typeof value === "object") { expandedSchema[key] = fluid.prefs.expandSchemaImpl(value, altSource); } else if (typeof value === "string") { var expandedVal = fluid.prefs.expandSchemaValue(altSource, value); if (expandedVal !== undefined) { expandedSchema[key] = expandedVal; } else { delete expandedSchema[key]; } } }); return expandedSchema; }; fluid.prefs.expandCompositePanels = function (auxSchema, compositePanelList, panelIndex, panelCommonOptions, subPanelCommonOptions, compositePanelBasedOnSubCommonOptions, panelModelCommonOptions, mappedDefaults) { var panelsToIgnore = []; fluid.each(compositePanelList, function (compositeDetail, compositeKey) { var compositePanelOptions = {}; var components = {}; var initialModel = {}; var selectors = {}; var templates = {}; var messages = {}; var selectorsToIgnore = []; var thisCompositeOptions = fluid.copy(compositeDetail); fluid.set(compositePanelOptions, "type", thisCompositeOptions.type); delete thisCompositeOptions.type; selectors = fluid.prefs.rearrangeDirect(thisCompositeOptions, compositeKey, "container"); templates = fluid.prefs.rearrangeDirect(thisCompositeOptions, compositeKey, "template"); messages = fluid.prefs.rearrangeDirect(thisCompositeOptions, compositeKey, "message"); var subPanelList = []; // list of subpanels to generate options for var subPanels = {}; var subPanelRenderOn = {}; // thisCompositeOptions.panels can be in two forms: // 1. an array of names of panels that should always be rendered; // 2. an object that describes what panels should always be rendered, // and what panels should be rendered when a preference is turned on // The loop below is only needed for processing the latter. if (fluid.isPlainObject(thisCompositeOptions.panels) && !fluid.isArrayable(thisCompositeOptions.panels)) { fluid.each(thisCompositeOptions.panels, function (subpanelArray, pref) { subPanelList = subPanelList.concat(subpanelArray); if (pref !== "always") { fluid.each(subpanelArray, function (onePanel) { fluid.set(subPanelRenderOn, onePanel, pref); }); } }); } else { subPanelList = thisCompositeOptions.panels; } fluid.each(subPanelList, function (subPanelID) { panelsToIgnore.push(subPanelID); var subPanelPrefsKey = fluid.get(auxSchema, [subPanelID, "type"]); var safeSubPanelPrefsKey = fluid.prefs.subPanel.safePrefKey(subPanelPrefsKey); selectorsToIgnore.push(safeSubPanelPrefsKey); var subPanelOptions = fluid.copy(fluid.get(auxSchema, [subPanelID, "panel"])); var subPanelType = fluid.get(subPanelOptions, "type"); fluid.set(subPanels, [safeSubPanelPrefsKey, "type"], subPanelType); var renderOn = fluid.get(subPanelRenderOn, subPanelID); if (renderOn) { fluid.set(subPanels, [safeSubPanelPrefsKey, "options", "renderOnPreference"], renderOn); } // Deal with preferenceMap related options var map = fluid.defaults(subPanelType).preferenceMap[subPanelPrefsKey]; var prefSchema = mappedDefaults[subPanelPrefsKey]; fluid.each(map, function (primaryPath, internalPath) { if (fluid.prefs.checkPrimarySchema(prefSchema, subPanelPrefsKey)) { var opts; if (internalPath.indexOf("model.") === 0 && primaryPath === "value") { // Set up the binding in "rules" accepted by the modelRelay base grade of every panel fluid.set(compositePanelOptions, ["options", "model"], fluid.get(compositePanelOptions, ["options", "model"]) || {}); fluid.prefs.addCommonOptions(compositePanelOptions, ["options", "model"], panelModelCommonOptions, { internalModelName: safeSubPanelPrefsKey, externalModelName: safeSubPanelPrefsKey }); fluid.set(initialModel, ["members", "initialModel", "preferences", safeSubPanelPrefsKey], prefSchema["default"]); } else { opts = opts || {options: {}}; fluid.set(opts, "options." + internalPath, prefSchema[primaryPath]); } $.extend(true, subPanels[safeSubPanelPrefsKey], opts); } }); fluid.set(templates, safeSubPanelPrefsKey, fluid.get(subPanelOptions, "template")); fluid.set(messages, safeSubPanelPrefsKey, fluid.get(subPanelOptions, "message")); fluid.set(compositePanelOptions, ["options", "selectors", safeSubPanelPrefsKey], fluid.get(subPanelOptions, "container")); fluid.set(compositePanelOptions, ["options", "resources"], fluid.get(compositePanelOptions, ["options", "resources"]) || {}); fluid.prefs.addCommonOptions(compositePanelOptions.options, "resources", compositePanelBasedOnSubCommonOptions, { subPrefKey: safeSubPanelPrefsKey }); // add additional options from the aux schema for subpanels delete subPanelOptions.type; delete subPanelOptions.template; delete subPanelOptions.message; delete subPanelOptions.container; fluid.set(subPanels, [safeSubPanelPrefsKey, "options"], $.extend(true, {}, fluid.get(subPanels, [safeSubPanelPrefsKey, "options"]), subPanelOptions)); fluid.prefs.addCommonOptions(subPanels, safeSubPanelPrefsKey, subPanelCommonOptions, { compositePanel: compositeKey, prefKey: safeSubPanelPrefsKey }); }); delete thisCompositeOptions.panels; // add additional options from the aux schema for the composite panel fluid.set(compositePanelOptions, ["options"], $.extend(true, {}, compositePanelOptions.options, thisCompositeOptions)); fluid.set(compositePanelOptions, ["options", "selectorsToIgnore"], selectorsToIgnore); fluid.set(compositePanelOptions, ["options", "components"], subPanels); components[compositeKey] = compositePanelOptions; fluid.prefs.addCommonOptions(components, compositeKey, panelCommonOptions, { prefKey: compositeKey }); // Add onto auxSchema fluid.prefs.addAtPath(auxSchema, ["panels", "components"], components); fluid.prefs.addAtPath(auxSchema, ["panels", "selectors"], selectors); fluid.prefs.addAtPath(auxSchema, ["templateLoader", "resources"], templates); fluid.prefs.addAtPath(auxSchema, ["messageLoader", "resources"], messages); fluid.prefs.addAtPath(auxSchema, "initialModel", initialModel); $.extend(true, auxSchema, {panelsToIgnore: panelsToIgnore}); }); return auxSchema; }; // Processes the auxiliary schema to output an object that contains all grade component definitions // required for building the preferences editor, uiEnhancer and the settings store. These grade components // are: panels, enactors, initialModel, messageLoader, templateLoader and terms. // These grades are consumed and integrated by builder.js // (https://github.com/fluid-project/infusion/blob/master/src/framework/preferences/js/Builder.js) fluid.prefs.expandSchema = function (schemaToExpand, indexes, topCommonOptions, elementCommonOptions, mappedDefaults) { var auxSchema = fluid.prefs.expandSchemaImpl(schemaToExpand); auxSchema.namespace = auxSchema.namespace || "fluid.prefs.created_" + fluid.allocateGuid(); var terms = fluid.get(auxSchema, "terms"); if (terms) { delete auxSchema.terms; fluid.set(auxSchema, ["terms", "terms"], terms); } var compositePanelList = fluid.get(auxSchema, "groups"); if (compositePanelList) { fluid.prefs.expandCompositePanels(auxSchema, compositePanelList, fluid.get(indexes, "panel"), fluid.get(elementCommonOptions, "panel"), fluid.get(elementCommonOptions, "subPanel"), fluid.get(elementCommonOptions, "compositePanelBasedOnSub"), fluid.get(elementCommonOptions, "panelModel"), mappedDefaults); } fluid.each(auxSchema, function (category, prefName) { // TODO: Replace this cumbersome scheme with one based on an extensible lookup to handlers var type = "panel"; // Ignore the subpanels that are only for composing composite panels if (category[type] && !fluid.contains(auxSchema.panelsToIgnore, prefName)) { fluid.prefs.expandSchemaComponents(auxSchema, "panels", category.type, category.alias, category[type], fluid.get(indexes, type), fluid.get(elementCommonOptions, type), fluid.get(elementCommonOptions, type + "Model"), mappedDefaults); } type = "enactor"; if (category[type]) { fluid.prefs.expandSchemaComponents(auxSchema, "enactors", category.type, category.alias, category[type], fluid.get(indexes, type), fluid.get(elementCommonOptions, type), fluid.get(elementCommonOptions, type + "Model"), mappedDefaults); } fluid.each(["template", "message"], function (type) { if (prefName === type) { fluid.set(auxSchema, [type + "Loader", "resources", "prefsEditor"], auxSchema[type]); delete auxSchema[type]; } }); }); // Remove subPanels array. It is to keep track of the panels that are only used as sub-components of composite panels. if (auxSchema.panelsToIgnore) { delete auxSchema.panelsToIgnore; } // Add top common options fluid.each(topCommonOptions, function (topOptions, type) { fluid.prefs.addCommonOptions(auxSchema, type, topOptions); }); return auxSchema; }; fluid.defaults("fluid.prefs.auxBuilder", { gradeNames: ["fluid.prefs.auxSchema"], mergePolicy: { elementCommonOptions: "noexpand" }, topCommonOptions: { panels: { gradeNames: ["fluid.prefs.prefsEditor"] }, enactors: { gradeNames: ["fluid.uiEnhancer"] }, templateLoader: { gradeNames: ["fluid.resourceLoader"] }, messageLoader: { gradeNames: ["fluid.resourceLoader"] }, initialModel: { gradeNames: ["fluid.prefs.initialModel"] }, terms: { gradeNames: ["fluid.component"] }, aliases_prefsEditor: { gradeNames: ["fluid.modelComponent"] }, aliases_enhancer: { gradeNames: ["fluid.modelComponent"] } }, elementCommonOptions: { panel: { "createOnEvent": "onPrefsEditorMarkupReady", "container": "{prefsEditor}.dom.%prefKey", "options.gradeNames": "fluid.prefs.prefsEditorConnections", "options.resources.template": "{templateLoader}.resources.%prefKey", "options.messageBase": "{messageLoader}.resources.%prefKey.resourceText" }, panelModel: { "%internalModelName": "{prefsEditor}.model.preferences.%externalModelName" }, compositePanelBasedOnSub: { "%subPrefKey": "{templateLoader}.resources.%subPrefKey" }, subPanel: { "container": "{%compositePanel}.dom.%prefKey", "options.messageBase": "{messageLoader}.resources.%prefKey.resourceText" }, enactor: { "container": "{uiEnhancer}.container" }, enactorModel: { "%internalModelName": "{uiEnhancer}.model.%externalModelName" } }, indexes: { panel: { expander: { func: "fluid.indexDefaults", args: ["panelsIndex", { gradeNames: "fluid.prefs.panel", indexFunc: "fluid.prefs.auxBuilder.prefMapIndexer" }] } }, enactor: { expander: { func: "fluid.indexDefaults", args: ["enactorsIndex", { gradeNames: "fluid.prefs.enactor", indexFunc: "fluid.prefs.auxBuilder.prefMapIndexer" }] } } }, mappedDefaults: {}, expandedAuxSchema: { expander: { func: "fluid.prefs.expandSchema", args: [ "{that}.options.auxiliarySchema", "{that}.options.indexes", "{that}.options.topCommonOptions", "{that}.options.elementCommonOptions", "{that}.options.mappedDefaults" ] } } }); fluid.prefs.auxBuilder.prefMapIndexer = function (defaults) { return fluid.keys(defaults.preferenceMap); }; })(jQuery, fluid_3_0_0);