1 /*
  2  Copyright 2008-2023
  3  Matthias Ehmann,
  4  Michael Gerhaeuser,
  5  Carsten Miller,
  6  Bianca Valentin,
  7  Alfred Wassermann,
  8  Peter Wilfahrt
  9 
 10  This file is part of JSXGraph.
 11 
 12  JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14  You can redistribute it and/or modify it under the terms of the
 15 
 16  * GNU Lesser General Public License as published by
 17  the Free Software Foundation, either version 3 of the License, or
 18  (at your option) any later version
 19  OR
 20  * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22  JSXGraph is distributed in the hope that it will be useful,
 23  but WITHOUT ANY WARRANTY; without even the implied warranty of
 24  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25  GNU Lesser General Public License for more details.
 26 
 27  You should have received a copy of the GNU Lesser General Public License and
 28  the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 29  and <https://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 /*global JXG: true, define: true*/
 33 /*jslint nomen: true, plusplus: true*/
 34 
 35 import JXG from "../jxg";
 36 import Type from "../utils/type";
 37 
 38 /**
 39  * A composition is a simple container that manages none or more {@link JXG.GeometryElement}s.
 40  * @param {Object} elements A list of elements with a descriptive name for the element as the key and a reference
 41  * to the element as the value of every list entry. The name is used to access the element later on.
 42  * @example
 43  * var p1 = board.create('point', [1, 2]),
 44  *     p2 = board.create('point', [2, 3]),
 45  *     c = new JXG.Composition({
 46  *         start: p1,
 47  *         end: p2
 48  *     });
 49  *
 50  * // moves p1 to [3, 3]
 51  * c.start.moveTo([3, 3]);
 52  * @class JXG.Composition
 53  */
 54 JXG.Composition = function (elements) {
 55     var e,
 56         that = this,
 57         genericMethods = [
 58             /**
 59              * Invokes setAttribute for every stored element with a setAttribute method and hands over the given arguments.
 60              * See {@link JXG.GeometryElement#setAttribute} for further description, valid parameters and return values.
 61              * @name setAttribute
 62              * @memberOf JXG.Composition.prototype
 63              * @function
 64              */
 65             "setAttribute",
 66 
 67             /**
 68              * Invokes setParents for every stored element with a setParents method and hands over the given arguments.
 69              * See {@link JXG.GeometryElement#setParents} for further description, valid parameters and return values.
 70              * @name setParents
 71              * @memberOf JXG.Composition.prototype
 72              * @function
 73              */
 74             "setParents",
 75 
 76             /**
 77              * Invokes prepareUpdate for every stored element with a prepareUpdate method and hands over the given arguments.
 78              * See {@link JXG.GeometryElement#prepareUpdate} for further description, valid parameters and return values.
 79              * @name prepareUpdate
 80              * @memberOf JXG.Composition.prototype
 81              * @function
 82              */
 83             "prepareUpdate",
 84 
 85             /**
 86              * Invokes updateRenderer for every stored element with a updateRenderer method and hands over the given arguments.
 87              * See {@link JXG.GeometryElement#updateRenderer} for further description, valid parameters and return values.
 88              * @name updateRenderer
 89              * @memberOf JXG.Composition.prototype
 90              * @function
 91              */
 92             "updateRenderer",
 93 
 94             /**
 95              * Invokes update for every stored element with a update method and hands over the given arguments.
 96              * See {@link JXG.GeometryElement#update} for further description, valid parameters and return values.
 97              * @name update
 98              * @memberOf JXG.Composition.prototype
 99              * @function
100              */
101             "update",
102 
103             /**
104              * Invokes fullUpdate for every stored element with a fullUpdate method and hands over the given arguments.
105              * See {@link JXG.GeometryElement#fullUpdate} for further description, valid parameters and return values.
106              * @name fullUpdate
107              * @memberOf JXG.Composition.prototype
108              * @function
109              */
110             "fullUpdate",
111 
112             /**
113              * Invokes highlight for every stored element with a highlight method and hands over the given arguments.
114              * See {@link JXG.GeometryElement#highlight} for further description, valid parameters and return values.
115              * @name highlight
116              * @memberOf JXG.Composition.prototype
117              * @function
118              */
119             "highlight",
120 
121             /**
122              * Invokes noHighlight for every stored element with a noHighlight method and hands over the given arguments.
123              * See {@link JXG.GeometryElement#noHighlight} for further description, valid parameters and return values.
124              * @name noHighlight
125              * @memberOf JXG.Composition.prototype
126              * @function
127              */
128             "noHighlight"
129         ],
130         generateMethod = function (what) {
131             return function () {
132                 var i;
133 
134                 for (i in that.elements) {
135                     if (that.elements.hasOwnProperty(i)) {
136                         if (Type.exists(that.elements[i][what])) {
137                             that.elements[i][what].apply(that.elements[i], arguments);
138                         }
139                     }
140                 }
141                 return that;
142             };
143         };
144 
145     for (e = 0; e < genericMethods.length; e++) {
146         this[genericMethods[e]] = generateMethod(genericMethods[e]);
147     }
148 
149     this.elements = {};
150     this.objects = this.elements;
151 
152     this.elementsByName = {};
153     this.objectsList = [];
154 
155     // unused, required for select()
156     this.groups = {};
157 
158     this.methodMap = {
159         setAttribute: "setAttribute",
160         setProperty: "setAttribute",
161         setParents: "setParents",
162         add: "add",
163         remove: "remove",
164         select: "select"
165     };
166 
167     for (e in elements) {
168         if (elements.hasOwnProperty(e)) {
169             this.add(e, elements[e]);
170         }
171     }
172 
173     this.dump = true;
174     this.subs = {};
175 };
176 
177 JXG.extend(
178     JXG.Composition.prototype,
179     /** @lends JXG.Composition.prototype */ {
180         /**
181          * Adds an element to the composition container.
182          * @param {String} what Descriptive name for the element, e.g. <em>startpoint</em> or <em>area</em>. This is used to
183          * access the element later on. There are some reserved names: <em>elements, add, remove, update, prepareUpdate,
184          * updateRenderer, highlight, noHighlight</em>, and all names that would form invalid object property names in
185          * JavaScript.
186          * @param {JXG.GeometryElement|JXG.Composition} element A reference to the element that is to be added. This can be
187          * another composition, too.
188          * @returns {Boolean} True, if the element was added successfully. Reasons why adding the element failed include
189          * using a reserved name and providing an invalid element.
190          */
191         add: function (what, element) {
192             if (!Type.exists(this[what]) && Type.exists(element)) {
193                 if (Type.exists(element.id)) {
194                     this.elements[element.id] = element;
195                 } else {
196                     this.elements[what] = element;
197                 }
198 
199                 if (Type.exists(element.name)) {
200                     this.elementsByName[element.name] = element;
201                 }
202 
203                 element.on("attribute:name", this.nameListener, this);
204 
205                 this.objectsList.push(element);
206                 this[what] = element;
207                 this.methodMap[what] = element;
208 
209                 return true;
210             }
211 
212             return false;
213         },
214 
215         /**
216          * Remove an element from the composition container.
217          * @param {String} what The name used to access the element.
218          * @returns {Boolean} True, if the element has been removed successfully.
219          */
220         remove: function (what) {
221             var found = false,
222                 e;
223 
224             for (e in this.elements) {
225                 if (this.elements.hasOwnProperty(e)) {
226                     if (this.elements[e].id === this[what].id) {
227                         found = true;
228                         break;
229                     }
230                 }
231             }
232 
233             if (found) {
234                 delete this.elements[this[what].id];
235                 delete this[what];
236             }
237 
238             return found;
239         },
240 
241         nameListener: function (oval, nval, el) {
242             delete this.elementsByName[oval];
243             this.elementsByName[nval] = el;
244         },
245 
246         select: function (filter) {
247             // for now, hijack JXG.Board's select() method
248             if (Type.exists(JXG.Board)) {
249                 return JXG.Board.prototype.select.call(this, filter);
250             }
251 
252             return new JXG.Composition();
253         },
254 
255         getParents: function () {
256             return this.parents;
257         },
258 
259         getType: function () {
260             return this.elType;
261         },
262 
263         getAttributes: function () {
264             var attr = {},
265                 e;
266 
267             for (e in this.subs) {
268                 if (this.subs.hasOwnProperty(e)) {
269                     attr[e] = this.subs[e].visProp;
270                 }
271             }
272 
273             return this.attr;
274         }
275     }
276 );
277 
278 export default JXG.Composition;
279