1 /*
  2  Copyright 2008-2021
  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 <http://www.gnu.org/licenses/>
 29  and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  utils/type
 39  */
 40 
 41 define(['jxg', 'utils/type'], function (JXG, Type) {
 42 
 43     "use strict";
 44 
 45     /**
 46      * A composition is a simple container that manages none or more {@link JXG.GeometryElement}s.
 47      * @param {Object} elements A list of elements with a descriptive name for the element as the key and a reference
 48      * to the element as the value of every list entry. The name is used to access the element later on.
 49      * @example
 50      * var p1 = board.create('point', [1, 2]),
 51      *     p2 = board.create('point', [2, 3]),
 52      *     c = new JXG.Composition({
 53      *         start: p1,
 54      *         end: p2
 55      *     });
 56      *
 57      * // moves p1 to [3, 3]
 58      * c.start.moveTo([3, 3]);
 59      * @class JXG.Composition
 60      */
 61     JXG.Composition = function (elements) {
 62         var e,
 63             that = this,
 64             genericMethods = [
 65                 /**
 66                  * Invokes setAttribute for every stored element with a setAttribute method and hands over the given arguments.
 67                  * See {@link JXG.GeometryElement#setAttribute} for further description, valid parameters and return values.
 68                  * @name setAttribute
 69                  * @memberOf JXG.Composition.prototype
 70                  * @function
 71                  */
 72                 'setAttribute',
 73 
 74                 /**
 75                 * Invokes setParents for every stored element with a setParents method and hands over the given arguments.
 76                 * See {@link JXG.GeometryElement#setParents} for further description, valid parameters and return values.
 77                 * @name setParents
 78                 * @memberOf JXG.Composition.prototype
 79                 * @function
 80                 */
 81                 'setParents',
 82 
 83                 /**
 84                  * Invokes prepareUpdate for every stored element with a prepareUpdate method and hands over the given arguments.
 85                  * See {@link JXG.GeometryElement#prepareUpdate} for further description, valid parameters and return values.
 86                  * @name prepareUpdate
 87                  * @memberOf JXG.Composition.prototype
 88                  * @function
 89                  */
 90                 'prepareUpdate',
 91 
 92                 /**
 93                  * Invokes updateRenderer for every stored element with a updateRenderer method and hands over the given arguments.
 94                  * See {@link JXG.GeometryElement#updateRenderer} for further description, valid parameters and return values.
 95                  * @name updateRenderer
 96                  * @memberOf JXG.Composition.prototype
 97                  * @function
 98                  */
 99                 'updateRenderer',
100 
101                 /**
102                  * Invokes update for every stored element with a update method and hands over the given arguments.
103                  * See {@link JXG.GeometryElement#update} for further description, valid parameters and return values.
104                  * @name update
105                  * @memberOf JXG.Composition.prototype
106                  * @function
107                  */
108                 'update',
109 
110                 /**
111                  * Invokes fullUpdate for every stored element with a fullUpdate method and hands over the given arguments.
112                  * See {@link JXG.GeometryElement#fullUpdate} for further description, valid parameters and return values.
113                  * @name fullUpdate
114                  * @memberOf JXG.Composition.prototype
115                  * @function
116                  */
117                 'fullUpdate',
118 
119                 /**
120                  * Invokes highlight for every stored element with a highlight method and hands over the given arguments.
121                  * See {@link JXG.GeometryElement#highlight} for further description, valid parameters and return values.
122                  * @name highlight
123                  * @memberOf JXG.Composition.prototype
124                  * @function
125                  */
126                 'highlight',
127 
128                 /**
129                  * Invokes noHighlight for every stored element with a noHighlight method and hands over the given arguments.
130                  * See {@link JXG.GeometryElement#noHighlight} for further description, valid parameters and return values.
131                  * @name noHighlight
132                  * @memberOf JXG.Composition.prototype
133                  * @function
134                  */
135                 'noHighlight'
136             ],
137             generateMethod = function (what) {
138                 return function () {
139                     var i;
140 
141                     for (i in that.elements) {
142                         if (that.elements.hasOwnProperty(i)) {
143                             if (Type.exists(that.elements[i][what])) {
144                                 that.elements[i][what].apply(that.elements[i], arguments);
145                             }
146                         }
147                     }
148                     return that;
149                 };
150             };
151 
152         for (e = 0; e < genericMethods.length; e++) {
153             this[genericMethods[e]] = generateMethod(genericMethods[e]);
154         }
155 
156         this.elements = {};
157         this.objects = this.elements;
158 
159         this.elementsByName = {};
160         this.objectsList = [];
161 
162         // unused, required for select()
163         this.groups = {};
164 
165         this.methodMap = {
166             setAttribute: 'setAttribute',
167             setProperty: 'setAttribute',
168             setParents: 'setParents',
169             add: 'add',
170             remove: 'remove',
171             select: 'select'
172         };
173 
174         for (e in elements) {
175             if (elements.hasOwnProperty(e)) {
176                 this.add(e, elements[e]);
177             }
178         }
179 
180         this.dump = true;
181         this.subs = {};
182     };
183 
184     JXG.extend(JXG.Composition.prototype, /** @lends JXG.Composition.prototype */ {
185 
186         /**
187          * Adds an element to the composition container.
188          * @param {String} what Descriptive name for the element, e.g. <em>startpoint</em> or <em>area</em>. This is used to
189          * access the element later on. There are some reserved names: <em>elements, add, remove, update, prepareUpdate,
190          * updateRenderer, highlight, noHighlight</em>, and all names that would form invalid object property names in
191          * JavaScript.
192          * @param {JXG.GeometryElement|JXG.Composition} element A reference to the element that is to be added. This can be
193          * another composition, too.
194          * @returns {Boolean} True, if the element was added successfully. Reasons why adding the element failed include
195          * using a reserved name and providing an invalid element.
196          */
197         add: function (what, element) {
198             if (!Type.exists(this[what]) && Type.exists(element)) {
199                 if (Type.exists(element.id)) {
200                     this.elements[element.id] = element;
201                 } else {
202                     this.elements[what] = element;
203                 }
204 
205                 if (Type.exists(element.name)) {
206                     this.elementsByName[element.name] = element;
207                 }
208 
209                 element.on('attribute:name', this.nameListener, this);
210 
211                 this.objectsList.push(element);
212                 this[what] = element;
213                 this.methodMap[what] = element;
214 
215                 return true;
216             }
217 
218             return false;
219         },
220 
221         /**
222          * Remove an element from the composition container.
223          * @param {String} what The name used to access the element.
224          * @returns {Boolean} True, if the element has been removed successfully.
225          */
226         remove: function (what) {
227             var found = false,
228                 e;
229 
230             for (e in this.elements) {
231                 if (this.elements.hasOwnProperty(e)) {
232                     if (this.elements[e].id === this[what].id) {
233                         found = true;
234                         break;
235                     }
236                 }
237             }
238 
239             if (found) {
240                 delete this.elements[this[what].id];
241                 delete this[what];
242             }
243 
244             return found;
245         },
246 
247         nameListener: function (oval, nval, el) {
248             delete this.elementsByName[oval];
249             this.elementsByName[nval] = el;
250         },
251 
252         select: function (filter) {
253             // for now, hijack JXG.Board's select() method
254             if (Type.exists(JXG.Board)) {
255                 return JXG.Board.prototype.select.call(this, filter);
256             }
257 
258             return new JXG.Composition();
259         },
260 
261         getParents: function () {
262             return this.parents;
263         },
264 
265         getType: function () {
266             return this.elType;
267         },
268 
269         getAttributes: function () {
270             var attr = {},
271                 e;
272 
273             for (e in this.subs) {
274                 if (this.subs.hasOwnProperty(e)) {
275                     attr[e] = this.subs[e].visProp;
276                 }
277             }
278 
279             return this.attr;
280         }
281     });
282 
283     return JXG.Composition;
284 });
285