/** * @module ol/Object */ import Event from './events/Event.js'; import ObjectEventType from './ObjectEventType.js'; import Observable from './Observable.js'; import {getUid} from './util.js'; import {isEmpty} from './obj.js'; /** * @classdesc * Events emitted by {@link module:ol/Object~BaseObject} instances are instances of this type. */ export class ObjectEvent extends Event { /** * @param {string} type The event type. * @param {string} key The property name. * @param {*} oldValue The old value for `key`. */ constructor(type, key, oldValue) { super(type); /** * The name of the property whose value is changing. * @type {string} * @api */ this.key = key; /** * The old value. To get the new value use `e.target.get(e.key)` where * `e` is the event object. * @type {*} * @api */ this.oldValue = oldValue; } } /*** * @template Return * @typedef {import("./Observable").OnSignature & * import("./Observable").OnSignature & * import("./Observable").CombinedOnSignature} ObjectOnSignature */ /** * @classdesc * Abstract base class; normally only used for creating subclasses and not * instantiated in apps. * Most non-trivial classes inherit from this. * * This extends {@link module:ol/Observable~Observable} with observable * properties, where each property is observable as well as the object as a * whole. * * Classes that inherit from this have pre-defined properties, to which you can * add your owns. The pre-defined properties are listed in this documentation as * 'Observable Properties', and have their own accessors; for example, * {@link module:ol/Map~Map} has a `target` property, accessed with * `getTarget()` and changed with `setTarget()`. Not all properties are however * settable. There are also general-purpose accessors `get()` and `set()`. For * example, `get('target')` is equivalent to `getTarget()`. * * The `set` accessors trigger a change event, and you can monitor this by * registering a listener. For example, {@link module:ol/View~View} has a * `center` property, so `view.on('change:center', function(evt) {...});` would * call the function whenever the value of the center property changes. Within * the function, `evt.target` would be the view, so `evt.target.getCenter()` * would return the new center. * * You can add your own observable properties with * `object.set('prop', 'value')`, and retrieve that with `object.get('prop')`. * You can listen for changes on that property value with * `object.on('change:prop', listener)`. You can get a list of all * properties with {@link module:ol/Object~BaseObject#getProperties}. * * Note that the observable properties are separate from standard JS properties. * You can, for example, give your map object a title with * `map.title='New title'` and with `map.set('title', 'Another title')`. The * first will be a `hasOwnProperty`; the second will appear in * `getProperties()`. Only the second is observable. * * Properties can be deleted by using the unset method. E.g. * object.unset('foo'). * * @fires ObjectEvent * @api */ class BaseObject extends Observable { /** * @param {Object} [values] An object with key-value pairs. */ constructor(values) { super(); /*** * @type {ObjectOnSignature} */ this.on; /*** * @type {ObjectOnSignature} */ this.once; /*** * @type {ObjectOnSignature} */ this.un; // Call {@link module:ol/util.getUid} to ensure that the order of objects' ids is // the same as the order in which they were created. This also helps to // ensure that object properties are always added in the same order, which // helps many JavaScript engines generate faster code. getUid(this); /** * @private * @type {Object} */ this.values_ = null; if (values !== undefined) { this.setProperties(values); } } /** * Gets a value. * @param {string} key Key name. * @return {*} Value. * @api */ get(key) { let value; if (this.values_ && this.values_.hasOwnProperty(key)) { value = this.values_[key]; } return value; } /** * Get a list of object property names. * @return {Array} List of property names. * @api */ getKeys() { return (this.values_ && Object.keys(this.values_)) || []; } /** * Get an object of all property names and values. * @return {Object} Object. * @api */ getProperties() { return (this.values_ && Object.assign({}, this.values_)) || {}; } /** * @return {boolean} The object has properties. */ hasProperties() { return !!this.values_; } /** * @param {string} key Key name. * @param {*} oldValue Old value. */ notify(key, oldValue) { let eventType; eventType = `change:${key}`; if (this.hasListener(eventType)) { this.dispatchEvent(new ObjectEvent(eventType, key, oldValue)); } eventType = ObjectEventType.PROPERTYCHANGE; if (this.hasListener(eventType)) { this.dispatchEvent(new ObjectEvent(eventType, key, oldValue)); } } /** * @param {string} key Key name. * @param {import("./events.js").Listener} listener Listener. */ addChangeListener(key, listener) { this.addEventListener(`change:${key}`, listener); } /** * @param {string} key Key name. * @param {import("./events.js").Listener} listener Listener. */ removeChangeListener(key, listener) { this.removeEventListener(`change:${key}`, listener); } /** * Sets a value. * @param {string} key Key name. * @param {*} value Value. * @param {boolean} [silent] Update without triggering an event. * @api */ set(key, value, silent) { const values = this.values_ || (this.values_ = {}); if (silent) { values[key] = value; } else { const oldValue = values[key]; values[key] = value; if (oldValue !== value) { this.notify(key, oldValue); } } } /** * Sets a collection of key-value pairs. Note that this changes any existing * properties and adds new ones (it does not remove any existing properties). * @param {Object} values Values. * @param {boolean} [silent] Update without triggering an event. * @api */ setProperties(values, silent) { for (const key in values) { this.set(key, values[key], silent); } } /** * Apply any properties from another object without triggering events. * @param {BaseObject} source The source object. * @protected */ applyProperties(source) { if (!source.values_) { return; } Object.assign(this.values_ || (this.values_ = {}), source.values_); } /** * Unsets a property. * @param {string} key Key name. * @param {boolean} [silent] Unset without triggering an event. * @api */ unset(key, silent) { if (this.values_ && key in this.values_) { const oldValue = this.values_[key]; delete this.values_[key]; if (isEmpty(this.values_)) { this.values_ = null; } if (!silent) { this.notify(key, oldValue); } } } } export default BaseObject;