MDL-62449 scorm: Move related data to data in the privacy export
[moodle.git] / lib / editor / atto / plugins / media / yui / src / button / js / button.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /*
17  * @package    atto_media
18  * @copyright  2013 Damyon Wiese  <damyon@moodle.com>
19  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
20  */
22 /**
23  * @module moodle-atto_media-button
24  */
26 /**
27  * Atto media selection tool.
28  *
29  * @namespace M.atto_media
30  * @class Button
31  * @extends M.editor_atto.EditorPlugin
32  */
34 var COMPONENTNAME = 'atto_media',
35     MEDIA_TYPES = {LINK: 'LINK', VIDEO: 'VIDEO', AUDIO: 'AUDIO'},
36     TRACK_KINDS = {
37         SUBTITLES: 'SUBTITLES',
38         CAPTIONS: 'CAPTIONS',
39         DESCRIPTIONS: 'DESCRIPTIONS',
40         CHAPTERS: 'CHAPTERS',
41         METADATA: 'METADATA'
42     },
43     CSS = {
44         SOURCE: 'atto_media_source',
45         TRACK: 'atto_media_track',
46         MEDIA_SOURCE: 'atto_media_media_source',
47         LINK_SOURCE: 'atto_media_link_source',
48         POSTER_SOURCE: 'atto_media_poster_source',
49         TRACK_SOURCE: 'atto_media_track_source',
50         DISPLAY_OPTIONS: 'atto_media_display_options',
51         NAME_INPUT: 'atto_media_name_entry',
52         URL_INPUT: 'atto_media_url_entry',
53         POSTER_SIZE: 'atto_media_poster_size',
54         LINK_SIZE: 'atto_media_link_size',
55         WIDTH_INPUT: 'atto_media_width_entry',
56         HEIGHT_INPUT: 'atto_media_height_entry',
57         TRACK_KIND_INPUT: 'atto_media_track_kind_entry',
58         TRACK_LABEL_INPUT: 'atto_media_track_label_entry',
59         TRACK_LANG_INPUT: 'atto_media_track_lang_entry',
60         TRACK_DEFAULT_SELECT: 'atto_media_track_default',
61         MEDIA_CONTROLS_TOGGLE: 'atto_media_controls',
62         MEDIA_AUTOPLAY_TOGGLE: 'atto_media_autoplay',
63         MEDIA_MUTE_TOGGLE: 'atto_media_mute',
64         MEDIA_LOOP_TOGGLE: 'atto_media_loop',
65         ADVANCED_SETTINGS: 'atto_media_advancedsettings',
66         LINK: MEDIA_TYPES.LINK.toLowerCase(),
67         VIDEO: MEDIA_TYPES.VIDEO.toLowerCase(),
68         AUDIO: MEDIA_TYPES.AUDIO.toLowerCase(),
69         TRACK_SUBTITLES: TRACK_KINDS.SUBTITLES.toLowerCase(),
70         TRACK_CAPTIONS: TRACK_KINDS.CAPTIONS.toLowerCase(),
71         TRACK_DESCRIPTIONS: TRACK_KINDS.DESCRIPTIONS.toLowerCase(),
72         TRACK_CHAPTERS: TRACK_KINDS.CHAPTERS.toLowerCase(),
73         TRACK_METADATA: TRACK_KINDS.METADATA.toLowerCase()
74     },
75     SELECTORS = {
76         SOURCE: '.' + CSS.SOURCE,
77         TRACK: '.' + CSS.TRACK,
78         MEDIA_SOURCE: '.' + CSS.MEDIA_SOURCE,
79         POSTER_SOURCE: '.' + CSS.POSTER_SOURCE,
80         TRACK_SOURCE: '.' + CSS.TRACK_SOURCE,
81         DISPLAY_OPTIONS: '.' + CSS.DISPLAY_OPTIONS,
82         NAME_INPUT: '.' + CSS.NAME_INPUT,
83         URL_INPUT: '.' + CSS.URL_INPUT,
84         POSTER_SIZE: '.' + CSS.POSTER_SIZE,
85         LINK_SIZE: '.' + CSS.LINK_SIZE,
86         WIDTH_INPUT: '.' + CSS.WIDTH_INPUT,
87         HEIGHT_INPUT: '.' + CSS.HEIGHT_INPUT,
88         TRACK_KIND_INPUT: '.' + CSS.TRACK_KIND_INPUT,
89         TRACK_LABEL_INPUT: '.' + CSS.TRACK_LABEL_INPUT,
90         TRACK_LANG_INPUT: '.' + CSS.TRACK_LANG_INPUT,
91         TRACK_DEFAULT_SELECT: '.' + CSS.TRACK_DEFAULT_SELECT,
92         MEDIA_CONTROLS_TOGGLE: '.' + CSS.MEDIA_CONTROLS_TOGGLE,
93         MEDIA_AUTOPLAY_TOGGLE: '.' + CSS.MEDIA_AUTOPLAY_TOGGLE,
94         MEDIA_MUTE_TOGGLE: '.' + CSS.MEDIA_MUTE_TOGGLE,
95         MEDIA_LOOP_TOGGLE: '.' + CSS.MEDIA_LOOP_TOGGLE,
96         ADVANCED_SETTINGS: '.' + CSS.ADVANCED_SETTINGS,
97         LINK_TAB: 'li[data-medium-type="' + CSS.LINK + '"]',
98         LINK_PANE: '.tab-pane[data-medium-type="' + CSS.LINK + '"]',
99         VIDEO_TAB: 'li[data-medium-type="' + CSS.VIDEO + '"]',
100         VIDEO_PANE: '.tab-pane[data-medium-type="' + CSS.VIDEO + '"]',
101         AUDIO_TAB: 'li[data-medium-type="' + CSS.AUDIO + '"]',
102         AUDIO_PANE: '.tab-pane[data-medium-type="' + CSS.AUDIO + '"]',
103         TRACK_SUBTITLES_TAB: 'li[data-track-kind="' + CSS.TRACK_SUBTITLES + '"]',
104         TRACK_SUBTITLES_PANE: '.tab-pane[data-track-kind="' + CSS.TRACK_SUBTITLES + '"]',
105         TRACK_CAPTIONS_TAB: 'li[data-track-kind="' + CSS.TRACK_CAPTIONS + '"]',
106         TRACK_CAPTIONS_PANE: '.tab-pane[data-track-kind="' + CSS.TRACK_CAPTIONS + '"]',
107         TRACK_DESCRIPTIONS_TAB: 'li[data-track-kind="' + CSS.TRACK_DESCRIPTIONS + '"]',
108         TRACK_DESCRIPTIONS_PANE: '.tab-pane[data-track-kind="' + CSS.TRACK_DESCRIPTIONS + '"]',
109         TRACK_CHAPTERS_TAB: 'li[data-track-kind="' + CSS.TRACK_CHAPTERS + '"]',
110         TRACK_CHAPTERS_PANE: '.tab-pane[data-track-kind="' + CSS.TRACK_CHAPTERS + '"]',
111         TRACK_METADATA_TAB: 'li[data-track-kind="' + CSS.TRACK_METADATA + '"]',
112         TRACK_METADATA_PANE: '.tab-pane[data-track-kind="' + CSS.TRACK_METADATA + '"]'
113     },
114     TEMPLATES = {
115         ROOT: '' +
116             '<form class="mform atto_form atto_media" id="{{elementid}}_atto_media_form">' +
117                 '<ul class="root nav nav-tabs" role="tablist">' +
118                     '<li data-medium-type="{{CSS.LINK}}" class="nav-item">' +
119                         '<a class="nav-link active" href="#{{elementid}}_{{CSS.LINK}}" role="tab" data-toggle="tab">' +
120                             '{{get_string "link" component}}' +
121                         '</a>' +
122                     '</li>' +
123                     '<li data-medium-type="{{CSS.VIDEO}}" class="nav-item">' +
124                         '<a class="nav-link" href="#{{elementid}}_{{CSS.VIDEO}}" role="tab" data-toggle="tab">' +
125                             '{{get_string "video" component}}' +
126                         '</a>' +
127                     '</li>' +
128                     '<li data-medium-type="{{CSS.AUDIO}}" class="nav-item">' +
129                         '<a class="nav-link" href="#{{elementid}}_{{CSS.AUDIO}}" role="tab" data-toggle="tab">' +
130                             '{{get_string "audio" component}}' +
131                         '</a>' +
132                     '</li>' +
133                 '</ul>' +
134                 '<div class="root tab-content">' +
135                     '<div data-medium-type="{{CSS.LINK}}" class="tab-pane active" id="{{elementid}}_{{CSS.LINK}}">' +
136                         '{{> tab_panes.link}}' +
137                     '</div>' +
138                     '<div data-medium-type="{{CSS.VIDEO}}" class="tab-pane" id="{{elementid}}_{{CSS.VIDEO}}">' +
139                         '{{> tab_panes.video}}' +
140                     '</div>' +
141                     '<div data-medium-type="{{CSS.AUDIO}}" class="tab-pane" id="{{elementid}}_{{CSS.AUDIO}}">' +
142                         '{{> tab_panes.audio}}' +
143                     '</div>' +
144                 '</div>' +
145                 '<div class="mdl-align">' +
146                     '<br/>' +
147                     '<button class="submit" type="submit">{{get_string "createmedia" component}}</button>' +
148                 '</div>' +
149             '</form>',
150         TAB_PANES: {
151             LINK: '' +
152                 '{{renderPartial "form_components.source" context=this id=CSS.LINK_SOURCE}}' +
153                 '<label>' +
154                     'Enter name' +
155                     '<input class="fullwidth {{CSS.NAME_INPUT}}" type="text" id="{{elementid}}_link_nameentry"' +
156                         'size="32" required="true"/>' +
157                 '</label>',
158             VIDEO: '' +
159                 '{{renderPartial "form_components.source" context=this id=CSS.MEDIA_SOURCE entersourcelabel="videosourcelabel"' +
160                     ' addcomponentlabel="addsource" multisource="true" addsourcehelp=helpStrings.addsource}}' +
161                 '<fieldset class="collapsible collapsed" id="{{elementid}}_video-display-options">' +
162                     '<input name="mform_isexpanded_{{elementid}}_video-display-options" type="hidden">' +
163                     '<legend class="ftoggler">{{get_string "displayoptions" component}}</legend>' +
164                     '<div class="fcontainer">' +
165                         '{{> form_components.display_options}}' +
166                     '</div>' +
167                 '</fieldset>' +
168                 '<fieldset class="collapsible collapsed" id="{{elementid}}_video-advanced-settings">' +
169                     '<input name="mform_isexpanded_{{elementid}}_video-advanced-settings" type="hidden">' +
170                     '<legend class="ftoggler">{{get_string "advancedsettings" component}}</legend>' +
171                     '<div class="fcontainer">' +
172                         '{{> form_components.advanced_settings}}' +
173                     '</div>' +
174                 '</fieldset>' +
175                 '<fieldset class="collapsible collapsed" id="{{elementid}}_video-tracks">' +
176                     '<input name="mform_isexpanded_{{elementid}}_video-tracks" type="hidden">' +
177                     '<legend class="ftoggler">{{get_string "tracks" component}} {{{helpStrings.tracks}}}</legend>' +
178                     '<div class="fcontainer">' +
179                         '{{renderPartial "form_components.track_tabs" context=this id=CSS.VIDEO}}' +
180                     '</div>' +
181                 '</fieldset>',
182             AUDIO: '' +
183                 '{{renderPartial "form_components.source" context=this id=CSS.MEDIA_SOURCE entersourcelabel="audiosourcelabel"' +
184                     ' addcomponentlabel="addsource" multisource="true" addsourcehelp=helpStrings.addsource}}' +
185                 '<fieldset class="collapsible collapsed" id="{{elementid}}_audio-advanced-settings">' +
186                     '<input name="mform_isexpanded_{{elementid}}_audio-advanced-settings" type="hidden">' +
187                     '<legend class="ftoggler">{{get_string "advancedsettings" component}}</legend>' +
188                     '<div class="fcontainer">' +
189                         '{{> form_components.advanced_settings}}' +
190                     '</div>' +
191                 '</fieldset>' +
192                 '<fieldset class="collapsible collapsed" id="{{elementid}}_audio-tracks">' +
193                     '<input name="mform_isexpanded_{{elementid}}_audio-tracks" type="hidden">' +
194                     '<legend class="ftoggler">{{get_string "tracks" component}} {{{helpStrings.tracks}}}</legend>' +
195                     '<div class="fcontainer">' +
196                         '{{renderPartial "form_components.track_tabs" context=this id=CSS.AUDIO}}' +
197                     '</div>' +
198                 '</fieldset>'
199         },
200         FORM_COMPONENTS: {
201             SOURCE: '' +
202                 '<div class="{{CSS.SOURCE}} {{id}}">' +
203                     '<label>' +
204                         '{{#entersourcelabel}}{{get_string ../entersourcelabel ../component}}{{/entersourcelabel}}' +
205                         '{{^entersourcelabel}}{{get_string "entersource" ../component}}{{/entersourcelabel}}</a>' +
206                         '<br/>' +
207                         '<input class="{{CSS.URL_INPUT}}" type="url" size="32"/>' +
208                     '</label>' +
209                     '<button class="openmediabrowser" type="button">{{get_string "browserepositories" component}}</button>' +
210                     '{{#multisource}}' +
211                         '{{renderPartial "form_components.add_component" context=../this label=../addcomponentlabel ' +
212                             ' help=../addsourcehelp}}' +
213                     '{{/multisource}}' +
214                 '</div>',
215             ADD_COMPONENT: '' +
216                 '<div>' +
217                     '<a href="#" class="addcomponent">' +
218                         '{{#label}}{{get_string ../label ../component}}{{/label}}' +
219                         '{{^label}}{{get_string "add" ../component}}{{/label}}' +
220                     '</a>' +
221                     '{{#help}}{{{../help}}}{{/help}}' +
222                 '</div>',
223             REMOVE_COMPONENT: '' +
224                 '<div>' +
225                     '<a href="#" class="removecomponent">' +
226                         '{{#label}}{{get_string ../label ../component}}{{/label}}' +
227                         '{{^label}}{{get_string "remove" ../component}}{{/label}}' +
228                     '</a>' +
229                 '</div>',
230             DISPLAY_OPTIONS: '' +
231                 '<div class="{{CSS.DISPLAY_OPTIONS}}">' +
232                     '<label>' +
233                         '{{get_string "size" component}}' +
234                         '<div class={{CSS.POSTER_SIZE}}>' +
235                             '<label>' +
236                                 '<span class="accesshide">{{get_string "videowidth" component}}</span>' +
237                                 '<input type="text" class="{{CSS.WIDTH_INPUT}} input-mini" size="4"/>' +
238                             '</label>' +
239                             ' x ' +
240                             '<label>' +
241                                 '<span class="accesshide">{{get_string "videoheight" component}}</span>' +
242                                 '<input type="text" class="{{CSS.HEIGHT_INPUT}} input-mini" size="4"/>' +
243                             '</label>' +
244                         '</div>' +
245                     '</label>' +
246                     '<div class="clearfix"></div>' +
247                     '{{renderPartial "form_components.source" context=this id=CSS.POSTER_SOURCE entersourcelabel="poster"}}' +
248                 '<div>',
249             ADVANCED_SETTINGS: '' +
250                 '<div class="{{CSS.ADVANCED_SETTINGS}}">' +
251                     '<label>' +
252                         '<input type="checkbox" checked="true" class="{{CSS.MEDIA_CONTROLS_TOGGLE}}"/>' +
253                         '{{get_string "controls" component}}' +
254                     '</label>' +
255                     '<label>' +
256                         '<input type="checkbox" class="{{CSS.MEDIA_AUTOPLAY_TOGGLE}}"/>' +
257                         '{{get_string "autoplay" component}}' +
258                     '</label>' +
259                     '<label>' +
260                         '<input type="checkbox" class="{{CSS.MEDIA_MUTE_TOGGLE}}"/>' +
261                         '{{get_string "mute" component}}' +
262                     '</label>' +
263                     '<label>' +
264                         '<input type="checkbox" class="{{CSS.MEDIA_LOOP_TOGGLE}}"/>' +
265                         '{{get_string "loop" component}}' +
266                     '</label>' +
267                 '</div>',
268             TRACK_TABS: '' +
269                 '<ul class="nav nav-tabs">' +
270                     '<li data-track-kind="{{CSS.TRACK_SUBTITLES}}" class="nav-item">' +
271                         '<a class="nav-link active" href="#{{elementid}}_{{id}}_{{CSS.TRACK_SUBTITLES}}"' +
272                             ' role="tab" data-toggle="tab">' +
273                             '{{get_string "subtitles" component}}' +
274                         '</a>' +
275                     '</li>' +
276                     '<li data-track-kind="{{CSS.TRACK_CAPTIONS}}" class="nav-item">' +
277                         '<a class="nav-link" href="#{{elementid}}_{{id}}_{{CSS.TRACK_CAPTIONS}}" role="tab" data-toggle="tab">' +
278                             '{{get_string "captions" component}}' +
279                         '</a>' +
280                     '</li>' +
281                     '<li data-track-kind="{{CSS.TRACK_DESCRIPTIONS}}"  class="nav-item">' +
282                         '<a class="nav-link" href="#{{elementid}}_{{id}}_{{CSS.TRACK_DESCRIPTIONS}}"' +
283                             ' role="tab" data-toggle="tab">' +
284                             '{{get_string "descriptions" component}}' +
285                         '</a>' +
286                     '</li>' +
287                     '<li data-track-kind="{{CSS.TRACK_CHAPTERS}}" class="nav-item">' +
288                         '<a class="nav-link" href="#{{elementid}}_{{id}}_{{CSS.TRACK_CHAPTERS}}" role="tab" data-toggle="tab">' +
289                             '{{get_string "chapters" component}}' +
290                         '</a>' +
291                     '</li>' +
292                     '<li data-track-kind="{{CSS.TRACK_METADATA}}" class="nav-item">' +
293                         '<a class="nav-link" href="#{{elementid}}_{{id}}_{{CSS.TRACK_METADATA}}" role="tab" data-toggle="tab">' +
294                             '{{get_string "metadata" component}}' +
295                         '</a>' +
296                     '</li>' +
297                 '</ul>' +
298                 '<div class="tab-content">' +
299                     '<div data-track-kind="{{CSS.TRACK_SUBTITLES}}" class="tab-pane active"' +
300                         ' id="{{elementid}}_{{id}}_{{CSS.TRACK_SUBTITLES}}">' +
301                         '<div class="trackhelp">{{{helpStrings.subtitles}}}</div>' +
302                         '{{renderPartial "form_components.track" context=this sourcelabel="subtitlessourcelabel"' +
303                             ' addcomponentlabel="addsubtitlestrack"}}' +
304                     '</div>' +
305                     '<div data-track-kind="{{CSS.TRACK_CAPTIONS}}" class="tab-pane"' +
306                         ' id="{{elementid}}_{{id}}_{{CSS.TRACK_CAPTIONS}}">' +
307                         '<div class="trackhelp">{{{helpStrings.captions}}}</div>' +
308                         '{{renderPartial "form_components.track" context=this sourcelabel="captionssourcelabel"' +
309                             ' addcomponentlabel="addcaptionstrack"}}' +
310                     '</div>' +
311                     '<div data-track-kind="{{CSS.TRACK_DESCRIPTIONS}}" class="tab-pane"' +
312                         ' id="{{elementid}}_{{id}}_{{CSS.TRACK_DESCRIPTIONS}}">' +
313                         '<div class="trackhelp">{{{helpStrings.descriptions}}}</div>' +
314                         '{{renderPartial "form_components.track" context=this sourcelabel="descriptionssourcelabel"' +
315                             ' addcomponentlabel="adddescriptionstrack"}}' +
316                     '</div>' +
317                     '<div data-track-kind="{{CSS.TRACK_CHAPTERS}}" class="tab-pane"' +
318                         ' id="{{elementid}}_{{id}}_{{CSS.TRACK_CHAPTERS}}">' +
319                         '<div class="trackhelp">{{{helpStrings.chapters}}}</div>' +
320                         '{{renderPartial "form_components.track" context=this sourcelabel="chapterssourcelabel"' +
321                             ' addcomponentlabel="addchapterstrack"}}' +
322                     '</div>' +
323                     '<div data-track-kind="{{CSS.TRACK_METADATA}}" class="tab-pane"' +
324                         ' id="{{elementid}}_{{id}}_{{CSS.TRACK_METADATA}}">' +
325                         '<div class="trackhelp">{{{helpStrings.metadata}}}</div>' +
326                         '{{renderPartial "form_components.track" context=this sourcelabel="metadatasourcelabel"' +
327                             ' addcomponentlabel="addmetadatatrack"}}' +
328                     '</div>' +
329                 '</div>',
330             TRACK: '' +
331                 '<div class="{{CSS.TRACK}}">' +
332                     '{{renderPartial "form_components.source" context=this id=CSS.TRACK_SOURCE entersourcelabel=sourcelabel}}' +
333                     '<label class="langlabel">' +
334                         '<span>{{get_string "srclang" component}}</span>' +
335                         '<select class="{{CSS.TRACK_LANG_INPUT}}">' +
336                             '<optgroup label="{{get_string "languagesinstalled" component}}">' +
337                                 '{{#langsinstalled}}' +
338                                     '<option value="{{code}}" {{#default}}selected="selected"{{/default}}>{{lang}}</option>' +
339                                 '{{/langsinstalled}}' +
340                             '</optgroup>' +
341                             '<optgroup label="{{get_string "languagesavailable" component}} ">' +
342                                 '{{#langsavailable}}<option value="{{code}}">{{lang}}</option>{{/langsavailable}}' +
343                             '</optgroup>' +
344                         '</select>' +
345                     '</label>' +
346                     '<label class="labellabel">' +
347                         '<span>{{get_string "label" component}}</span>' +
348                         '<input class="{{CSS.TRACK_LABEL_INPUT}}" type="text"/>' +
349                     '</label>' +
350                     '<label class="defaultlabel">' +
351                         '<input type="checkbox" class="{{CSS.TRACK_DEFAULT_SELECT}}"/>' +
352                         '{{get_string "default" component}}' +
353                     '</label>' +
354                     '{{renderPartial "form_components.add_component" context=this label=addcomponentlabel}}' +
355                 '</div>'
356         },
357         HTML_MEDIA: {
358             VIDEO: '' +
359                 '&nbsp;<video ' +
360                     '{{#width}}width="{{../width}}" {{/width}}' +
361                     '{{#height}}height="{{../height}}" {{/height}}' +
362                     '{{#poster}}poster="{{../poster}}" {{/poster}}' +
363                     '{{#showControls}}controls="true" {{/showControls}}' +
364                     '{{#loop}}loop="true" {{/loop}}' +
365                     '{{#muted}}muted="true" {{/muted}}' +
366                     '{{#autoplay}}autoplay="true" {{/autoplay}}' +
367                 '>' +
368                     '{{#sources}}<source src="{{source}}">{{/sources}}' +
369                     '{{#tracks}}' +
370                         '<track src="{{track}}" kind="{{kind}}" srclang="{{srclang}}" label="{{label}}"' +
371                             ' {{#defaultTrack}}default="true"{{/defaultTrack}}>' +
372                     '{{/tracks}}' +
373                     '{{#description}}{{../description}}{{/description}}' +
374                 '</video>&nbsp',
375             AUDIO: '' +
376                 '&nbsp;<audio ' +
377                     '{{#showControls}}controls="true" {{/showControls}}' +
378                     '{{#loop}}loop="true" {{/loop}}' +
379                     '{{#muted}}muted="true" {{/muted}}' +
380                     '{{#autoplay}}autoplay="true" {{/autoplay}}' +
381                 '>' +
382                     '{{#sources}}<source src="{{source}}">{{/sources}}' +
383                     '{{#tracks}}' +
384                         '<track src="{{track}}" kind="{{kind}}" srclang="{{srclang}}" label="{{label}}"' +
385                             ' {{#defaultTrack}}default="true"{{/defaultTrack}}>' +
386                     '{{/tracks}}' +
387                     '{{#description}}{{../description}}{{/description}}' +
388                 '</audio>&nbsp',
389             LINK: '' +
390                 '<a href="{{url}}" ' +
391                     '{{#width}}data-width="{{../width}}" {{/width}}' +
392                     '{{#height}}data-height="{{../height}}"{{/height}}' +
393                 '>{{#name}}{{../name}}{{/name}}{{^name}}{{../url}}{{/name}}</a>'
394          }
395     };
397 Y.namespace('M.atto_media').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
399     initializer: function() {
400         if (this.get('host').canShowFilepicker('media')) {
401             this.editor.delegate('dblclick', this._displayDialogue, 'video', this);
402             this.editor.delegate('click', this._handleClick, 'video', this);
404             this.addButton({
405                 icon: 'e/insert_edit_video',
406                 callback: this._displayDialogue,
407                 tags: 'video, audio',
408                 tagMatchRequiresAll: false
409             });
410         }
411     },
413     /**
414      * Gets the root context for all templates, with extra supplied context.
415      *
416      * @method _getContext
417      * @param  {Object} extra The extra context to add
418      * @return {Object}
419      * @private
420      */
421     _getContext: function(extra) {
422         return Y.merge({
423             elementid: this.get('host').get('elementid'),
424             component: COMPONENTNAME,
425             langsinstalled: this.get('langs').installed,
426             langsavailable: this.get('langs').available,
427             helpStrings: this.get('help'),
428             CSS: CSS
429         }, extra);
430     },
432     /**
433      * Handles a click on a media element.
434      *
435      * @method _handleClick
436      * @param  {EventFacade} e
437      * @private
438      */
439     _handleClick: function(e) {
440         var medium = e.target;
442         var selection = this.get('host').getSelectionFromNode(medium);
443         if (this.get('host').getSelection() !== selection) {
444             this.get('host').setSelection(selection);
445         }
446     },
448     /**
449      * Display the media editing tool.
450      *
451      * @method _displayDialogue
452      * @private
453      */
454     _displayDialogue: function() {
455         if (this.get('host').getSelection() === false) {
456             return;
457         }
459         if (!('renderPartial' in Y.Handlebars.helpers)) {
460             (function smashPartials(chain, obj) {
461                 Y.each(obj, function(value, index) {
462                     chain.push(index);
463                     if (typeof value !== "object") {
464                         Y.Handlebars.registerPartial(chain.join('.').toLowerCase(), value);
465                     } else {
466                         smashPartials(chain, value);
467                     }
468                     chain.pop();
469                 });
470             })([], TEMPLATES);
472             Y.Handlebars.registerHelper('renderPartial', function(partialName, options) {
473                 if (!partialName) {
474                     return '';
475                 }
477                 var partial = Y.Handlebars.partials[partialName];
478                 var parentContext = options.hash.context ? Y.clone(options.hash.context) : {};
479                 var context = Y.merge(parentContext, options.hash);
480                 delete context.context;
482                 if (!partial) {
483                     return '';
484                 }
485                 return new Y.Handlebars.SafeString(Y.Handlebars.compile(partial)(context));
486             });
487         }
489         var dialogue = this.getDialogue({
490             headerContent: M.util.get_string('createmedia', COMPONENTNAME),
491             focusAfterHide: true,
492             width: 660,
493             focusOnShowSelector: SELECTORS.URL_INPUT
494         });
496         // Set the dialogue content, and then show the dialogue.
497         dialogue.set('bodyContent', this._getDialogueContent(this.get('host').getSelection())).show();
498         M.form.shortforms({formid: this.get('host').get('elementid') + '_atto_media_form'});
499     },
501     /**
502      * Returns the dialogue content for the tool.
503      *
504      * @method _getDialogueContent
505      * @param  {WrappedRange[]} selection Current editor selection
506      * @return {Y.Node}
507      * @private
508      */
509     _getDialogueContent: function(selection) {
510         var content = Y.Node.create(
511             Y.Handlebars.compile(TEMPLATES.ROOT)(this._getContext())
512         );
514         var medium = this.get('host').getSelectedNodes().filter('video,audio').shift();
515         var mediumProperties = medium ? this._getMediumProperties(medium) : false;
516         return this._attachEvents(this._applyMediumProperties(content, mediumProperties), selection);
517     },
519     /**
520      * Attaches required events to the content node.
521      *
522      * @method _attachEvents
523      * @param  {Y.Node}         content The content to which events will be attached
524      * @param  {WrappedRange[]} selection Current editor selection
525      * @return {Y.Node}
526      * @private
527      */
528     _attachEvents: function(content, selection) {
529         // Delegate add component link for media source fields.
530         content.delegate('click', function(e) {
531             e.preventDefault();
532             this._addMediaSourceComponent(e.currentTarget);
533         }, SELECTORS.MEDIA_SOURCE + ' .addcomponent', this);
535         // Delegate add component link for track fields.
536         content.delegate('click', function(e) {
537             e.preventDefault();
538             this._addTrackComponent(e.currentTarget);
539         }, SELECTORS.TRACK + ' .addcomponent', this);
541         // Only allow one track per tab to be selected as "default".
542         content.delegate('click', function(e) {
543             var element = e.currentTarget;
544             if (element.get('checked')) {
545                 var getKind = function(el) {
546                     return this._getTrackTypeFromTabPane(el.ancestor('.tab-pane'));
547                 }.bind(this);
549                 element.ancestor('.root.tab-content').all(SELECTORS.TRACK_DEFAULT_SELECT).each(function(select) {
550                     if (select !== element && getKind(element) === getKind(select)) {
551                         select.set('checked', false);
552                     }
553                 });
554             }
555         }, SELECTORS.TRACK_DEFAULT_SELECT, this);
557         // Set up filepicker click event.
558         content.delegate('click', function(e) {
559             var element = e.currentTarget;
560             var fptype = (element.ancestor(SELECTORS.POSTER_SOURCE) && 'image') ||
561                     (element.ancestor(SELECTORS.TRACK_SOURCE) && 'subtitle') ||
562                     'media';
563             e.preventDefault();
564             this.get('host').showFilepicker(fptype, this._getFilepickerCallback(element, fptype), this);
565         }, '.openmediabrowser', this);
567         // This is a nasty hack. Basically we are using BS4 markup for the tabs
568         // but it isn't completely backwards compatible with BS2. The main problem is
569         // that the "active" class goes on a different node. So the idea is to put it
570         // the node for BS4, and then use CSS to make it look right in BS2. However,
571         // once another tab is clicked, everything sorts itself out, more or less. Except
572         // that the original "active" tab hasn't had the BS4 "active" class removed
573         // (so the styles will still apply to it). So we need to remove the "active"
574         // class on the BS4 node so that BS2 is happy.
575         //
576         // This doesn't upset BS4 since it removes this class anyway when clicking on
577         // another tab.
578         content.all('.nav-item').on('click', function(elem) {
579             elem.currentTarget.get('parentNode').all('.active').removeClass('active');
580         });
582         content.one('.submit').on('click', function(e) {
583             e.preventDefault();
584             var mediaHTML = this._getMediaHTML(e.currentTarget.ancestor('.atto_form')),
585                 host = this.get('host');
586             this.getDialogue({
587                 focusAfterHide: null
588             }).hide();
589             if (mediaHTML) {
590                 host.setSelection(selection);
591                 host.insertContentAtFocusPoint(mediaHTML);
592                 this.markUpdated();
593             }
594         }, this);
596         return content;
597     },
599     /**
600      * Applies medium properties to the content node.
601      *
602      * @method _applyMediumProperties
603      * @param  {Y.Node} content The content to apply the properties to
604      * @param  {object} properties The medium properties to apply
605      * @return {Y.Node}
606      * @private
607      */
608     _applyMediumProperties: function(content, properties) {
609         if (!properties) {
610             return content;
611         }
613         var applyTrackProperties = function(track, properties) {
614             track.one(SELECTORS.TRACK_SOURCE + ' ' + SELECTORS.URL_INPUT).set('value', properties.src);
615             track.one(SELECTORS.TRACK_LANG_INPUT).set('value', properties.srclang);
616             track.one(SELECTORS.TRACK_LABEL_INPUT).set('value', properties.label);
617             track.one(SELECTORS.TRACK_DEFAULT_SELECT).set('checked', properties.defaultTrack);
618         };
620         var tabPane = content.one('.root.tab-content > .tab-pane#' + this.get('host').get('elementid') +
621                               '_' + properties.type.toLowerCase());
623         // Populate sources.
624         tabPane.one(SELECTORS.MEDIA_SOURCE + ' ' + SELECTORS.URL_INPUT).set('value', properties.sources[0]);
625         Y.Array.each(properties.sources.slice(1), function(source) {
626             this._addMediaSourceComponent(tabPane.one(SELECTORS.MEDIA_SOURCE + ' .addcomponent'), function(newComponent) {
627                 newComponent.one(SELECTORS.URL_INPUT).set('value', source);
628             });
629         }, this);
631         // Populate tracks.
632         Y.Object.each(properties.tracks, function(value, key) {
633             var trackData = value.length ? value : [{src: '', srclang: '', label: '', defaultTrack: false}];
634             var paneSelector = SELECTORS['TRACK_' + key.toUpperCase() + '_PANE'];
636             applyTrackProperties(tabPane.one(paneSelector + ' ' + SELECTORS.TRACK), trackData[0]);
637             Y.Array.each(trackData.slice(1), function(track) {
638                 this._addTrackComponent(
639                     tabPane.one(paneSelector + ' ' + SELECTORS.TRACK + ' .addcomponent'), function(newComponent) {
640                     applyTrackProperties(newComponent, track);
641                 });
642             }, this);
643         }, this);
645         // Populate values.
646         tabPane.one(SELECTORS.POSTER_SOURCE + ' ' + SELECTORS.URL_INPUT).setAttribute('value', properties.poster);
647         tabPane.one(SELECTORS.WIDTH_INPUT).set('value', properties.width);
648         tabPane.one(SELECTORS.HEIGHT_INPUT).set('value', properties.height);
649         tabPane.one(SELECTORS.MEDIA_CONTROLS_TOGGLE).set('checked', properties.controls);
650         tabPane.one(SELECTORS.MEDIA_AUTOPLAY_TOGGLE).set('checked', properties.autoplay);
651         tabPane.one(SELECTORS.MEDIA_MUTE_TOGGLE).set('checked', properties.muted);
652         tabPane.one(SELECTORS.MEDIA_LOOP_TOGGLE).set('checked', properties.loop);
654         // Switch to the correct tab.
655         var mediumType = this._getMediumTypeFromTabPane(tabPane);
657         // Remove active class from all tabs + tab panes.
658         tabPane.siblings('.active').removeClass('active');
659         content.all('.root.nav-tabs .nav-item a').removeClass('active');
661         // Add active class to the desired tab and tab pane.
662         tabPane.addClass('active');
663         content.one(SELECTORS[mediumType.toUpperCase() + '_TAB'] + ' a').addClass('active');
665         return content;
666     },
668     /**
669      * Extracts medium properties.
670      *
671      * @method _getMediumProperties
672      * @param  {Y.Node} medium The medium node from which to extract
673      * @return {Object}
674      * @private
675      */
676     _getMediumProperties: function(medium) {
677         var boolAttr = function(elem, attr) {
678             return elem.getAttribute(attr) ? true : false;
679         };
681         var tracks = {
682             subtitles: [],
683             captions: [],
684             descriptions: [],
685             chapters: [],
686             metadata: []
687         };
689         medium.all('track').each(function(track) {
690             tracks[track.getAttribute('kind')].push({
691                 src: track.getAttribute('src'),
692                 srclang: track.getAttribute('srclang'),
693                 label: track.getAttribute('label'),
694                 defaultTrack: boolAttr(track, 'default')
695             });
696         });
698         return {
699             type: medium.test('video') ? MEDIA_TYPES.VIDEO : MEDIA_TYPES.AUDIO,
700             sources: medium.all('source').get('src'),
701             poster: medium.getAttribute('poster'),
702             width: medium.getAttribute('width'),
703             height: medium.getAttribute('height'),
704             autoplay: boolAttr(medium, 'autoplay'),
705             loop: boolAttr(medium, 'loop'),
706             muted: boolAttr(medium, 'muted'),
707             controls: boolAttr(medium, 'controls'),
708             tracks: tracks
709         };
710     },
712     /**
713      * Adds a track form component.
714      *
715      * @method _addTrackComponent
716      * @param  {Y.Node}   element    The element which was used to trigger this function
717      * @param  {Function} [callback] Function to be called when the new component is added
718      *     @param {Y.Node}    callback.newComponent The compiled component
719      * @private
720      */
721     _addTrackComponent: function(element, callback) {
722         var trackType = this._getTrackTypeFromTabPane(element.ancestor('.tab-pane'));
723         var context = this._getContext({
724             sourcelabel: trackType + 'sourcelabel',
725             addcomponentlabel: 'add' + trackType + 'track'
726         });
728         this._addComponent(element, TEMPLATES.FORM_COMPONENTS.TRACK, SELECTORS.TRACK, context, callback);
729     },
731     /**
732      * Adds a media source form component.
733      *
734      * @method _addMediaSourceComponent
735      * @param  {Y.Node}   element    The element which was used to trigger this function
736      * @param  {Function} [callback] Function to be called when the new component is added
737      *     @param {Y.Node}    callback.newComponent The compiled component
738      * @private
739      */
740     _addMediaSourceComponent: function(element, callback) {
741         var mediumType = this._getMediumTypeFromTabPane(element.ancestor('.tab-pane'));
742         var context = this._getContext({
743             multisource: true,
744             id: CSS.MEDIA_SOURCE,
745             entersourcelabel: mediumType + 'sourcelabel',
746             addcomponentlabel: 'addsource',
747             addsourcehelp: this.get('help').addsource
748         });
749         this._addComponent(element, TEMPLATES.FORM_COMPONENTS.SOURCE, SELECTORS.MEDIA_SOURCE, context, callback);
750     },
752     /**
753      * Adds an arbitrary form component.
754      *
755      * This function Compiles and adds the provided component in the supplied 'ancestor' container.
756      * It will also add links to add/remove the relevant components, attaching the
757      * necessary events.
758      *
759      * @method _addComponent
760      * @param  {Y.Node}   element    The element which was used to trigger this function
761      * @param  {String}   component  The component to compile and add
762      * @param  {String}   ancestor   A selector used to find an ancestor of 'component', to which
763      *                               the compiled component will be appended
764      * @param  {Object}   context    The context with which to render the component
765      * @param  {Function} [callback] Function to be called when the new component is added
766      *     @param {Y.Node}    callback.newComponent The compiled component
767      * @private
768      */
769     _addComponent: function(element, component, ancestor, context, callback) {
770         var currentComponent = element.ancestor(ancestor),
771             newComponent = Y.Node.create(Y.Handlebars.compile(component)(context)),
772             removeNodeContext = this._getContext(context);
774         removeNodeContext.label = "remove";
775         var removeNode = Y.Node.create(Y.Handlebars.compile(TEMPLATES.FORM_COMPONENTS.REMOVE_COMPONENT)(removeNodeContext));
777         removeNode.one('.removecomponent').on('click', function(e) {
778             e.preventDefault();
779             currentComponent.remove(true);
780         });
782         currentComponent.insert(newComponent, 'after');
783         element.ancestor().insert(removeNode, 'after');
784         element.ancestor().remove(true);
786         if (callback) {
787             callback.call(this, newComponent);
788         }
789     },
791     /**
792      * Returns the callback for the file picker to call after a file has been selected.
793      *
794      * @method _getFilepickerCallback
795      * @param  {Y.Node} element The element which triggered the callback
796      * @param  {String} fptype  The file pickertype (as would be passed to `showFilePicker`)
797      * @return {Function} The function to be used as a callback when the file picker returns the file
798      * @private
799      */
800     _getFilepickerCallback: function(element, fptype) {
801         return function(params) {
802             if (params.url !== '') {
803                 var tabPane = element.ancestor('.tab-pane');
804                 element.ancestor(SELECTORS.SOURCE).one(SELECTORS.URL_INPUT).set('value', params.url);
806                 // Links (and only links) have a name field.
807                 if (tabPane.get('id') === this.get('host').get('elementid') + '_' + CSS.LINK) {
808                     tabPane.one(SELECTORS.NAME_INPUT).set('value', params.file);
809                 }
811                 if (fptype === 'subtitle') {
812                     var subtitleLang = params.file.split('.vtt')[0].split('-').slice(-1)[0];
813                     var langObj = this.get('langs').available.reduce(function(carry, lang) {
814                         return lang.code === subtitleLang ? lang : carry;
815                     }, false);
816                     if (langObj) {
817                         element.ancestor(SELECTORS.TRACK).one(SELECTORS.TRACK_LABEL_INPUT).set('value',
818                                 langObj.lang.substr(0, langObj.lang.lastIndexOf(' ')));
819                         element.ancestor(SELECTORS.TRACK).one(SELECTORS.TRACK_LANG_INPUT).set('value', langObj.code);
820                     }
821                 }
822             }
823         };
824     },
826     /**
827      * Given a "medium" tab pane, returns what kind of medium it contains.
828      *
829      * @method _getMediumTypeFromTabPane
830      * @param  {Y.Node} tabPane The tab pane
831      * @return {String} The type of medium in the pane
832      */
833     _getMediumTypeFromTabPane: function(tabPane) {
834         return tabPane.getAttribute('data-medium-type');
835     },
837     /**
838      * Given a "track" tab pane, returns what kind of track it contains.
839      *
840      * @method _getTrackTypeFromTabPane
841      * @param  {Y.Node} tabPane The tab pane
842      * @return {String} The type of track in the pane
843      */
844     _getTrackTypeFromTabPane: function(tabPane) {
845         return tabPane.getAttribute('data-track-kind');
846     },
848     /**
849      * Returns the HTML to be inserted to the text area.
850      *
851      * @method _getMediaHTML
852      * @param  {Y.Node} form The form from which to extract data
853      * @return {String} The compiled markup
854      * @private
855      */
856     _getMediaHTML: function(form) {
857         var mediumType = this._getMediumTypeFromTabPane(form.one('.root.tab-content > .tab-pane.active'));
858         var tabContent = form.one(SELECTORS[mediumType.toUpperCase() + '_PANE']);
860         return this['_getMediaHTML' + mediumType[0].toUpperCase() + mediumType.substr(1)](tabContent);
861     },
863     /**
864      * Returns the HTML to be inserted to the text area for the link tab.
865      *
866      * @method _getMediaHTMLLink
867      * @param  {Y.Node} tab The tab from which to extract data
868      * @return {String} The compiled markup
869      * @private
870      */
871     _getMediaHTMLLink: function(tab) {
872         var context = {
873             url: tab.one(SELECTORS.URL_INPUT).get('value'),
874             name: tab.one(SELECTORS.NAME_INPUT).get('value') || false
875         };
877         return context.url ? Y.Handlebars.compile(TEMPLATES.HTML_MEDIA.LINK)(context) : '';
878     },
880     /**
881      * Returns the HTML to be inserted to the text area for the video tab.
882      *
883      * @method _getMediaHTMLVideo
884      * @param  {Y.Node} tab The tab from which to extract data
885      * @return {String} The compiled markup
886      * @private
887      */
888     _getMediaHTMLVideo: function(tab) {
889         var context = this._getContextForMediaHTML(tab);
890         context.width = tab.one(SELECTORS.WIDTH_INPUT).get('value') || false;
891         context.height = tab.one(SELECTORS.HEIGHT_INPUT).get('value') || false;
892         context.poster = tab.one(SELECTORS.POSTER_SOURCE + ' ' + SELECTORS.URL_INPUT).get('value') || false;
894         return context.sources.length ? Y.Handlebars.compile(TEMPLATES.HTML_MEDIA.VIDEO)(context) : '';
895     },
897     /**
898      * Returns the HTML to be inserted to the text area for the audio tab.
899      *
900      * @method _getMediaHTMLAudio
901      * @param  {Y.Node} tab The tab from which to extract data
902      * @return {String} The compiled markup
903      * @private
904      */
905     _getMediaHTMLAudio: function(tab) {
906         var context = this._getContextForMediaHTML(tab);
908         return context.sources.length ? Y.Handlebars.compile(TEMPLATES.HTML_MEDIA.AUDIO)(context) : '';
909     },
911     /**
912      * Returns the context with which to render a media template.
913      *
914      * @method _getContextForMediaHTML
915      * @param  {Y.Node} tab The tab from which to extract data
916      * @return {Object}
917      * @private
918      */
919     _getContextForMediaHTML: function(tab) {
920         var tracks = [];
922         tab.all(SELECTORS.TRACK).each(function(track) {
923             tracks.push({
924                 track: track.one(SELECTORS.TRACK_SOURCE + ' ' + SELECTORS.URL_INPUT).get('value'),
925                 kind: this._getTrackTypeFromTabPane(track.ancestor('.tab-pane')),
926                 label: track.one(SELECTORS.TRACK_LABEL_INPUT).get('value') ||
927                     track.one(SELECTORS.TRACK_LANG_INPUT).get('value'),
928                 srclang: track.one(SELECTORS.TRACK_LANG_INPUT).get('value'),
929                 defaultTrack: track.one(SELECTORS.TRACK_DEFAULT_SELECT).get('checked') ? "true" : null
930             });
931         }, this);
933         return {
934             sources: tab.all(SELECTORS.MEDIA_SOURCE + ' ' + SELECTORS.URL_INPUT).get('value').filter(function(source) {
935                 return !!source;
936             }).map(function(source) {
937                 return {source: source};
938             }),
939             description: tab.one(SELECTORS.MEDIA_SOURCE + ' ' + SELECTORS.URL_INPUT).get('value') || false,
940             tracks: tracks.filter(function(track) {
941                 return !!track.track;
942             }),
943             showControls: tab.one(SELECTORS.MEDIA_CONTROLS_TOGGLE).get('checked'),
944             autoplay: tab.one(SELECTORS.MEDIA_AUTOPLAY_TOGGLE).get('checked'),
945             muted: tab.one(SELECTORS.MEDIA_MUTE_TOGGLE).get('checked'),
946             loop: tab.one(SELECTORS.MEDIA_LOOP_TOGGLE).get('checked')
947         };
948     }
949 }, {
950     ATTRS: {
951         langs: {},
952         help: {}
953     }
954 });