container.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. 'use strict';
  2. const Node = require('./node');
  3. class Container extends Node {
  4. constructor (opts) {
  5. super(opts);
  6. if (!this.nodes) {
  7. this.nodes = [];
  8. }
  9. }
  10. push (child) {
  11. child.parent = this;
  12. this.nodes.push(child);
  13. return this;
  14. }
  15. each (callback) {
  16. if (!this.lastEach) this.lastEach = 0;
  17. if (!this.indexes) this.indexes = { };
  18. this.lastEach += 1;
  19. let id = this.lastEach,
  20. index,
  21. result;
  22. this.indexes[id] = 0;
  23. if (!this.nodes) return undefined;
  24. while (this.indexes[id] < this.nodes.length) {
  25. index = this.indexes[id];
  26. result = callback(this.nodes[index], index);
  27. if (result === false) break;
  28. this.indexes[id] += 1;
  29. }
  30. delete this.indexes[id];
  31. return result;
  32. }
  33. walk (callback) {
  34. return this.each((child, i) => {
  35. let result = callback(child, i);
  36. if (result !== false && child.walk) {
  37. result = child.walk(callback);
  38. }
  39. return result;
  40. });
  41. }
  42. walkType (type, callback) {
  43. if (!type || !callback) {
  44. throw new Error('Parameters {type} and {callback} are required.');
  45. }
  46. // allow users to pass a constructor, or node type string; eg. Word.
  47. const isTypeCallable = typeof type === 'function';
  48. return this.walk((node, index) => {
  49. if (isTypeCallable && node instanceof type || !isTypeCallable && node.type === type) {
  50. return callback.call(this, node, index);
  51. }
  52. });
  53. }
  54. append (node) {
  55. node.parent = this;
  56. this.nodes.push(node);
  57. return this;
  58. }
  59. prepend (node) {
  60. node.parent = this;
  61. this.nodes.unshift(node);
  62. return this;
  63. }
  64. cleanRaws (keepBetween) {
  65. super.cleanRaws(keepBetween);
  66. if (this.nodes) {
  67. for (let node of this.nodes) node.cleanRaws(keepBetween);
  68. }
  69. }
  70. insertAfter (oldNode, newNode) {
  71. let oldIndex = this.index(oldNode),
  72. index;
  73. this.nodes.splice(oldIndex + 1, 0, newNode);
  74. for (let id in this.indexes) {
  75. index = this.indexes[id];
  76. if (oldIndex <= index) {
  77. this.indexes[id] = index + this.nodes.length;
  78. }
  79. }
  80. return this;
  81. }
  82. insertBefore (oldNode, newNode) {
  83. let oldIndex = this.index(oldNode),
  84. index;
  85. this.nodes.splice(oldIndex, 0, newNode);
  86. for (let id in this.indexes) {
  87. index = this.indexes[id];
  88. if (oldIndex <= index) {
  89. this.indexes[id] = index + this.nodes.length;
  90. }
  91. }
  92. return this;
  93. }
  94. removeChild (child) {
  95. child = this.index(child);
  96. this.nodes[child].parent = undefined;
  97. this.nodes.splice(child, 1);
  98. let index;
  99. for (let id in this.indexes) {
  100. index = this.indexes[id];
  101. if (index >= child) {
  102. this.indexes[id] = index - 1;
  103. }
  104. }
  105. return this;
  106. }
  107. removeAll () {
  108. for (let node of this.nodes) node.parent = undefined;
  109. this.nodes = [];
  110. return this;
  111. }
  112. every (condition) {
  113. return this.nodes.every(condition);
  114. }
  115. some (condition) {
  116. return this.nodes.some(condition);
  117. }
  118. index (child) {
  119. if (typeof child === 'number') {
  120. return child;
  121. }
  122. else {
  123. return this.nodes.indexOf(child);
  124. }
  125. }
  126. get first () {
  127. if (!this.nodes) return undefined;
  128. return this.nodes[0];
  129. }
  130. get last () {
  131. if (!this.nodes) return undefined;
  132. return this.nodes[this.nodes.length - 1];
  133. }
  134. toString () {
  135. let result = this.nodes.map(String).join('');
  136. if (this.value) {
  137. result = this.value + result;
  138. }
  139. if (this.raws.before) {
  140. result = this.raws.before + result;
  141. }
  142. if (this.raws.after) {
  143. result += this.raws.after;
  144. }
  145. return result;
  146. }
  147. }
  148. Container.registerWalker = (constructor) => {
  149. let walkerName = 'walk' + constructor.name;
  150. // plural sugar
  151. if (walkerName.lastIndexOf('s') !== walkerName.length - 1) {
  152. walkerName += 's';
  153. }
  154. if (Container.prototype[walkerName]) {
  155. return;
  156. }
  157. // we need access to `this` so we can't use an arrow function
  158. Container.prototype[walkerName] = function (callback) {
  159. return this.walkType(constructor, callback);
  160. };
  161. };
  162. module.exports = Container;