UNPKG

fin-hypergrid

Version:
297 lines (260 loc) 10.3 kB
'use strict'; // This file creates the Hypergrid theme registry, exposed via: // shared methods `Hypergrid.registerTheme` and `Hypergrid.applyTheme` // and instance methods `myGrid.applyTheme`. // The initial registry consists of a single theme ('default'). // Application developers can add additional themes to this registry. var _ = require('object-iterators'); // fyi: installs the Array.prototype.find polyfill, as needed var defaults = require('./defaults'); var dynamicPropertyDescriptors = require('./lib/dynamicProperties'); var HypergridError = require('./lib/error'); var styles = [ 'BackgroundColor', 'Color', 'Font' ]; var stylesWithHalign = styles.concat([ 'Halign' ]); var dataCellStyles = stylesWithHalign.concat([ 'cellPadding', 'iconPadding' ]); var stylers = [ { prefix: '', props: dataCellStyles }, { prefix: 'foregroundSelection', props: styles }, { prefix: 'columnHeader', props: stylesWithHalign }, { prefix: 'columnHeaderForegroundSelection', props: styles }, { prefix: 'rowHeader', props: styles }, { prefix: 'rowHeaderForegroundSelection', props: styles } ]; var dynamicCosmetics = { rowHeaderCheckboxes: defaults.rowHeaderCheckboxes, rowHeaderNumbers: defaults.rowHeaderNumbers, gridBorder: defaults.gridBorder, gridBorderTop: defaults.gridBorderTop, gridBorderRight: defaults.gridBorderRight, gridBorderBottom: defaults.gridBorderBottom, gridBorderLeft: defaults.gridBorderLeft, gridRenderer: defaults.gridRenderer }; // Create the `defaultTheme` theme by copying over the theme props, // which is a subset of all the props defined in defaults.js, beginning with // they dynamic cosmetics and `themeName`... var defaultTheme = Object.assign({}, dynamicCosmetics, { themeName: defaults.themeName }); // ...and then adding non-dynamic cosmetics into `defaultTheme`, by combining the above // prefixes with their styles to get prop names and then copy those props from `defaults`. stylers.reduce(function(theme, styler) { return styler.props.reduce(function(theme, prop) { prop = styler.prefix + prop; prop = prop.replace('ForegroundSelectionBackground', 'BackgroundSelection'); // unfortunate! prop = prop[0].toLowerCase() + prop.substr(1); theme[prop] = defaults[prop]; return theme; }, theme); }, defaultTheme); /** * @summary The Hypergrid theme registry. * @desc The standard registry consists of a single theme, `default`, built from values in defaults.js. */ var registry = Object.create(null, { default: { value: defaultTheme } }); var pseudopropAdvice = { showRowNumbers: 'rowHeaderCheckboxes and rowHeaderNumbers', lineColor: 'gridLinesHColor and gridLinesVColor', lineWidth: 'gridLinesHWidth and gridLinesVWidth', gridBorder: 'gridBorderLeft, gridBorderRight, gridBorderTop, and gridBorderBottom' }; function applyTheme(theme) { var themeLayer, grids, props; if (theme && typeof theme === 'object' && !Object.getOwnPropertyNames(theme).length) { theme = null; } if (this._theme) { grids = [this]; themeLayer = this._theme; props = this.properties; // If removing theme, reset props to defaults if (!theme) { // Delete (non-dynamic) grid props named in this theme, revealing defaults Object.keys(themeLayer).forEach(function(key) { if (!(key in dynamicPropertyDescriptors)) { delete props[key]; } }); // Reset dynamic cosmetic props to defaults Object.keys(dynamicCosmetics).forEach(function(key) { props.var[key] = defaults[key]; }); } // Delete all own props from this grid instance's theme layer (defined by an eariler call) Object.keys(themeLayer).forEach(function(key) { delete themeLayer[key]; }); } else { grids = this.grids; themeLayer = defaults; // global theme layer theme = theme || 'default'; } if (typeof theme === 'string') { if (!registry[theme]) { throw new HypergridError('Unknown theme "' + theme + '"'); } theme = registry[theme]; } if (theme) { // When no theme name, set it to explicit `undefined` (to mask defaults.themeName). if (!theme.themeName) { theme.themeName = undefined; } Object.keys(theme).forEach(function(key) { if (!(key in dynamicPropertyDescriptors)) { grids.forEach(function(grid) { delete grid.properties[key]; }); } else if (key in dynamicCosmetics) { grids.forEach(function(grid) { grid.properties[key] = theme[key]; }); } else { // Dynamic properties are defined on properties layer; defining these // r-values on the theme layer is ineffective so let's not allow it. var message = pseudopropAdvice[key]; message = message ? 'Ignoring unexpected pseudo-prop ' + key + ' in theme object. Use actual props ' + message + ' instead.' : 'Ignoring invalid property ' + key + ' in theme object.'; console.warn(message); delete theme[key]; } }); // No .assign() because themeName is read-only in defaults layer Object.defineProperties(themeLayer, Object.getOwnPropertyDescriptors(theme)); } grids.forEach(function(grid) { grid.repaint(); }); } /** * Additions to `Hypergrid.prototype` for setting an instance theme. * @mixin */ var mixin = { initThemeLayer: function() { /** * Descends from {@link module:defaults|defaults}. * @memberOf Hypergrid# * @private */ this._theme = Object.create(defaults); return Object.create(this._theme, dynamicPropertyDescriptors); }, /** * @summary Apply a grid theme. * @desc Apply props from the given theme object to the grid instance, * the instance's `myGrid.themeLayer` layer in the properties hierarchy. * @this {Hypergrid} * @param {object|string} [theme] - One of: * * **string:** A registered theme name. * * **object:** A unregistered (anonymous) theme object. Empty object removes grid theme, exposing global theme. * * _falsy value:_ Also removes grid theme. * @param {string|undefined} [theme.themeName=undefined] * @memberOf Hypergrid# */ applyTheme: applyTheme, /** * @summary Get currently active theme. * @desc May return a theme name or a theme object. * @returns {string|undefined|object} One of: * * **string:** Theme name (registered theme). * * **object:** Theme object (unregistered anonymous theme). * * **undefined:** No theme (i.e., the default theme). * @memberOf Hypergrid# */ getTheme: function() { var themeLayer = this._theme, themeName = themeLayer.themeName; return themeName === 'default' || !Object.getOwnPropertyNames(themeLayer).length ? undefined // default theme or no theme : themeName in registry ? themeName // registered theme name : themeLayer; // unregistered theme object } }; Object.defineProperty(mixin, 'theme', { enumerable: true, set: mixin.applyTheme, get: mixin.getTheme }); /** * Shared properties of `Hypergrid` for registering themes and setting a global theme. * @mixin */ var sharedMixin = { /** * @param {string} [name] - A registry name for the new theme. May be omitted if the theme has an embedded name (in `theme.themeName`). * _If omitted, the 2nd parameter (`theme`) is promoted to first position._ * @param {HypergridThemeObject} [theme] * To build a Hypergrid theme object from a loaded {@link https://polymerthemes.com Polymer Theme} CSS stylesheet: * ```javascript * var myTheme = require('fin-hypergrid-themes').buildTheme(); * ``` * @this {Hypergrid.constructor} * @memberOf Hypergrid. */ registerTheme: function(name, theme) { if (arguments.length === 1) { theme = name; name = theme.themeName; } if (!name) { throw new HypergridError('Cannot register an anonymous theme.'); } if (name === 'default') { throw new HypergridError('Cannot register or unregister the "default" theme.'); } if (theme) { theme.themeName = name; registry[name] = theme; } else { delete registry[name]; } }, /** * App developers are free to add in additional themes, such as those in {@link https://openfin.github.com/fin-hypergrid-themes/themes}: * ```javascript * Hypergrind.registerThemes(require('fin-hypergrid-themes')); * ``` * @param {object} themeCollection * @memberOf Hypergrid. */ registerThemes: function(themeCollection) { _(themeCollection).each(function(theme, name) { this.registerTheme(name, theme); }, this); }, /** * @summary Apply global theme. * @desc Apply props from the given theme object to the global theme object, * the `defaults` layer at the bottom of the properties hierarchy. * @this {Hypergrid.constructor} * @param {object|string} [theme=registry.default] - One of: * * **string:** A registered theme name. * * **object:** A theme object. Empty object removes global them, restoring defaults. * * _falsy value:_ Also restores defaults. * @param {string|undefined} [theme.themeName=undefined] * @memberOf Hypergrid. */ applyTheme: applyTheme }; Object.defineProperty(sharedMixin, 'theme', { // global theme setter/getter enumerable: true, set: applyTheme, get: function() { return defaults; } // the defaults layer *is* the global theme layer }); module.exports = { mixin: mixin, sharedMixin: sharedMixin };