/* eslint-env browser */
/**
* This is a registry of `HTMLImageIcon` objects.
*
* Hypergrid comes with a few images (see below).
*
* Application developer is free to register additional image objects here (see {@link module:images.add|add}).
* @module images
*/
'use strict';
var _ = require('object-iterators');
var svgThemer = require('svg-themer');
var images = require('./images'); // this is the file generated by gulpfile.js (and ignored by git)
/**
*
* @name calendar
* @memberOf module:images
*/
/**
*
* @name checked
* @memberOf module:images
*/
/**
*
* @name unchecked
* @memberOf module:images
*/
/**
*
* @name filter-off
* @memberOf module:images
*/
/**
*
* @name filter-on
* @memberOf module:images
*/
/**
*
* @name up-down
* @memberOf module:images
*/
_(images).each(function(image, key) {
var element = new Image();
element.src = 'data:' + image.type + ';base64,' + image.data;
images[key] = element;
});
/**
* Synonym of {@link module:images.checked|checked} (unaffected if `checked` overridden).
* @name checkbox-on
* @memberOf module:images
*/
images['checkbox-on'] = images.checked;
/**
* Synonym of {@link module:images.unchecked|unchecked} (unaffected if `unchecked` overridden).
* @name checkbox-off
* @memberOf module:images
*/
images['checkbox-off'] = images.unchecked;
/**
* @method
* @param {string} name
* @param {HTMLImageElement} img
* @param {boolean} [themeable] - If truthy, the image will be themed by {@link module:images.setTheme images.setTheme}, called by {@link Hypergrid.applyTheme}.
* If falsy, the image won't be themed until `images[name].themeable` is set to `true`.
* In any case the remaining parameters are processed.
* @param {function} [setSvgProps=svgThemer.setSvgProps] - Optional custom theming code for this image and the rules implied by `styles`. _If omitted, `styles` is promoted 2nd parameter position._
* @param {boolean|string[]} [styles] - Optional list style names with which to create CSS rules.
* * If falsy (or omitted), no rules are created.
* * Else if truthy but not an array, create a single rule:
* ```css
* `.hypergrid-background-image-name { background-image: url(...) }`
* where _name_ is the value of the `name` parameter.
* * Else if an array, create a CSS rule for each style named therein.
*
* For each rule thus created:
* * Inserted into `style#injected-stylesheet-grid`.
* * Selector is `.hypergrid-style-name` (where `style` is element value and `name` is image name).
* (If a rule with that selector already exists, it is replaced.)
* * Contains the named style with a value of `url(...)` where `...` is the image data.
* Possible styles must be one of those listed in {*link https://github.com/joneit/svg-themer/blob/master/README.md#cssimagepropertynames svgThemer.cssImagePropertyNames} (which you can extend if needed).
* * Will be automatically themed when the grid is themed (which is the whole point).
*
* @see {@link https://github.com/joneit/svg-themer}
* @memberOf module:images
*/
function add(name, img, themeable, setSvgProps, styles) {
if (/^data:image\/svg\+xml|\.svg/.test(img.src)) {
img.themeable = !!themeable;
if (typeof setSvgProps === 'object') {
styles = setSvgProps;
setSvgProps = undefined;
}
if (setSvgProps) {
img.setSvgProps = setSvgProps;
}
if (styles) {
img.themeableRules = createThemeableRules(name, img, setSvgProps, styles);
}
}
return (images[name] = img);
}
function createThemeableRules(key, img, setSvgProps, styles) {
// find or create stylesheet as needed
var styleEl = document.querySelector('style#injected-stylesheet-themeables');
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = 'injected-stylesheet-themeables';
document.head.appendChild(styleEl);
}
var sheet = styleEl.sheet;
return (styles.length ? styles : ['background-image']).reduce(function(rules, styleName) {
var selectorText = '.hypergrid-' + styleName + '-' + key;
// find and delete existing rule, if any
var ruleIndex = Array.prototype.findIndex.call(sheet.cssRules, function(rule) {
return rule.selectorText === selectorText;
});
if (ruleIndex !== -1) {
sheet.deleteRule(ruleIndex);
}
// create and insert new rule consisting of selector + style "collection"
var ruleStyles = {};
// add image data style
ruleStyles[styleName] = 'url(' + img.src + ')';
// add dimensions if known
if (img.width) { ruleStyles.width = img.width + 'px'; }
if (img.height) { ruleStyles.height = img.height + 'px'; }
// combine the above styles into a semi-colon-separated "collection"
var styleCollection = Object.keys(ruleStyles).map(function(key) {
return key + ':' + ruleStyles[key];
}).join(';');
var ruleText = '{' + styleCollection + '}';
sheet.insertRule(selectorText + ruleText);
var themeableRule = {
rule: sheet.cssRules[0]
};
if (setSvgProps) {
themeableRule.setSvgProps = setSvgProps;
}
rules.push(themeableRule);
return rules;
}, []);
}
/**
* @param {object} theme
* @memberOf module:images
*/
function setTheme(theme) {
Object.keys(images).forEach(function(name) {
var img = images[name];
if (img.themeable) {
svgThemer.setImgSvgProps.call(img, theme, img.setSvgProps);
}
if (img.themeableRules) {
img.themeableRules.forEach(function(themeable) {
var selectorText = themeable.rule.selectorText;
// extract style name using list of possible names
var regex = new RegExp('^\.hypergrid-(' + svgThemer.cssImagePropertyNames.join('|') + ')-.*$');
var styleName = selectorText.replace(regex, '$1');
svgThemer.setRuleSvgProps.call(themeable.rule, theme, img.setSvgProps, styleName);
});
}
});
}
/**
* Convenience function.
* @param {boolean} state
* @returns {HTMLImageElement} {@link module:images.checked|checked} when `state` is truthy or {@link module:images.unchecked|unchecked} otherwise.
* @memberOf module:images
*/
function checkbox(state) {
return images[state ? 'checked' : 'unchecked'];
}
/**
* Convenience function.
* @param {boolean} state
* @returns {HTMLImageElement} {@link module:images.filter-off|filter-off} when `state` is truthy or {@link module:images.filter-on|filter-on} otherwise.
* @memberOf module:images
*/
function filter(state) {
return images[state ? 'filter-on' : 'filter-off'];
}
// add methods as non-enumerable members so member images can be enumerated
Object.defineProperties(images, {
add: { value: add },
setTheme: { value: setTheme },
checkbox: { value: checkbox },
filter: { value: filter }
});
module.exports = images;