UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

243 lines (214 loc) 12.1 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 */ /* global process */ "use strict"; fluid.registerNamespace("fluid.contextAware"); fluid.defaults("fluid.contextAware.marker", { gradeNames: ["fluid.component"] }); // unsupported, NON-API function fluid.contextAware.makeCheckMarkers = function (checks, path, instantiator) { fluid.each(checks, function (value, markerTypeName) { fluid.constructSingle(path, { type: markerTypeName, gradeNames: "fluid.contextAware.marker", value: value }, instantiator); }); }; /** * Peforms the computation for `fluid.contextAware.makeChecks` and returns a structure suitable for being sent to `fluid.contextAware.makeCheckMarkers` - * * @param {Object} checkHash - a hash of context awareness checks to compute * @return {Object} - A hash of marker type names to grade names - this can be sent to fluid.contextAware.makeCheckMarkers */ // unsupported, NON-API function fluid.contextAware.performChecks = function (checkHash) { return fluid.transform(checkHash, function (checkRecord) { if (typeof(checkRecord) === "function") { checkRecord = {func: checkRecord}; } else if (typeof(checkRecord) === "string") { checkRecord = {funcName: checkRecord}; } if (fluid.isPrimitive(checkRecord)) { return checkRecord; } else if ("value" in checkRecord) { return checkRecord.value; } else if ("func" in checkRecord) { return checkRecord.func(); } else if ("funcName" in checkRecord) { return fluid.invokeGlobalFunction(checkRecord.funcName); } else { fluid.fail("Error in contextAwareness check record ", checkRecord, " - must contain an entry with name value, func, or funcName"); } }); }; /** * Takes an object whose keys are check context names and whose values are check records, designating a collection of context markers which might be registered at a location * in the component tree. * * @param {Object} checkHash - The keys in this structure are the context names to be supplied if the check passes, and the values are check records. * A check record contains: * ONE OF: * value {Any} [optional] A literal value name to be attached to the context * func {Function} [optional] A zero-arg function to be called to compute the value * funcName {String} [optional] The name of a zero-arg global function which will compute the value * If the check record consists of a Number or Boolean, it is assumed to be the value given to "value". * @param {String|Array} [path] - [optional] The path in the component tree at which the check markers are to be registered. If omitted, "" is assumed * @param {Instantiator} [instantiator] - [optional] The instantiator holding the component tree which will receive the markers. If omitted, use `fluid.globalInstantiator`. */ fluid.contextAware.makeChecks = function (checkHash, path, instantiator) { var checkOptions = fluid.contextAware.performChecks(checkHash); fluid.contextAware.makeCheckMarkers(checkOptions, path, instantiator); }; /** * Forgets a check made at a particular level of the component tree. * * @param {String[]} markerNames - The marker typeNames whose check values are to be forgotten. * @param {String|String[]} [path] - [optional] The path in the component tree at which the check markers are to be removed. If omitted, "" is assumed * @param {Instantiator} [instantiator] - [optional] The instantiator holding the component tree the markers are to be removed from. If omitted, use `fluid.globalInstantiator`. */ fluid.contextAware.forgetChecks = function (markerNames, path, instantiator) { instantiator = instantiator || fluid.globalInstantiator; path = path || []; var markerArray = fluid.makeArray(markerNames); fluid.each(markerArray, function (markerName) { var memberName = fluid.typeNameToMemberName(markerName); var segs = fluid.model.parseToSegments(path, instantiator.parseEL, true); segs.push(memberName); fluid.destroy(segs, instantiator); }); }; /** A grade to be given to a component which requires context-aware adaptation. * This grade consumes configuration held in the block named "contextAwareness", which is an object whose keys are check namespaces and whose values hold * sequences of "checks" to be made in the component tree above the component. The value searched by * each check is encoded as the element named `contextValue` - this either represents an IoC reference to a component * or a particular value held at the component. If this reference has no path component, the path ".options.value" will be assumed. * These checks seek contexts which * have been previously registered using fluid.contextAware.makeChecks. The first context which matches * with a value of `true` terminates the search, and returns by applying the grade names held in `gradeNames` to the current component. * If no check matches, the grades held in `defaultGradeNames` will be applied. */ fluid.defaults("fluid.contextAware", { mergePolicy: { contextAwareness: "noexpand" }, contextAwareness: { // Hash of names (check namespaces) to records: { // checks: {}, // Hash of check namespace to: { // contextValue: IoCExpression testing value in environment, // gradeNames: gradeNames which will be output, // priority: String/Number for priority of check [optional] // equals: Value to be compared to contextValue [optional - default is `true`] // defaultGradeNames: // String or String[] holding default gradeNames which will be output if no check matches [optional] // priority: // Number or String encoding priority relative to other records (same format as with event listeners) [optional] // } } }); fluid.contextAware.getCheckValue = function (that, reference) { // cf. core of distributeOptions! var targetRef = fluid.parseContextReference(reference); var targetComponent = fluid.resolveContext(targetRef.context, that); var path = targetRef.path || (fluid.isComponent(targetComponent) && fluid.componentHasGrade(targetComponent, "fluid.contextAware.marker") ? ["options", "value"] : []); var value = fluid.getForComponent(targetComponent, path); return value; }; // unsupported, NON-API function fluid.contextAware.checkOne = function (that, contextAwareRecord) { if (contextAwareRecord.checks && contextAwareRecord.checks.contextValue) { fluid.fail("Nesting error in contextAwareness record ", contextAwareRecord, " - the \"checks\" entry must contain a hash and not a contextValue/gradeNames record at top level"); } var checkList = fluid.parsePriorityRecords(contextAwareRecord.checks, "contextAwareness checkRecord"); return fluid.find(checkList, function (check) { if (!check.contextValue) { fluid.fail("Cannot perform check for contextAwareness record ", check, " without a valid field named \"contextValue\""); } var value = fluid.contextAware.getCheckValue(that, check.contextValue); if (check.equals === undefined ? value : value === check.equals) { return check.gradeNames; } }, contextAwareRecord.defaultGradeNames); }; // unsupported, NON-API function // Note that this function is now invoked directly from fluid.computeDynamicGrades /** The top-level engine for the contextAwareness algorithm. Accepts the context-aware component and its top-level `contextAwarenessOptions` block * and returns the array of grade names to be contributed into its `gradeNames` * @param {fluid.contextAware} that - The `contextAware` component * @param {Object} contextAwarenessOptions - The component's top-level `contextAwareness` options * @return {String[]} An array, possibly empty, of grade names to be contributed into the component */ fluid.contextAware.check = function (that, contextAwarenessOptions) { var gradeNames = []; var contextAwareList = fluid.parsePriorityRecords(contextAwarenessOptions, "contextAwareness adaptationRecord"); fluid.each(contextAwareList, function (record) { var matched = fluid.contextAware.checkOne(that, record); gradeNames = gradeNames.concat(fluid.makeArray(matched)); }); return gradeNames; }; /** Given a set of options, broadcast an adaptation to all instances of a particular component in a particular context. ("new demands blocks"). * This has the effect of fabricating a grade with a particular name with an options distribution to `{/ typeName}` for the required component, * and then constructing a single well-known instance of it. * Options layout: * distributionName {String} A grade name - the name to be given to the fabricated grade * targetName {String} A grade name - the name of the grade to receive the adaptation * adaptationName {String} the name of the contextAwareness record to receive the record - this will be a simple string * checkName {String} the name of the check within the contextAwareness record to receive the record - this will be a simple string * record {Object} the record to be broadcast into contextAwareness - should contain entries * contextValue {IoC expression} the context value to be checked to activate the adaptation * gradeNames {String/String[]} the grade names to be supplied to the adapting target (matching advisedName) * * @param {Object} options - The options to use when making an adaptation. See above for supported sub-options. */ fluid.contextAware.makeAdaptation = function (options) { fluid.expect("fluid.contextAware.makeAdaptation", options, ["distributionName", "targetName", "adaptationName", "checkName", "record"]); fluid.defaults(options.distributionName, { gradeNames: ["fluid.component"], distributeOptions: { target: "{/ " + options.targetName + "}.options.contextAwareness." + options.adaptationName + ".checks." + options.checkName, record: options.record } }); fluid.constructSingle([], options.distributionName); }; // Context awareness for the browser environment fluid.contextAware.isBrowser = function () { return typeof(window) !== "undefined" && !!window.document; }; // Context awareness for the node.js environment - taken from https://stackoverflow.com/a/35813135 fluid.contextAware.isNode = function () { return typeof(process) !== "undefined" && process.versions && process.versions.node; }; fluid.contextAware.makeChecks({ "fluid.browser": { funcName: "fluid.contextAware.isBrowser" }, "fluid.node": { funcName: "fluid.contextAware.isNode" } }); // Context awareness for the reported browser platform name (operating system) fluid.registerNamespace("fluid.contextAware.browser"); fluid.contextAware.browser.getPlatformName = function () { return typeof(navigator) !== "undefined" && navigator.platform ? navigator.platform : undefined; }; // Context awareness for the reported user agent name fluid.contextAware.browser.getUserAgent = function () { return typeof(navigator) !== "undefined" && navigator.userAgent ? navigator.userAgent : undefined; }; fluid.contextAware.makeChecks({ "fluid.browser.platformName": { funcName: "fluid.contextAware.browser.getPlatformName" }, "fluid.browser.userAgent": { funcName: "fluid.contextAware.browser.getUserAgent" } });