UNPKG

@teipublisher/pb-components

Version:
281 lines (263 loc) 10.4 kB
import { LitElement, html } from 'lit-element'; import '@polymer/paper-checkbox'; import { pbMixin, waitOnce } from './pb-mixin.js'; import { registry } from "./urls.js"; /** * @param {{ selector: any; command: string; state: boolean; }} newConfig * @param {any[]} configs */ export function addSelector(newConfig, configs) { const idx = configs.findIndex((item) => item.selector === newConfig.selector); if (idx > -1) { configs[idx] = newConfig; } else { configs.push(newConfig); } } /** * Enable or disable a particular display feature by setting a predefined or custom parameter. * Toggling display features can be done server-side or client-side. * * It is important that `pb-toggle-feature` emits and subscribes to the same channel as the target `pb-view`. * * # Server side toggling * * You may set the following view parameters which correspond to the properties supported by `pb-view`: * * | Parameter | Description | * | ----------------|-------------| * | odd | the ODD to use | * | view | the view type: 'page', 'div' or 'single' | * | columnSeparator | CSS selector to find elements to use as column separator | * | xpath | XPath expression to select a portion of the text for display | * * For example, one may switch between page-by-page and by-division view using * * ```html * <pb-toggle-feature emit="transcription" name="view" on="page" off="div">Page View</pb-toggle-feature> * ``` * * It is also possible to set custom parameters, which will be passed to the ODD as user-defined parameters. * This can be used e.g. to implement different views on the text, e.g. a 'diplomatic' and a 'normalized' mode. Both * views would be backed by the same ODD and processing model, while the passed in parameter * can be used to distinguish the modes. * * For example, the following snippet would result in a custom parameter called `mode` with * either a value of `dipl` or `norm`. Within processing model predicates it could be queried as * `$parameters?mode='norm'` to check if the normalized version should be output. * * ```html * <pb-toggle-feature name="mode" on="diplomatic" off="norm">Diplomatic View</pb-toggle-feature> * ``` * * Besides setting a single parameter, you may also set multiple * properties on the target `pb-view` via the `properties-on` and `properties-off` * attributes (as an alternative to `on` and `off`). For example, in 'on' state, you may * want to use a different ODD and switch the view to 'page' at the same time: * * ```html * <pb-toggle-feature properties-on='{"odd": "myodd", "view": "page"}' properties-off='{"odd": "myodd-diplomatic", "view": "div"}'> * Diplomatic View * </pb-toggle-feature> * ``` * * # Client side toggling * * The component can also be used to toggle features client-side, i.e. without requiring a server-roundtrip. * To enable this, the `selector` property should be set to a CSS3 selector targetting the HTML elements * to toggle. In `on` state, the selected elements will be assigned a class `.toggled`. * * ```html * <pb-toggle-feature name="normalized" selector=".choice,.choice-alternate,br">Normalized View</pb-toggle-feature> * ``` * * Note that the name attribute is still required: it is used to determine if the feature is in on or off state by * looking at request parameters. * * Instead of toggling the class, you can also completely disable the elements selected - provided that they are * publisher components implementing the corresponding `command` method of `pb-mixin`. To disable elements instead of * toggling, set the `action` property to *disable*. * * ```html * <pb-toggle-feature name="plain" selector=".tei-foreign,pb-highlight,pb-popover" action="disable" default="off">Plain Reading View</pb-toggle-feature> * ``` * * @fires pb-toggle - Fired when the feature is toggled, it's changing properties between values of its `on` and `off` state * @fires pb-global-toggle - Fired if property `global` is set. Will be caught by the surrounding `pb-page` * @fires pb-update - When received, sets the features passed from the event */ export class PbToggleFeature extends pbMixin(LitElement) { static get properties() { return { ...super.properties, /** * The name of the feature (required). This will correspond to the name of the custom parameter * passed to the ODD. */ name: { type: String }, /** * (optional) CSS selector: selects the elements to toggle client side (sets or unsets a * CSS class `.toggled`). Setting this property will not trigger a reload as everything is * handled by javascript. */ selector: { type: String }, /** * In combination with a selector specifies the action to be taken, currently one of * `toggle` (default) or `disable`. */ action: { type: String }, /** * Value to set the parameter to when the feature is enabled. */ on: { type: String }, /** * Value to set the parameter to when the feature is disabled. */ off: { type: String }, /** * The default setting: either 'on' or 'off' */ default: { type: String }, /** * Additional properties to set on the pb-view if toggle is in 'on' state. * Possible properties are 'view', 'odd' and 'columnSeparator'. */ propertiesOn: { type: Object, attribute: 'properties-on' }, /** * Additional properties to set on the pb-view if toggle is in 'off' state. * Possible properties are 'view', 'odd' and 'columnSeparator'. */ propertiesOff: { type: Object, attribute: 'properties-off' }, checked: { type: Boolean }, /** * If set to false (the default), `pb-toggle-feature` will pass its properties to the * connected view before this loads content for the first time. If true, * `pb-toggle-feature` will initialize its state depending on the setting of the view. * This only makes sense for the special properties 'view' and 'odd' though. */ initFromView: { type: Boolean, attribute: 'init-from-view' }, /** * If set, toggle the state of elements which reside * in the surrounding HTML context below `pb-page` * (means: elements which are not inside a `pb-view` or `pb-load`). */ global: { type: Boolean } }; } constructor() { super(); this.default = 'on'; this.on = 'on'; this.off = 'off'; this.action = 'toggle'; this.propertiesOn = {}; this.propertiesOff = {}; this.initFromView = false; this.global = false; } render() { return html` <paper-checkbox id="checkbox" @change="${this._changed}" .checked="${this.checked}" .disabled="${this.disabled}"><slot></slot></paper-checkbox> `; } connectedCallback() { super.connectedCallback(); registry.subscribe(this, (state) => { const param = state[this.name]; this._setChecked(param); }); const param = registry.state[this.name]; this._setChecked(param); const newState = {}; newState[this.name] = this.checked ? this.on : this.off; registry.replace(this, newState); this._saveState(); this.signalReady(); waitOnce('pb-page-ready', () => { if (this.global) { this.dispatchEvent(new CustomEvent('pb-global-toggle', { detail: { selector: this.selector, command: this.action, state: this.checked }, bubbles: true, composed: true })); } else if (this.selector) { this.emitTo('pb-toggle', {refresh: false}); } }); } _setChecked(param) { if (typeof param !== 'undefined') { this.checked = param === this.on; } else { this.checked = this.default === this.on; } } attributeChangedCallback(name, oldVal, newVal) { super.attributeChangedCallback(name, oldVal, newVal); switch (name) { case this.on: this.propertiesOn[this.name] = newVal; break; case this.off: this.propertiesOff[this.name] = newVal; break; default: break; } } _changed() { this.checked = this.shadowRoot.getElementById('checkbox').checked; this._saveState(); if (!this.global) { this.emitTo('pb-toggle', {refresh: !this.selector}); } else { const state = {}; state[this.name] = this.checked ? this.on : this.off; registry.commit(this, state); } } _saveState() { const state = registry.getState(this); state[this.name] = this.checked ? this.on : this.off; Object.assign(state, this.checked ? this.propertiesOn : this.propertiesOff); if (this.selector) { const config = { selector: this.selector, command: this.action, state: this.checked }; if (this.global) { this.dispatchEvent(new CustomEvent('pb-global-toggle', { detail: config, bubbles: true, composed: true })); } else { state.selectors = state.selectors || []; addSelector(config, state.selectors); } } } } customElements.define('pb-toggle-feature', PbToggleFeature);