Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
H5PEditor.MetadataForm = (function (EventDispatcher, $, metadataSemantics) {
2
 
3
  /**
4
   * Create a metadata form popup that attaches to other fields
5
   *
6
   * @class
7
   * @param {Object} parent
8
   * @param {Object} params
9
   * @param {$} $container
10
   * @param {boolean} [hasExtraTitleField=true]
11
   * @param {boolean} [populateTitleField=false]
12
   */
13
  function MetadataForm(parent, params, $container, hasExtraTitleField, populateTitleField) {
14
    var self = this;
15
 
16
    // Initialize event inheritance
17
    EventDispatcher.call(self);
18
 
19
    // We're a parent, so we must handle readies callbacks
20
    self.passReadies = true;
21
    // (but in a special way since we process multiple semantics chunks)
22
 
23
    // Set current author as default in semantics
24
    const currentUserName = (H5PIntegration.user && H5PIntegration.user.name) ? H5PIntegration.user.name : undefined;
25
    if (currentUserName) {
26
      // Set current user as default for "changed by":
27
      findField('changes').field.fields[1].default = currentUserName;
28
      findField('authors').field.fields[0].default = currentUserName;
29
    }
30
 
31
    /**
32
     * Open the popup
33
     * @param {Object} event
34
     */
35
    const openPopup = function (event) {
36
      var offset = event.clientY - 150;
37
 
38
      $('html,body').css('height', '100%');
39
      $('.h5peditor').append($overlay);
40
      $wrapper.css('margin-top', (offset > 20 ? offset : 20) + 'px');
41
 
42
      // Focus title field
43
      titleField.$input.focus();
44
    };
45
 
46
    /**
47
     * Close the popup
48
     */
49
    const closePopup = function () {
50
      $('html,body').css('height', '');
51
      $overlay.detach();
52
    };
53
 
54
    /**
55
     * @private
56
     */
57
    const handleSaveButtonClick = function () {
58
      // If license selected, and there's no authors, add the current one
59
      if (params.license !== 'U' && params.authors.length === 0) {
60
        metadataAuthorWidget.addDefaultAuthor(currentUserName, 'Author');
61
      }
62
 
63
      closePopup();
64
    };
65
 
66
    /**
67
     * @private
68
     */
69
    const setupTitleField = function () {
70
      // Select title field text on click
71
      titleField.$input.click(function () {
72
        if (this.selectionStart === 0 && this.selectionEnd === this.value.length) {
73
          return;
74
        }
75
        this.select();
76
        this.setSelectionRange(0, this.value.length); // Safari mobile fix
77
      });
78
 
79
      // Set the default title
80
      if (!params.title && populateTitleField) {
81
        titleField.$input.val(H5PEditor.LibraryListCache.getDefaultTitle(parent.currentLibrary)).change();
82
      }
83
 
84
      titleField.$input.change(function () {
85
        self.trigger('titlechange');
86
      });
87
    };
88
 
89
    /**
90
     * @private
91
     */
92
    const setupLicenseField = function () {
93
      // Locate license and version selectors
94
      var licenseField = H5PEditor.findField('license', self);
95
      var versionField = H5PEditor.findField('licenseVersion', self);
96
      versionField.field.optional = true; // Avoid any error messages
97
 
98
      // Listen for changes to license
99
      licenseField.changes.push(function (value) {
100
        // Find versions for selected value
101
        function getNestedOptions(options) {
102
          var flattenedOptions = [];
103
          options.forEach(function (option) {
104
            if (option.type === 'optgroup') {
105
              flattenedOptions = flattenedOptions.concat(getNestedOptions(option.options));
106
            }
107
            else {
108
              flattenedOptions.push(option);
109
            }
110
          });
111
          return flattenedOptions;
112
        }
113
 
114
        var nestedOptions = getNestedOptions(licenseField.field.options);
115
        var option = find(nestedOptions, 'value', value);
116
        var versions = (option) ? option.versions : undefined;
117
 
118
        versionField.$select.prop('disabled', versions === undefined);
119
        if (versions === undefined) {
120
          // If no versions add default
121
          versions = [{
122
            value: '-',
123
            label: '-'
124
          }];
125
        }
126
 
127
        // Find default selected version
128
        var selected = (params.license === value && params ? params.licenseVersion : versions[0].value);
129
 
130
        // Update versions selector
131
        versionField.$select.html(H5PEditor.Select.createOptionsHtml(versions, selected)).change();
132
      });
133
 
134
      // Trigger update straight away
135
      licenseField.changes[licenseField.changes.length - 1](params.license);
136
    };
137
 
138
    /**
139
     * Toggle visibility of accessibility title field
140
     */
141
    const toggleA11yTitle = function () {
142
      const a11yTitleField = H5PEditor.findField('a11yTitle', self);
143
      const wrapper = a11yTitleField.$item[0];
144
      self.isA11yExpanded = !self.isA11yExpanded;
145
      if (self.isA11yExpanded) {
146
        wrapper.classList.remove('hidden');
147
      }
148
      wrapper.classList[self.isA11yExpanded ? 'remove' : 'add']('hide');
149
      self.a11yTitleText.innerHTML = self.isA11yExpanded
150
        ? t('a11yTitleHideLabel') : t('a11yTitleShowLabel');
151
      wrapper.setAttribute('aria-hidden', !self.isA11yExpanded);
152
    }
153
 
154
    /**
155
     * Setup functionality of accessibility title field and button
156
     */
157
    const setupA11yTitleField = function () {
158
      const titleField = H5PEditor.findField('title', self);
159
      const a11yTitleField = H5PEditor.findField('a11yTitle', self);
160
      const label = titleField.$item[0].querySelector('label.h5peditor-label-wrapper');
161
      const toggleA11yTitleButton = document.createElement('button');
162
      const a11yTitleText = document.createElement('span');
163
      a11yTitleText.classList.add('h5p-a11y-title-text');
164
      a11yTitleText.innerHTML = t('a11yTitleShowLabel');
165
      self.a11yTitleText = a11yTitleText;
166
 
167
      toggleA11yTitleButton.classList.add('a11y-title-toggle');
168
      toggleA11yTitleButton.appendChild(a11yTitleText);
169
 
170
      a11yTitleField.$item[0].classList.add('hide');
171
      a11yTitleField.$item[0].setAttribute('aria-hidden', true);
172
      a11yTitleField.$item[0].addEventListener('transitionend', function () {
173
        // Hide when transition is done
174
        if (!self.isA11yExpanded) {
175
          a11yTitleField.$item[0].classList.add('hidden');
176
        }
177
      });
178
 
179
      toggleA11yTitleButton.addEventListener('click', toggleA11yTitle);
180
      self.togglea11yTitleButton = toggleA11yTitleButton;
181
      self.isA11yExpanded = false;
182
 
183
      label.appendChild(toggleA11yTitleButton);
184
    }
185
 
186
    /**
187
     * @private
188
     */
189
    const setupSourceField = function () {
190
      // Make sure the source field is empty or starts with a protocol
191
      const sourceField = H5PEditor.findField('source', self);
192
      sourceField.$item.on('change', function () {
193
        const sourceInput = $(this).find('input.h5peditor-text');
194
        if (sourceInput.val().trim() !== '' &&
195
          sourceInput.val().indexOf('https://') !== 0 &&
196
          sourceInput.val().indexOf('http://') !== 0
197
        ) {
198
          sourceInput.val('http://' + sourceInput.val()).trigger('change');
199
        }
200
      });
201
    };
202
 
203
    const $overlay = $('<div>', {
204
      'class': 'overlay h5p-metadata-popup-overlay'
205
    });
206
 
207
    const $wrapper = $(
208
      '<div class="h5p-metadata-wrapper">' +
209
        '<div class="h5p-metadata-header">' +
210
          '<div class="h5p-title-container">' +
211
            '<h2>' + t('metadataSharingAndLicensingInfo') + '</h2>' +
212
            '<p>' + t('fillInTheFieldsBelow') + '</p>' +
213
          '</div>' +
214
          '<div class="metadata-button-wrapper">' +
215
            '<button href="#" class="h5p-metadata-button h5p-save">' + t('saveMetadata') + '</button>' +
216
          '</div>' +
217
        '</div>' +
218
      '</div>');
219
 
220
    // Handle click on save button
221
    $wrapper.find('.h5p-save').click(handleSaveButtonClick);
222
 
223
    const $fieldsWrapper = $('<div/>', {
224
      'class': 'h5p-metadata-fields-wrapper',
225
      appendTo: $wrapper
226
    });
227
 
228
    $wrapper.appendTo($overlay);
229
 
230
    const $button = $(
231
      '<div role="button" tabindex="0" class="h5p-metadata-button-wrapper">' +
232
        '<div class="h5p-metadata-button-tip"></div>' +
233
        '<div class="h5p-metadata-toggler">' + t('metadata') + '</div>' +
234
      '</div>')
235
      .click(openPopup)
236
      .keydown(function (event) {
237
        if (event.which == 13 || event.which == 32) {
238
          openPopup(event);
239
        }
240
      });
241
 
242
    /**
243
     * Handle ready callbacks from children
244
     * @param {function} callback
245
     */
246
    self.ready = function (callback) {
247
      if (parent.passReadies) {
248
        parent.ready(callback); // Pass to parent, run when all editor fields are ready
249
      }
250
      else {
251
        readies.push(callback); // Run by processSemanticsChunk when all fields are done
252
      }
253
    };
254
 
255
    /**
256
     * @param {$} $container
257
     */
258
    self.appendTo = function ($container) {
259
      $wrapper.appendTo($container);
260
    };
261
 
262
    /**
263
     * @param {$} $element
264
     */
265
    self.appendButtonTo = function ($item) {
266
      $button.appendTo($item.children('.h5peditor-label-wrapper').wrap('<div class="h5p-editor-flex-wrapper"/>').parent());
267
    };
268
 
269
    /**
270
     * @return {Object} The extra title field instance
271
     */
272
    self.getExtraTitleField = function () {
273
      return hasExtraTitleField ? extraTitle : undefined;
274
    };
275
 
276
    // Prepare semantics
277
    const semantics = [];
278
    if (hasExtraTitleField) {
279
      semantics.push(getExtraTitleFieldSemantics());
280
    }
281
    semantics.push(findField('title'));
282
    semantics.push(findField('a11yTitle'));
283
    semantics.push(findField('license'));
284
    semantics.push(findField('licenseVersion'));
285
    semantics.push(findField('yearFrom'));
286
    semantics.push(findField('yearTo'));
287
    semantics.push(findField('source'));
288
 
289
    // Collect readies callbacks
290
    const readies = [];
291
 
292
    // Generate the form
293
    H5PEditor.processSemanticsChunk(semantics, params, $fieldsWrapper, self);
294
 
295
    // Keep track of children between generating
296
    let children = self.children;
297
 
298
    // Extra processing of fields
299
    const titleField = H5PEditor.findField('title', self);
300
    setupTitleField();
301
    setupLicenseField();
302
    setupSourceField();
303
    setupA11yTitleField();
304
 
305
    // Append the metadata author list widget (Not the same type of widgets as the rest of editor fields)
306
    const metadataAuthorWidget = H5PEditor.metadataAuthorWidget(findField('authors').field.fields, params, $fieldsWrapper, self);
307
    children = children.concat(self.children);
308
 
309
    // TODO: Ideally these widgets should behave the same way as the reset of
310
    // the editor widgets and be created through a single call to processSemanticsChunk().
311
 
312
    // Append the License Extras field
313
    H5PEditor.processSemanticsChunk([findField('licenseExtras')], params, $fieldsWrapper, self);
314
    children = children.concat(self.children);
315
 
316
    // Append the metadata changelog widget (Not the same type of widgets as the rest of editor fields)
317
    H5PEditor.metadataChangelogWidget([findField('changes').field], params, $fieldsWrapper, self);
318
    children = children.concat(self.children);
319
 
320
    // Append the Additional information group
321
    var additionals = new H5PEditor.widgets.group(self, {
322
      name: 'additionals',
323
      label: 'Additional information', // TODO: l10n ?
324
      fields: [
325
        findField('authorComments')
326
      ]
327
    }, params.authorComments, function (field, value) {
328
      params.authorComments = value;
329
    });
330
    additionals.appendTo($fieldsWrapper);
331
 
332
    // Add the final child
333
    self.children = children.concat([additionals]);
334
 
335
    let extraTitle;
336
    if (hasExtraTitleField) {
337
      // Append to correct place in DOM
338
      extraTitle = H5PEditor.findField('extraTitle', self);
339
      extraTitle.$item.appendTo($container);
340
      self.appendButtonTo(extraTitle.$item);
341
 
342
      linkFields(titleField, extraTitle);
343
    }
344
 
345
    if (!parent.passReadies) {
346
      // Run readies callbacks
347
      for (let i = 0; i < readies.length; i++) {
348
        readies[i]();
349
      }
350
    }
351
  }
352
 
353
  // Extends the event dispatcher
354
  MetadataForm.prototype = Object.create(EventDispatcher.prototype);
355
  MetadataForm.prototype.constructor = MetadataForm;
356
 
357
  MetadataForm.createLegacyForm = function (params, $container) {
358
    const legacyForm = {
359
      passReadies: false,
360
      getExtraTitleField: function () {
361
        return H5PEditor.findField('title', legacyForm);
362
      }
363
    };
364
 
365
    // Generate the form
366
    const field = getExtraTitleFieldSemantics();
367
    field.name = 'title';
368
    H5PEditor.processSemanticsChunk([field], params, $container, legacyForm);
369
 
370
    return legacyForm;
371
  };
372
 
373
  /**
374
   * @return {Object}
375
   */
376
  const getExtraTitleFieldSemantics = function () {
377
    const extraTitle = JSON.parse(JSON.stringify(findField('title'))); // Clone
378
    extraTitle.name = 'extraTitle'; // Change name to avoid conflicts
379
    extraTitle.description = t('usedForSearchingReportsAndCopyrightInformation');
380
    delete extraTitle.placeholder;
381
    return extraTitle;
382
  };
383
 
384
  /**
385
   * @param {string} key
386
   * @return {string}
387
   */
388
  const t = function (key) {
389
    return H5PEditor.t('core', key);
390
  };
391
 
392
  /**
393
   * Find metdata semantics field.
394
   * @param {string} name
395
   * @return {Object}
396
   */
397
  const findField = function (name) {
398
    for (let i = 0; i < metadataSemantics.length; i++) {
399
      if (metadataSemantics[i].name === name) {
400
        return metadataSemantics[i];
401
      }
402
    }
403
  };
404
 
405
  /**
406
   * Help find object in list with the given property value.
407
   *
408
   * @param {Object[]} list of objects to search through
409
   * @param {string} property to look for
410
   * @param {string} value to match property value against
411
   * @return {Object}
412
   */
413
  const find = function (list, property, value) {
414
    var properties = property.split('.');
415
 
416
    for (var i = 0; i < list.length; i++) {
417
      var objProp = list[i];
418
 
419
      for (var j = 0; j < properties.length; j++) {
420
        objProp = objProp[properties[j]];
421
      }
422
 
423
      if (objProp === value) {
424
        return list[i];
425
      }
426
    }
427
  };
428
 
429
  /**
430
   * Automatically sync all the given fields when one value changes.
431
   * Note: Currently only supports H5PEditor.Text field widgets.
432
   *
433
   * @private
434
   * @param {...*} var_args
435
   */
436
  const linkFields = function (var_args) {
437
    const fields = arguments;
438
 
439
    let preventLoop;
440
 
441
    /**
442
     * Change event handler for all fields
443
     * @private
444
     * @param {*} value
445
     */
446
    const updateAllFields = function (value) {
447
      if (preventLoop || value === undefined) {
448
        return;
449
      }
450
 
451
      // Do not run updates for this update
452
      preventLoop = true;
453
 
454
      // Apply value to all fields
455
      for (let i = 0; i < fields.length; i++) {
456
        fields[i].$input.val(value).change();
457
      }
458
 
459
      // Done
460
      preventLoop = false;
461
    };
462
 
463
    // Add change event listeners
464
    for (let i = 0; i < fields.length; i++) {
465
      fields[i].change(updateAllFields);
466
    }
467
 
468
    // Use initial value from first field
469
    if (fields[0].value !== undefined) {
470
      const escaper = document.createElement('div');
471
      escaper.innerHTML = fields[0].value;
472
      updateAllFields(escaper.innerText);
473
    }
474
  };
475
 
476
  return MetadataForm;
477
})(H5P.EventDispatcher, H5P.jQuery, H5PEditor.metadataSemantics);