Source: decor/Invalidating.js

/** @module decor/Invalidating */
define([
	"dcl/dcl",
	"./Stateful",
	"./Destroyable"
], function (dcl, Stateful, Destroyable) {
	/**
	 * Mixin class for widgets
	 * that want to calculate computed properties at once and/or to render UI at once upon multiple property changes.
	 * @class module:decor/Invalidating
	 */
	var Invalidating = dcl([Stateful, Destroyable], /** @lends module:decor/Invalidating# */ {
		// Call initializeInvalidating() right after class is constructed.  Note though that this code won't run for
		// custom elements, since they call createdCallback() rather than constructor().
		// Instead, delite/Widget calls initializeInvalidating() directly.
		constructor: dcl.after(function () {
			this.initializeInvalidating();
		}),

		/**
		 * Make initial calls to `computeProperties()`, `initializeRendering()`, and `refreshRendering()`,
		 * and setup observers so those methods are called whenever properties are modified in the future.
		 * Normally this method is called automatically by the constructor, and should not be called manually,
		 * but the method is exposed for custom elements since they do not call the `constructor()` method.
		 * @protected
		 */
		initializeInvalidating: function () {
			if (!this._hComputing && !this._hRendering) {
				// Make initial call to computeProperties() and setup listener for future calls to computeProperties().
				// Any call to computeProperties(), including the initial call, may trigger more immediate calls to
				// computeProperties().
				this.own(this._hComputing = this.observe(function (oldValues) {
					this.computeProperties(oldValues);
					this.deliverComputing();
				}));
				this.computeProperties(this, true);

				// Make initial call to initializeRendering() and refreshRendering(), and setup listener for future
				// calls.
				this.initializeRendering(this);
				this.refreshRendering(this, true);
				this.own(this._hRendering = this.observe(function (oldValues) {
					var shouldInitializeRendering = this.shouldInitializeRendering(oldValues);
					if (shouldInitializeRendering) {
						this.initializeRendering(oldValues);
						this.refreshRendering(this, true);
					} else {
						this.refreshRendering(oldValues);
					}
				}));
			}
		},

		/**
		 * Synchronously deliver change records for computed properties
		 * so that `computeProperties()` is called if there are pending change records.
		 */
		deliverComputing: function () {
			this._hComputing && this._hComputing.deliver();
			return this._hComputing;
		},

		/**
		 * Discard change records for computed properties.
		 */
		discardComputing: function () {
			this._hComputing && this._hComputing.discardChanges();
			return this._hComputing;
		},

		/**
		 * Function to return if rendering should be initialized.
		 * (Instead of making partial changes for post-initialization)
		 * @param {Object} oldValues The hash table of old property values, keyed by property names.
		 * @param {boolean} isAfterCreation True if this call is right after instantiation.
		 * @return {boolean} True if rendering should be initialized.
		 */
		shouldInitializeRendering: function () {},

		/**
		 * Callback function to calculate computed properties upon property changes.
		 * @param {Object} oldValues The hash table of old property values, keyed by property names.
		 * @param {boolean} isAfterCreation True if this call is right after instantiation.
		 */
		computeProperties: function () {},

		/**
		 * Callback function to initialize rendering.
		 * @param {Object} oldValues The hash table of old property values, keyed by property names.
		 */
		initializeRendering: function () {},

		/**
		 * Callback function to render UI upon property changes.
		 * @param {Object} oldValues The hash table of old property values, keyed by property names.
		 * @param {boolean} isAfterInitialRendering True if this call is right after `initializeRendering()`.
		 */
		refreshRendering: function () {}
	});

	dcl.chainAfter(Invalidating, "computeProperties");
	dcl.chainAfter(Invalidating, "refreshRendering");

	return Invalidating;
});