UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

499 lines (426 loc) 22.2 kB
/* Copyright 2013-2016 OCAD University 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_2_0_0 = fluid_2_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. * * @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 * * Example: * 1. Parameters: * source: * { * path1: { * path2: "here" * } * } * * template: "@path1.path2" * * 2. Return: "here" */ 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.expandSchemaComponents = function (auxSchema, type, prefKey, componentConfig, index, commonOptions, modelCommonOptions, mappedDefaults) { var componentOptions = fluid.copy(componentConfig) || {}; var components = {}; var initialModel = {}; var componentName = fluid.prefs.removeKey(componentOptions, "type"); var regexp = new RegExp("\\.", "g"); var memberName = componentName.replace(regexp, "_"); var flattenedPrefKey = prefKey.replace(regexp, "_"); 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) { 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[primaryPath]); } 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); } return auxSchema; }; /** * Expands a all "@" path references from an auxiliary schema. * Note that you cannot chain "@" paths. * * @param {object} schemaToExpand the shcema which will be expanded * @param {object} altSource an alternative look up object. This is primarily used for the internal recursive call. * @return {object} an expaneded 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 be always 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) { // 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[primaryPath]); } 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[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[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"] } }, 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_2_0_0);