Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
/* global ns */
2
/**
3
 * Create a group of fields.
4
 *
5
 * @param {mixed} parent
6
 * @param {object} field
7
 * @param {mixed} params
8
 * @param {function} setValue
9
 * @returns {ns.Group}
10
 */
11
ns.Group = function (parent, field, params, setValue) {
12
  // Support for events
13
  H5P.EventDispatcher.call(this);
14
 
15
  if (field.label === undefined) {
16
    field.label = field.name;
17
  }
18
  else if (field.label === 0) {
19
    field.label = '';
20
  }
21
 
22
  this.parent = parent;
23
  this.passReadies = true;
24
  this.params = params;
25
  this.setValue = setValue;
26
  this.library = parent.library + '/' + field.name;
27
  this.expandedState = false;
28
 
29
  if (field.deprecated !== undefined && field.deprecated) {
30
    this.field = H5P.cloneObject(field, true);
31
    var empties = 0;
32
    for (var i = 0; i < this.field.fields.length; i++) {
33
      var f = this.field.fields[i];
34
      if (params !== undefined && params[f.name] === '') {
35
        delete params[f.name];
36
      }
37
      if (params === undefined || params[f.name] === undefined) {
38
        f.widget = 'none';
39
        empties++;
40
      }
41
    }
42
    if (i === empties) {
43
      this.field.fields = [];
44
    }
45
  }
46
  else {
47
    this.field = field;
48
  }
49
 
50
  if (this.field.optional === true) {
51
    // If this field is optional, make sure child fields are as well
52
    for (var j = 0; j < this.field.fields.length; j++) {
53
      this.field.fields[j].optional = true;
54
    }
55
  }
56
};
57
 
58
// Extends the event dispatcher
59
ns.Group.prototype = Object.create(H5P.EventDispatcher.prototype);
60
ns.Group.prototype.constructor = ns.Group;
61
 
62
/**
63
 * Append group to its wrapper.
64
 *
65
 * @param {jQuery} $wrapper
66
 * @returns {undefined}
67
 */
68
ns.Group.prototype.appendTo = function ($wrapper) {
69
  var that = this;
70
 
71
  if (this.field.fields.length === 0) {
72
    // No fields or all are deprecated
73
    this.setValue(this.field);
74
    return;
75
  }
76
 
77
  // Add fieldset wrapper for group
78
  this.$group = ns.$('<fieldset/>', {
79
    'class': 'field group ' + H5PEditor.createImportance(this.field.importance) + ' field-name-' + this.field.name,
80
    appendTo: $wrapper
81
  });
82
 
83
  // Add title expand/collapse button
84
  this.$title = ns.$('<div/>', {
85
    'class': 'title',
86
    'aria-expanded': 'false',
87
    title: ns.t('core', 'expandCollapse'),
88
    role: 'button',
89
    tabIndex: 0,
90
    on: {
91
      click: function () {
92
        that.toggle();
93
      },
94
      keypress: function (event) {
95
        if ((event.charCode || event.keyCode) === 32) {
96
          that.toggle();
97
          event.preventDefault();
98
        }
99
      }
100
    },
101
    appendTo: this.$group
102
  });
103
 
104
  // Add content container
105
  this.$content = ns.$('<div/>', {
106
    'class': 'content',
107
    appendTo: this.$group
108
  });
109
 
110
  if (this.hasSingleChild() && !this.isSubContent()) {
111
    this.$content.addClass('h5peditor-single');
112
    this.children = [];
113
    var field = this.field.fields[0];
114
    var widget = field.widget === undefined ? field.type : field.widget;
115
    this.children[0] = new ns.widgets[widget](this, field, this.params, function (field, value) {
116
      that.setValue(that.field, value);
117
    });
118
    this.children[0].appendTo(this.$content);
119
  }
120
  else {
121
    if (this.params === undefined) {
122
      this.params = {};
123
      this.setValue(this.field, this.params);
124
    }
125
 
126
    this.params = this.initSubContent(this.params);
127
 
128
    ns.processSemanticsChunk(this.field.fields, this.params, this.$content, this);
129
  }
130
 
131
  // Set summary
132
  this.findSummary();
133
 
134
  // Check if group should be expanded.
135
  // Default is to be collapsed unless explicity defined in semantics by optional attribute expanded
136
  if (this.field.expanded === true) {
137
    this.expand();
138
  }
139
};
140
 
141
/**
142
 * Return whether this group is Sub Content
143
 *
144
 * @private
145
 * @return {boolean}
146
 */
147
ns.Group.prototype.hasSingleChild = function () {
148
  return this.field.fields.length === 1;
149
};
150
 
151
/**
152
 * Add generated 'subContentId' attribute, if group is "sub content (library-like embedded structure)"
153
 *
154
 * @param {object} params
155
 *
156
 * @private
157
 * @return {object}
158
 */
159
ns.Group.prototype.initSubContent = function (params) {
160
  // If group contains library-like sub content that needs UUIDs
161
  if (this.isSubContent()) {
162
    params['subContentId'] = params['subContentId'] || H5P.createUUID();
163
  }
164
 
165
  return params;
166
};
167
 
168
/**
169
 * Return whether this group is Sub Content
170
 *
171
 * @private
172
 * @return {boolean}
173
 */
174
ns.Group.prototype.isSubContent = function () {
175
  return this.field.isSubContent === true;
176
};
177
 
178
/**
179
 * Toggle expand/collapse for the given group.
180
 */
181
ns.Group.prototype.toggle = function () {
182
  if (this.preventToggle) {
183
    this.preventToggle = false;
184
    return;
185
  }
186
 
187
  if (this.isExpanded()) {
188
    this.collapse();
189
  }
190
  else {
191
    this.expand();
192
  }
193
};
194
 
195
/**
196
 * Expand the given group.
197
 */
198
ns.Group.prototype.expand = function () {
199
  this.$title.attr('aria-expanded', 'true');
200
  // Set timeout is necessary because aria-expanded status is not announced
201
  // when the :before element changes content because Firefox
202
  // re-creates the accessible element..
203
  // @see https://github.com/nvaccess/nvda/issues/8341
204
  // Should be fixeed by Firefox 70 (https://bugzilla.mozilla.org/show_bug.cgi?id=686400)
205
  setTimeout(function () {
206
    this.expandedState = true;
207
    this.trigger('expanded');
208
    this.$group.addClass('expanded');
209
  }.bind(this), 100);
210
};
211
 
212
/**
213
 * Collapse the given group (if valid).
214
 *
215
 * @returns {boolean} True, if the group is valid. False, otherwise.
216
 */
217
ns.Group.prototype.collapse = function () {
218
  // Do not collapse before valid!
219
  var valid = true;
220
  for (var i = 0; i < this.children.length; i++) {
221
    if (this.children[i].validate() === false) {
222
      valid = false;
223
    }
224
  }
225
  if (valid) {
226
    this.$title.attr('aria-expanded', 'false');
227
    // Set timeout is necessary because aria-expanded status is not announced
228
    // when the :before element changes content because Firefox
229
    // re-creates the accessible element..
230
    // @see https://github.com/nvaccess/nvda/issues/8341
231
    // Should be fixeed by Firefox 70 (https://bugzilla.mozilla.org/show_bug.cgi?id=686400)
232
    setTimeout(function () {
233
      this.expandedState = false;
234
      this.trigger('collapsed');
235
      this.$group.removeClass('expanded');
236
    }.bind(this), 100);
237
  }
238
 
239
  return valid;
240
};
241
 
242
/**
243
 * Determine if the group is expanded.
244
 * @returns {boolean} True, if the group is expanded. False, otherwise.
245
 */
246
ns.Group.prototype.isExpanded = function () {
247
  return this.expandedState;
248
};
249
 
250
/**
251
 * Find summary to display in group header.
252
 */
253
ns.Group.prototype.findSummary = function () {
254
  var that = this;
255
  var summary;
256
  for (var j = 0; j < this.children.length; j++) {
257
    var child = this.children[j];
258
    if (child.field === undefined) {
259
      continue;
260
    }
261
    var params = (that.hasSingleChild() && !that.isSubContent()) ? this.params : this.params[child.field.name];
262
    var widget = ns.getWidgetName(child.field);
263
 
264
    if (widget === 'text' || widget === 'html') {
265
      if (params !== undefined && params !== '') {
266
        summary = params.replace(/(<([^>]+)>)/ig, "");
267
      }
268
 
269
      child.$input.change(function () {
270
        var params = (that.hasSingleChild() && !that.isSubContent()) ? that.params : that.params[child.field.name];
271
        if (params !== undefined && params !== '') {
272
          that.setSummary(params.replace(/(<([^>]+)>)/ig, ""));
273
        }
274
      });
275
      break;
276
    }
277
    else if (widget === 'library') {
278
      let lastLib;
279
      if (child.params !== undefined) {
280
        summary = child.$select.children(':selected').text();
281
        if (child.params.metadata && child.params.metadata.title) {
282
          // The given title usually makes more sense than the type name
283
          summary = child.params.metadata.title + (!child.libraries || (child.libraries.length > 1 && child.params.metadata.title.indexOf(summary) === -1) ? ' (' +  summary + ')' : '');
284
        }
285
        else if (!child.params.library) {
286
          // Nothing selected
287
          summary = that.field.label;
288
        }
289
      }
290
      const setSummary = function () {
291
        if (child.params && child.params.metadata && child.params.metadata.title) {
292
          // The given title usually makes more sense than the type name
293
          that.setSummary(child.params.metadata.title + (child.libraries.length > 1 && child.params.metadata.title.indexOf(lastLib.title) === -1 ? ' (' +  lastLib.title + ')' : ''));
294
        }
295
        else {
296
          that.setSummary(lastLib ? lastLib.title : that.field.label);
297
        }
298
      };
299
      if (child.metadataForm) {
300
        child.metadataForm.on('titlechange', setSummary);
301
      }
302
      child.change(function (library) {
303
        lastLib = library;
304
        setSummary();
305
 
306
        if (child.metadataForm) {
307
          // Update summary when metadata title changes
308
          child.metadataForm.off('titlechange', setSummary);
309
          child.metadataForm.on('titlechange', setSummary);
310
        }
311
      });
312
      break;
313
    }
314
  }
315
  this.setSummary(summary);
316
};
317
 
318
/**
319
 * Set the given group summary.
320
 *
321
 * @param {string} summary
322
 * @returns {undefined}
323
 */
324
ns.Group.prototype.setSummary = function (summary) {
325
  var summaryText;
326
 
327
  // Parse html
328
  var summaryTextNode = ns.$.parseHTML(summary);
329
 
330
  if (summaryTextNode !== null && summaryTextNode.length) {
331
    summaryText = summaryTextNode[0].nodeValue;
332
  }
333
 
334
  // Make it possible for parent to monitor summary changes
335
  this.trigger('summary', summaryText);
336
 
337
  if (summaryText !== undefined) {
338
    summaryText = (summaryText.length > 48 ? summaryText.substr(0, 45) + '...' : summaryText);
339
  }
340
  else {
341
    summaryText = this.field.label;
342
  }
343
 
344
  this.$title.text(summaryText);
345
};
346
 
347
/**
348
 * Validate all children.
349
 */
350
ns.Group.prototype.validate = function () {
351
  var valid = true;
352
 
353
  if (this.children !== undefined) {
354
    for (var i = 0; i < this.children.length; i++) {
355
      if (this.children[i].validate() === false) {
356
        valid = false;
357
      }
358
    }
359
  }
360
 
361
  return valid;
362
};
363
 
364
/**
365
 * Allows ancestors and widgets to do stuff with our children.
366
 *
367
 * @public
368
 * @param {Function} task
369
 */
370
ns.Group.prototype.forEachChild = function (task) {
371
  for (var i = 0; i < this.children.length; i++) {
372
    task(this.children[i], i);
373
  }
374
};
375
 
376
/**
377
 * Collect functions to execute once the tree is complete.
378
 *
379
 * @param {function} ready
380
 * @returns {undefined}
381
 */
382
ns.Group.prototype.ready = function (ready) {
383
  this.parent.ready(ready);
384
};
385
 
386
/**
387
 * Remove this item.
388
 */
389
ns.Group.prototype.remove = function () {
390
  if (this.$group !== undefined) {
391
    ns.removeChildren(this.children);
392
    this.$group.remove();
393
  }
394
};
395
 
396
/**
397
 * Get a copy of the fields semantics used by this group.
398
 * @return {Array}
399
 */
400
ns.Group.prototype.getFields = function () {
401
  return H5PEditor.$.extend(true, [], this.field.fields);
402
};
403
 
404
/**
405
 * When someone from the outside wants to set a value.
406
 *
407
 * @param {Object} value
408
 */
409
ns.Group.prototype.forceValue = function (value) {
410
  for (let i = 0; i < this.children.length; i++) {
411
    this.children[i].forceValue(value[this.children[i].field.name]);
412
  }
413
};
414
 
415
// Tell the editor what widget we are.
416
ns.widgets.group = ns.Group;