UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature

123 lines (103 loc) 3.76 kB
// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010. // @author Mohit Cheppudira // // ## Description // // This file implements a registry for VexFlow elements. It allows users // to track, query, and manage some subset of generated elements, and // dynamically get and set attributes. // // There are two ways to regiser with a registry: // // 1) Explicitly call `element.register(registry)`, or, // 2) Call `Registry.enableDefaultRegistry(registry)` when ready, and all future // elements will automatically register with it. // // Once an element is registered, selected attributes are tracked and indexed by // the registry. This allows fast look up of elements by attributes like id, type, // and class. import { Vex } from './vex'; export const X = Vex.MakeException('RegistryError'); function setIndexValue(index, name, value, id, elem) { if (!index[name][value]) index[name][value] = {}; index[name][value][id] = elem; } export class Registry { static get INDEXES() { return ['type']; } constructor() { this.clear(); } // If you call `enableDefaultRegistry`, any new elements will auto-register with // the provided registry as soon as they're constructed. static enableDefaultRegistry(registry) { Registry.defaultRegistry = registry; } static getDefaultRegistry() { return Registry.defaultRegistry; } static disableDefaultRegistry() { Registry.defaultRegistry = null; } clear() { // Indexes are represented as maps of maps (of maps). This allows // for both multi-labeling (e.g., an element can have multiple classes) // and efficient lookup. this.index = { id: {}, type: {}, class: {}, }; return this; } // Updates the indexes for element 'id'. If an element's attribute changes // from A -> B, make sure to remove the element from A. updateIndex({ id, name, value, oldValue }) { const elem = this.getElementById(id); if (oldValue !== null && this.index[name][oldValue]) { delete this.index[name][oldValue][id]; } if (value !== null) { setIndexValue(this.index, name, value, elem.getAttribute('id'), elem); } } // Register element `elem` with this registry. This adds the element to its index and watches // it for attribute changes. register(elem, id) { id = id || elem.getAttribute('id'); if (!id) { throw new X('Can\'t add element without `id` attribute to registry', elem); } // Manually add id to index, then update other indexes. elem.setAttribute('id', id); setIndexValue(this.index, 'id', id, id, elem); Registry.INDEXES.forEach(name => { this.updateIndex({ id, name, value: elem.getAttribute(name), oldValue: null }); }); elem.onRegister(this); return this; } getElementById(id) { return this.index.id[id] ? this.index.id[id][id] : null; } getElementsByAttribute(attrName, value) { const index = this.index[attrName]; if (index && index[value]) { return Object.keys(index[value]).map(i => index[value][i]); } else { return []; } } getElementsByType(type) { return this.getElementsByAttribute('type', type); } getElementsByClass(className) { return this.getElementsByAttribute('class', className); } // This is called by the element when an attribute value changes. If an indexed // attribute changes, then update the local index. onUpdate({ id, name, value, oldValue }) { function includes(array, value) { return array.filter(x => x === value).length > 0; } if (!includes(Registry.INDEXES.concat(['id', 'class']), name)) return this; this.updateIndex({ id, name, value, oldValue }); return this; } } Registry.defaultRegistry = null;