ember-source
Version:
A JavaScript framework for creating ambitious web applications
182 lines (158 loc) • 6.78 kB
JavaScript
import { peekMeta } from '../-internals/meta/lib/meta.js';
import '../../shared-chunks/mandatory-setter-DHZe7-kW.js';
import '../debug/index.js';
import '../../@glimmer/destroyable/index.js';
import '../../@glimmer/validator/index.js';
import '../../shared-chunks/debug-to-string-CFb7h0lY.js';
import '../../@glimmer/global-context/index.js';
import '../../shared-chunks/reference-C3TKDRnP.js';
import '../../shared-chunks/capabilities-O_xc7Yqk.js';
import { g as get, q as hasListeners, r as removeObserver, u as addObserver, n as notifyPropertyChange, v as endPropertyChanges, w as beginPropertyChanges } from '../../shared-chunks/observers-Bj9qLVau.js';
import { s as set } from '../../shared-chunks/property_set-DaoZXGM5.js';
import { s as setProperties, g as getProperties } from '../../shared-chunks/set_properties-kVGzZL_a.js';
import '../-internals/environment/index.js';
import Mixin from './mixin.js';
import { assert } from '../debug/lib/assert.js';
/**
@module @ember/object/observable
*/
/**
## Overview
This mixin provides properties and property observing functionality, core
features of the Ember object model.
Properties and observers allow one object to observe changes to a
property on another object. This is one of the fundamental ways that
models, controllers and views communicate with each other in an Ember
application.
Any object that has this mixin applied can be used in observer
operations. That includes `EmberObject` and most objects you will
interact with as you write your Ember application.
Note that you will not generally apply this mixin to classes yourself,
but you will use the features provided by this module frequently, so it
is important to understand how to use it.
## Using `get()` and `set()`
Because of Ember's support for bindings and observers, you will always
access properties using the get method, and set properties using the
set method. This allows the observing objects to be notified and
computed properties to be handled properly.
More documentation about `get` and `set` are below.
## Observing Property Changes
You typically observe property changes simply by using the `observer`
function in classes that you write.
For example:
```javascript
import { observer } from '@ember/object';
import EmberObject from '@ember/object';
EmberObject.extend({
valueObserver: observer('value', function(sender, key, value, rev) {
// Executes whenever the "value" property changes
// See the addObserver method for more information about the callback arguments
})
});
```
Although this is the most common way to add an observer, this capability
is actually built into the `EmberObject` class on top of two methods
defined in this mixin: `addObserver` and `removeObserver`. You can use
these two methods to add and remove observers yourself if you need to
do so at runtime.
To add an observer for a property, call:
```javascript
object.addObserver('propertyKey', targetObject, targetAction)
```
This will call the `targetAction` method on the `targetObject` whenever
the value of the `propertyKey` changes.
Note that if `propertyKey` is a computed property, the observer will be
called when any of the property dependencies are changed, even if the
resulting value of the computed property is unchanged. This is necessary
because computed properties are not computed until `get` is called.
@class Observable
@public
*/
const Observable = Mixin.create({
get(keyName) {
return get(this, keyName);
},
getProperties(...args) {
return getProperties(this, ...args);
},
set(keyName, value) {
return set(this, keyName, value);
},
setProperties(hash) {
return setProperties(this, hash);
},
/**
Begins a grouping of property changes.
You can use this method to group property changes so that notifications
will not be sent until the changes are finished. If you plan to make a
large number of changes to an object at one time, you should call this
method at the beginning of the changes to begin deferring change
notifications. When you are done making changes, call
`endPropertyChanges()` to deliver the deferred change notifications and end
deferring.
@method beginPropertyChanges
@return {Observable}
@private
*/
beginPropertyChanges() {
beginPropertyChanges();
return this;
},
/**
Ends a grouping of property changes.
You can use this method to group property changes so that notifications
will not be sent until the changes are finished. If you plan to make a
large number of changes to an object at one time, you should call
`beginPropertyChanges()` at the beginning of the changes to defer change
notifications. When you are done making changes, call this method to
deliver the deferred change notifications and end deferring.
@method endPropertyChanges
@return {Observable}
@private
*/
endPropertyChanges() {
endPropertyChanges();
return this;
},
notifyPropertyChange(keyName) {
notifyPropertyChange(this, keyName);
return this;
},
addObserver(key, target, method, sync) {
addObserver(this, key, target, method, sync);
return this;
},
removeObserver(key, target, method, sync) {
removeObserver(this, key, target, method, sync);
return this;
},
/**
Returns `true` if the object currently has observers registered for a
particular key. You can use this method to potentially defer performing
an expensive action until someone begins observing a particular property
on the object.
@method hasObserverFor
@param {String} key Key to check
@return {Boolean}
@private
*/
hasObserverFor(key) {
return hasListeners(this, `${key}:change`);
},
incrementProperty(keyName, increment = 1) {
(!(!isNaN(parseFloat(String(increment))) && isFinite(increment)) && assert('Must pass a numeric value to incrementProperty', !isNaN(parseFloat(String(increment))) && isFinite(increment)));
return set(this, keyName, (parseFloat(get(this, keyName)) || 0) + increment);
},
decrementProperty(keyName, decrement = 1) {
(!((typeof decrement === 'number' || !isNaN(parseFloat(decrement))) && isFinite(decrement)) && assert('Must pass a numeric value to decrementProperty', (typeof decrement === 'number' || !isNaN(parseFloat(decrement))) && isFinite(decrement)));
return set(this, keyName, (get(this, keyName) || 0) - decrement);
},
toggleProperty(keyName) {
return set(this, keyName, !get(this, keyName));
},
cacheFor(keyName) {
let meta = peekMeta(this);
return meta !== null ? meta.valueFor(keyName) : undefined;
}
});
export { Observable as default };