Source: delite/Invalidating.js

  1. /** @module delite/Invalidating */
  2. define(["dcl/dcl", "dojo/_base/lang", "./Stateful"], function (dcl, lang, Stateful) {
  3. /**
  4. * Mixin for classes (usually widgets) that watch a set of invalidating properties.
  5. * @class module:delite/Invalidating
  6. * @augments {module:delite/Stateful}
  7. * @mixin
  8. */
  9. return dcl(Stateful, /** @lends module:delite/Invalidating# */{
  10. // summary:
  11. // Mixin for classes (usually widgets) that watch a set of invalidating properties
  12. // and delay to the next execution frame the refresh following the changes of
  13. // the values of these properties. The receiving class must extend delite/Widget
  14. // or dojo/Evented.
  15. // description:
  16. // Once a set of properties have been declared subject to invalidation using the method
  17. // addInvalidatingProperties(), changes of the values of these properties possibly
  18. // end up calling refreshProperties() and in all cases refreshRendering(),
  19. // thus allowing the receiving class to refresh itself based on the new values.
  20. _renderHandle: null,
  21. // _invalidatingProperties: [private] Object
  22. // A hash of properties to watch in order to trigger the invalidation of these properties
  23. // and/or the rendering invalidation.
  24. // This list must be initialized by the time buildRendering() completes, usually in preCreate(),
  25. // using addInvalidatingProperties(). Default value is null.
  26. _invalidatingProperties: null,
  27. // _invalidatedProperties: [private] Object
  28. // A hash of invalidated properties either to refresh them or to refresh the rendering.
  29. _invalidatedProperties: null,
  30. // invalidProperties: Boolean
  31. // Whether at least one property is invalid. This is readonly information, one must call
  32. // invalidateProperties() to modify this flag.
  33. /**
  34. * Whether at least one property is invalid. This is readonly information, one must call
  35. * invalidateProperties() to modify this flag.
  36. * @member {boolean}
  37. * @default false
  38. */
  39. invalidProperties: false,
  40. // invalidRenderering: Boolean
  41. // Whether the rendering is invalid. This is readonly information, one must call
  42. // invalidateRendering() to modify this flag.
  43. /**
  44. * Whether the rendering is invalid. This is readonly information, one must call
  45. * invalidateRendering() to modify this flag.
  46. * @member {boolean}
  47. * @default false
  48. */
  49. invalidRendering: false,
  50. // if we are not a Widget, setup the listeners at construction time
  51. constructor: dcl.after(function () {
  52. this._initializeInvalidating();
  53. }),
  54. // if we are on a Widget, listen for any changes to properties after the widget has been rendered,
  55. // including when declarative properties (ex: iconClass=xyz) are applied.
  56. buildRendering: dcl.after(function () {
  57. // tags:
  58. // protected
  59. this._initializeInvalidating();
  60. }),
  61. _initializeInvalidating: function () {
  62. if (this._invalidatingProperties) {
  63. var props = Object.keys(this._invalidatingProperties);
  64. for (var i = 0; i < props.length; i++) {
  65. this.watch(props[i], lang.hitch(this, this._invalidatingProperties[props[i]]));
  66. }
  67. }
  68. this._invalidatedProperties = {};
  69. },
  70. /**
  71. * Adds the properties listed as arguments to the properties watched for triggering invalidation.
  72. * This method must be called during the startup lifecycle before buildRendering() completes,
  73. * usually in preCreate().
  74. */
  75. addInvalidatingProperties: function () {
  76. // summary:
  77. // Adds the properties listed as arguments to the properties watched for triggering invalidation.
  78. // This method must be called during the startup lifecycle before buildRendering() completes,
  79. // usually in preCreate().
  80. // description:
  81. // This can be used to trigger invalidation for rendering or for both property and rendering. When
  82. // no invalidation mechanism is specified, only the rendering refresh will be triggered, that is only
  83. // the refreshRendering() method will be called.
  84. // This method can either be called with a list of properties to invalidate the rendering as follows:
  85. // this.addInvalidatingProperties("foo", "bar", ...);
  86. // or with an hash of keys/values, the keys being the properties to invalidate and the values
  87. // being the invalidation method (either rendering or property and rendering):
  88. // this.addInvalidatingProperties({
  89. // "foo": "invalidateProperty",
  90. // "bar": "invalidateRendering"
  91. // });
  92. // tags:
  93. // protected
  94. if (this._invalidatingProperties == null) {
  95. this._invalidatingProperties = {};
  96. }
  97. for (var i = 0; i < arguments.length; i++) {
  98. if (typeof arguments[i] === "string") {
  99. // we just want the rendering to be refreshed
  100. this._invalidatingProperties[arguments[i]] = "invalidateRendering";
  101. } else {
  102. // we just merge key/value objects into our list of invalidating properties
  103. var props = Object.keys(arguments[i]);
  104. for (var j = 0; j < props.length; j++) {
  105. this._invalidatingProperties[props[j]] = arguments[i][props[j]];
  106. }
  107. }
  108. }
  109. },
  110. /**
  111. * Invalidates the property for the next execution frame.
  112. */
  113. invalidateProperty: function (name) {
  114. // summary:
  115. // Invalidates the property for the next execution frame.
  116. // name: String?
  117. // The name of the property to invalidate. If absent, the revalidation
  118. // is performed without a particular property being invalidated, that is
  119. // the argument passed to refreshProperties() does not contain is called without any argument.
  120. // tags:
  121. // protected
  122. if (name) {
  123. this._invalidatedProperties[name] = true;
  124. }
  125. if (!this.invalidProperties) {
  126. this.invalidProperties = true;
  127. // if we have a pending render, let's cancel it to execute it post properties refresh
  128. if (this._renderHandle) {
  129. // TODO: if we switch to defer() use remove() here
  130. clearTimeout(this._renderHandle);
  131. this.invalidRendering = false;
  132. this._renderHandle = null;
  133. }
  134. // TODO: should be defer but we might change this mechanism to a centralized one, so keep it like
  135. // that for now. If we don't come up with a centralized one, move defer to Destroyable and require
  136. // the Destroyable mixin.
  137. setTimeout(lang.hitch(this, "validateProperties"), 0);
  138. }
  139. },
  140. /**
  141. * Invalidates the rendering for the next execution frame.
  142. */
  143. invalidateRendering: function (name) {
  144. // summary:
  145. // Invalidates the rendering for the next execution frame.
  146. // name: String?
  147. // The name of the property to invalidate. If absent then the revalidation is asked without a
  148. // particular property being invalidated, that is refreshRendering() is called without
  149. // any argument.
  150. // tags:
  151. // protected
  152. if (name) {
  153. this._invalidatedProperties[name] = true;
  154. }
  155. if (!this.invalidRendering) {
  156. this.invalidRendering = true;
  157. // TODO: should be defer but we might change this mechanism to a centralized one, so keep it like
  158. // that for now. If we don't come up with a centralized one, move defer to Destroyable and require
  159. // the Destroyable mixin.
  160. this._renderHandle = setTimeout(lang.hitch(this, "validateRendering"), 0);
  161. }
  162. },
  163. validateProperties: function () {
  164. // summary:
  165. // Immediately validates the properties.
  166. // description:
  167. // Does nothing if no invalidating property is invalid.
  168. // You generally do not call that method yourself.
  169. // tags:
  170. // protected
  171. if (this.invalidProperties) {
  172. var props = lang.clone(this._invalidatedProperties);
  173. this.invalidProperties = false;
  174. this.refreshProperties(this._invalidatedProperties);
  175. this.emit("refresh-properties-complete",
  176. { invalidatedProperties: props, bubbles: true, cancelable: false });
  177. // if there are properties still marked invalid pursue further with rendering refresh
  178. this.invalidateRendering();
  179. }
  180. },
  181. /**
  182. * Immediately validates the rendering.
  183. */
  184. validateRendering: function () {
  185. // summary:
  186. // Immediately validates the rendering.
  187. // description:
  188. // Does nothing if the rendering is not invalid.
  189. // You generally do not call that method yourself.
  190. // tags:
  191. // protected
  192. if (this.invalidRendering) {
  193. var props = lang.clone(this._invalidatedProperties);
  194. this.invalidRendering = false;
  195. this.refreshRendering(this._invalidatedProperties);
  196. // do not fully delete invalidateProperties because someone might have set a property in
  197. // its refreshRendering method (not wise but who knows what people are doing) and a new cycle
  198. // should start with that properties listed as invalid instead of a blank set of properties
  199. for (var key in props) {
  200. delete this._invalidatedProperties[key];
  201. }
  202. this.emit("refresh-rendering-complete",
  203. { invalidatedProperties: props, bubbles: true, cancelable: false });
  204. }
  205. },
  206. /**
  207. * Immediately validates the properties and the rendering.
  208. */
  209. validate: function () {
  210. // summary:
  211. // Immediately validates the properties and the rendering.
  212. // description:
  213. // The method calls validateProperties() then validateRendering().
  214. // You generally do not call that method yourself.
  215. // tags:
  216. // protected
  217. this.validateProperties();
  218. this.validateRendering();
  219. },
  220. /**
  221. * Actually refreshes the properties.
  222. */
  223. refreshProperties: function (/*jshint unused: vars */props) {
  224. // summary:
  225. // Actually refreshes the properties.
  226. // description:
  227. // The default implementation does nothing. A class using this mixin
  228. // should implement this method if it needs to react to changes
  229. // of the value of an invalidating property, except for modifying the
  230. // DOM in which case refreshRendering() should be used instead.
  231. // Typically, this method should be overriden for implementing
  232. // the reconciliation of properties, for instance for adjusting
  233. // interdependent properties such as "min", "max", and "value".
  234. // The mixin calls this method before refreshRendering().
  235. // props: Object
  236. // A hash of invalidated properties. This hash will then be passed further down to the
  237. // refreshRendering() method. As such any modification to this hash will be
  238. // visible in refreshRendering().
  239. // tags:
  240. // protected
  241. },
  242. /**
  243. * Actually refreshes the rendering.
  244. */
  245. refreshRendering: function (/*jshint unused: vars */props) {
  246. // summary:
  247. // Actually refreshes the rendering.
  248. // description:
  249. // The default implementation does nothing. A class using this mixin
  250. // should implement this method if it needs to modify the DOM in reaction
  251. // to changes of the value of invalidating properties.
  252. // The mixin calls this method after refreshProperties().
  253. // props: Object
  254. // A hash of invalidated properties.
  255. // tags:
  256. // protected
  257. }
  258. });
  259. });