Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
H5PEditor.List = (function ($) {
2
  /**
3
   * List structure.
4
   *
5
   * @class
6
   * @param {*} parent structure
7
   * @param {Object} field Semantic description of field
8
   * @param {Array} [parameters] Default parameters for this field
9
   * @param {Function} setValue Call to set our parameters
10
   */
11
  function List(parent, field, parameters, setValue) {
12
    var self = this;
13
 
14
    // Initialize semantics structure inheritance
15
    H5PEditor.SemanticStructure.call(self, field, {
16
      name: 'ListEditor',
17
      label: H5PEditor.t('core', 'listLabel')
18
    });
19
 
20
    self.field = field;
21
    // Make it possible to travel up tree.
22
    self.parent = parent; // (Could this be done a better way in the future?)
23
 
24
    /**
25
     * Keep track of child fields. Should not be exposed directly,
26
     * create functions for using or finding the children.
27
     *
28
     * @private
29
     * @type {Array}
30
     */
31
    var children = [];
32
 
33
    // Prepare the old ready callback system
34
    var readyCallbacks = [];
35
    var passReadyCallbacks = true;
36
    parent.ready(function () {
37
      passReadyCallbacks = false;
38
    }); // (In the future we should use the event system for this, i.e. self.once('ready'))
39
 
40
    // Listen for widget changes
41
    self.on('changeWidget', function () {
42
      // Append all items to new widget
43
      for (var i = 0; i < children.length; i++) {
44
        self.widget.addItem(children[i], i);
45
      }
46
    });
47
 
48
    /**
49
     * Add all items to list without appending to DOM.
50
     *
51
     * @public
52
     */
53
    var init = function () {
54
      var i;
55
      if (parameters !== undefined && parameters.length) {
56
        for (i = 0; i < parameters.length; i++) {
57
          if (parameters[i] === null) {
58
            parameters[i] = undefined;
59
          }
60
          addItem(i);
61
        }
62
      }
63
      else {
64
        if (field.defaultNum === undefined) {
65
          // Use min or 1 if no default item number is set.
66
          field.defaultNum = (field.min !== undefined ? field.min : 1);
67
        }
68
        // Add default number of fields.
69
        for (i = 0; i < field.defaultNum; i++) {
70
          addItem(i);
71
        }
72
      }
73
    };
74
 
75
    /**
76
     * Make sure list is created when setting a parameter.
77
     *
78
     * @private
79
     * @param {number} index
80
     * @param {*} value
81
     */
82
    var setParameters = function (index, value) {
83
      if (parameters === undefined) {
84
        // Create new parameters for list
85
        parameters = [];
86
        setValue(field, parameters);
87
      }
88
      parameters[index] = value;
89
    };
90
 
91
    /**
92
     * Handle group collapsed state change.
93
     * Used when children are groups to determine if all groups are collapsed.
94
     */
95
    const handleGroupCollapsedStateChanged = () => {
96
      if (children.some(child => !(child instanceof H5PEditor.Group))) {
97
        return; // Only groups have collapsed state
98
      }
99
 
100
      const areAllGroupsCollapsed = !children.some(
101
        (child) => child.isExpanded()
102
      );
103
 
104
      this.trigger('groupCollapsedStateChanged', {
105
        allGroupsCollapsed: areAllGroupsCollapsed
106
      });
107
    };
108
 
109
    /**
110
     * Add item to list.
111
     *
112
     * @private
113
     * @param {Number} index
114
     * @param {*} [paramsOverride] Override params using this value.
115
     */
116
    var addItem = function (index, paramsOverride) {
117
      var childField = field.field;
118
      var widget = H5PEditor.getWidgetName(childField);
119
 
120
      if (
121
        (
122
          parameters === undefined ||
123
          parameters[index] === undefined
124
        ) &&
125
        childField['default'] !== undefined
126
      ) {
127
        // Use default value
128
        setParameters(index, childField['default']);
129
      }
130
      if (paramsOverride !== undefined) {
131
        // Use override params
132
        setParameters(index, paramsOverride);
133
      }
134
 
135
      var child = children[index] = new H5PEditor.widgets[widget](
136
        self,
137
        childField,
138
        parameters === undefined ? undefined : parameters[index],
139
        function (myChildField, value) {
140
          var i = findIndex(child);
141
          setParameters(i === undefined ? index : i, value);
142
        }
143
      );
144
 
145
      if (child instanceof H5PEditor.Group) {
146
        child.on('collapsed', () => {
147
          handleGroupCollapsedStateChanged();
148
        });
149
 
150
        child.on('expanded', () => {
151
          handleGroupCollapsedStateChanged();
152
        });
153
      }
154
 
155
      return child;
156
    };
157
 
158
    /**
159
     * Finds the index for the given child.
160
     *
161
     * @private
162
     * @param {Object} child field instance
163
     * @returns {Number} index
164
     */
165
    var findIndex = function (child) {
166
      for (var i = 0; i < children.length; i++) {
167
        if (children[i] === child) {
168
          return i;
169
        }
170
      }
171
    };
172
 
173
    /**
174
     * Get the singular form of the items added in the list.
175
     *
176
     * @public
177
     * @returns {String} The entity type
178
     */
179
    self.getEntity = function () {
180
      return (field.entity === undefined ? 'item' : field.entity);
181
    };
182
 
183
    /**
184
     * Adds a new list item and child field at the end of the list
185
     *
186
     * @public
187
     * @param {*} [paramsOverride] Override params using this value.
188
     * @returns {Boolean}
189
     */
190
    self.addItem = function (paramsOverride) {
191
      var id = children.length;
192
      if (field.max === id) {
193
        return false;
194
      }
195
 
196
      var child = addItem(id, paramsOverride);
197
      self.widget.addItem(child, id);
198
 
199
      if (!passReadyCallbacks) {
200
        // Run collected ready callbacks
201
        for (var i = 0; i < readyCallbacks.length; i++) {
202
          readyCallbacks[i]();
203
        }
204
        readyCallbacks = []; // Reset
205
      }
206
      self.trigger('addedItem', child);
207
 
208
      return true;
209
    };
210
 
211
    /**
212
     * Removes the list item at the given index.
213
     *
214
     * @public
215
     * @param {Number} index
216
     */
217
    self.removeItem = function (index) {
218
      // Remove child field
219
      children[index].remove();
220
      children.splice(index, 1);
221
 
222
      if (parameters !== undefined) {
223
        // Clean up parameters
224
        parameters.splice(index, 1);
225
        if (!parameters.length) {
226
          // Create new parameters for list
227
          parameters = undefined;
228
          setValue(field);
229
        }
230
      }
231
      self.trigger('removedItem', index);
232
 
233
      // Ensure that collapsed state is set according to remaining items
234
      handleGroupCollapsedStateChanged();
235
    };
236
 
237
    /**
238
     * Removes all items.
239
     * This is useful if a widget wants to reset the list.
240
     *
241
     * @public
242
     */
243
    self.removeAllItems = function () {
244
      if (parameters === undefined) {
245
        return;
246
      }
247
 
248
      // Remove child fields
249
      for (var i = 0; i < children.length; i++) {
250
        children[i].remove();
251
      }
252
      children = [];
253
 
254
      // Clean up parameters
255
      parameters = undefined;
256
      setValue(field);
257
    };
258
 
259
    /**
260
     * Change the order of the items in the list.
261
     * Be aware that this may change the index of other existing items.
262
     *
263
     * @public
264
     * @param {Number} currentIndex
265
     * @param {Number} newIndex
266
     */
267
    self.moveItem = function (currentIndex, newIndex) {
268
      // Update child fields
269
      var child = children.splice(currentIndex, 1);
270
      children.splice(newIndex, 0, child[0]);
271
 
272
      // Update parameters
273
      if (parameters) {
274
        var params = parameters.splice(currentIndex, 1);
275
        parameters.splice(newIndex, 0, params[0]);
276
      }
277
    };
278
 
279
    /**
280
     * Toggle the collapsed state of all group items in list.
281
     * @param {boolean|undefined} [shouldBeCollapsed] If set explicitly, true to collapse all, false to expand.
282
     * @returns {boolean} New state or undefined if unclear.
283
     */
284
    this.toggleItemCollapsed = (shouldBeCollapsed) => {
285
      if (typeof shouldBeCollapsed !== 'boolean') {
286
        shouldBeCollapsed = children.some((child) => child.isExpanded());
287
      }
288
 
289
      this.forEachChild((child) => {
290
        if (!(child instanceof H5PEditor.Group)) {
291
          return;
292
        }
293
 
294
        if (shouldBeCollapsed) {
295
          const valid = child.collapse();
296
 
297
          if (!valid) {
298
            this.trigger('cannotCollapseAll');
299
          }
300
        }
301
        else {
302
          child.expand();
303
        }
304
      });
305
 
306
      /*
307
       * Return state could be omitted, because the success of collapsing or
308
       * expanding is not checked for. It's good style for a toggle function
309
       * though.
310
       */
311
      return shouldBeCollapsed;
312
    };
313
 
314
    /**
315
     * Allows ancestors and widgets to do stuff with our children.
316
     *
317
     * @public
318
     * @param {Function} task
319
     */
320
    self.forEachChild = function (task) {
321
      for (var i = 0; i < children.length; i++) {
322
        task(children[i], i);
323
      }
324
    };
325
 
326
    /**
327
     * Collect callback to run when the editor is ready. If this item isn't
328
     * ready yet, jusy pass them on to the parent item.
329
     *
330
     * @public
331
     * @param {Function} ready
332
     */
333
    self.ready = function (ready) {
334
      if (passReadyCallbacks) {
335
        parent.ready(ready);
336
      }
337
      else {
338
        readyCallbacks.push(ready);
339
      }
340
    };
341
 
342
    /**
343
     * Make sure that this field and all child fields are valid.
344
     *
345
     * @public
346
     * @returns {Boolean}
347
     */
348
    self.validate = function () {
349
      var self = this;
350
      var valid = true;
351
 
352
      // Remove old error messages
353
      self.clearErrors();
354
 
355
      // Make sure child fields are valid
356
      for (var i = 0; i < children.length; i++) {
357
        if (children[i].validate() === false) {
358
          valid = false;
359
        }
360
      }
361
 
362
      // Validate our self
363
      if (field.max !== undefined && field.max > 0 &&
364
          children !== undefined && children.length > field.max) {
365
        // Invalid, more parameters than max allowed.
366
        valid = false;
367
        self.setError(
368
          H5PEditor.t('core', 'listExceedsMax', { ':max': field.max })
369
        );
370
      }
371
      if (field.min !== undefined && field.min > 0 &&
372
          (children === undefined || children.length < field.min)) {
373
        // Invalid, less parameters than min allowed.
374
        valid = false;
375
        self.setError(
376
          H5PEditor.t('core', 'listBelowMin', { ':min': field.min })
377
        );
378
      }
379
 
380
      return valid;
381
    };
382
 
383
    self.getImportance = function () {
384
      if (field.importance !== undefined) {
385
        return H5PEditor.createImportance(field.importance);
386
      }
387
      else if (field.field.importance !== undefined) {
388
        return H5PEditor.createImportance(field.field.importance);
389
      }
390
      else {
391
        return '';
392
      }
393
    };
394
 
395
    /**
396
     * Creates a copy of the current valid value. A copy is created to avoid
397
     * mistakes like directly editing the parameter values, which will cause
398
     * inconsistencies between the parameters and the editor widgets.
399
     *
400
     * @public
401
     * @returns {Array}
402
     */
403
    self.getValue = function () {
404
      return (
405
        parameters === undefined ? parameters : $.extend(true, [], parameters)
406
      );
407
    };
408
 
409
    /**
410
     * Get a copy of the field semantics used by this list to create rows.
411
     * @return {Object}
412
     */
413
    self.getField = function () {
414
      return $.extend(true, {}, field.field);
415
    };
416
 
417
    // Start the party!
418
    init();
419
  }
420
 
421
  // Extends the semantics structure
422
  List.prototype = Object.create(H5PEditor.SemanticStructure.prototype);
423
  List.prototype.constructor = List;
424
 
425
  return List;
426
})(H5P.jQuery);
427
 
428
// Register widget
429
H5PEditor.widgets.list = H5PEditor.List;