| 1 | efrain | 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/>.
 | 
        
           |  |  | 15 |   | 
        
           |  |  | 16 | /**
 | 
        
           |  |  | 17 |  * This module allows to enhance the form elements MoodleQuickForm_filetypes
 | 
        
           |  |  | 18 |  *
 | 
        
           |  |  | 19 |  * @module     core_form/filetypes
 | 
        
           |  |  | 20 |  * @copyright  2017 David Mudrak <david@moodle.com>
 | 
        
           |  |  | 21 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 22 |  * @since      3.3
 | 
        
           |  |  | 23 |  */
 | 
        
           |  |  | 24 | define(['jquery', 'core/log', 'core/modal_events', 'core/modal_save_cancel', 'core/ajax',
 | 
        
           |  |  | 25 |         'core/templates', 'core/tree'],
 | 
        
           |  |  | 26 |     function($, Log, ModalEvents, ModalSaveCancel, Ajax, Templates, Tree) {
 | 
        
           |  |  | 27 |   | 
        
           |  |  | 28 |     "use strict";
 | 
        
           |  |  | 29 |   | 
        
           |  |  | 30 |     /**
 | 
        
           |  |  | 31 |      * Constructor of the FileTypes instances.
 | 
        
           |  |  | 32 |      *
 | 
        
           |  |  | 33 |      * @constructor
 | 
        
           |  |  | 34 |      * @param {String} elementId The id of the form element to enhance
 | 
        
           |  |  | 35 |      * @param {String} elementLabel The label of the form element used as the modal selector title
 | 
        
           |  |  | 36 |      * @param {String} onlyTypes Limit the list of offered types to this
 | 
        
           |  |  | 37 |      * @param {Bool} allowAll Allow presence of the "All file types" item
 | 
        
           |  |  | 38 |      */
 | 
        
           |  |  | 39 |     var FileTypes = function(elementId, elementLabel, onlyTypes, allowAll) {
 | 
        
           |  |  | 40 |   | 
        
           |  |  | 41 |         this.elementId = elementId;
 | 
        
           |  |  | 42 |         this.elementLabel = elementLabel;
 | 
        
           |  |  | 43 |         this.onlyTypes = onlyTypes;
 | 
        
           |  |  | 44 |         this.allowAll = allowAll;
 | 
        
           |  |  | 45 |   | 
        
           |  |  | 46 |         this.inputField = $('#' + elementId);
 | 
        
           |  |  | 47 |         this.wrapperBrowserTrigger = $('[data-filetypesbrowser="' + elementId + '"]');
 | 
        
           |  |  | 48 |         this.wrapperDescriptions = $('[data-filetypesdescriptions="' + elementId + '"]');
 | 
        
           |  |  | 49 |   | 
        
           |  |  | 50 |         if (!this.wrapperBrowserTrigger.length) {
 | 
        
           |  |  | 51 |             // This is a valid case. Most probably the element is frozen and
 | 
        
           |  |  | 52 |             // the filetypes browser should not be available.
 | 
        
           |  |  | 53 |             return;
 | 
        
           |  |  | 54 |         }
 | 
        
           |  |  | 55 |   | 
        
           |  |  | 56 |         if (!this.inputField.length || !this.wrapperDescriptions.length) {
 | 
        
           |  |  | 57 |             Log.error('core_form/filetypes: Unexpected DOM structure, unable to enhance filetypes field ' + elementId);
 | 
        
           |  |  | 58 |             return;
 | 
        
           |  |  | 59 |         }
 | 
        
           |  |  | 60 |   | 
        
           |  |  | 61 |         this.prepareBrowserTrigger()
 | 
        
           |  |  | 62 |             .then(function() {
 | 
        
           |  |  | 63 |                 return this.prepareBrowserModal();
 | 
        
           |  |  | 64 |             }.bind(this))
 | 
        
           |  |  | 65 |   | 
        
           |  |  | 66 |             .then(function() {
 | 
        
           |  |  | 67 |                 return this.prepareBrowserTree();
 | 
        
           |  |  | 68 |             }.bind(this));
 | 
        
           |  |  | 69 |     };
 | 
        
           |  |  | 70 |   | 
        
           |  |  | 71 |     /**
 | 
        
           |  |  | 72 |      * Create and set the browser trigger widget (this.browserTrigger).
 | 
        
           |  |  | 73 |      *
 | 
        
           |  |  | 74 |      * @method prepareBrowserTrigger
 | 
        
           |  |  | 75 |      * @returns {Promise}
 | 
        
           |  |  | 76 |      */
 | 
        
           |  |  | 77 |     FileTypes.prototype.prepareBrowserTrigger = function() {
 | 
        
           |  |  | 78 |         return Templates.render('core_form/filetypes-trigger', {})
 | 
        
           |  |  | 79 |             .then(function(html) {
 | 
        
           |  |  | 80 |                 this.wrapperBrowserTrigger.html(html);
 | 
        
           |  |  | 81 |                 this.browserTrigger = this.wrapperBrowserTrigger.find('[data-filetypeswidget="browsertrigger"]');
 | 
        
           |  |  | 82 |             }.bind(this));
 | 
        
           |  |  | 83 |     };
 | 
        
           |  |  | 84 |   | 
        
           |  |  | 85 |     /**
 | 
        
           |  |  | 86 |      * Create and set the modal for displaying the browser (this.browserModal).
 | 
        
           |  |  | 87 |      *
 | 
        
           |  |  | 88 |      * @method prepareBrowserModal
 | 
        
           |  |  | 89 |      * @returns {Promise}
 | 
        
           |  |  | 90 |      */
 | 
        
           |  |  | 91 |     FileTypes.prototype.prepareBrowserModal = function() {
 | 
        
           |  |  | 92 |         return ModalSaveCancel.create({
 | 
        
           |  |  | 93 |             title: this.elementLabel,
 | 
        
           |  |  | 94 |         })
 | 
        
           |  |  | 95 |         .then(function(modal) {
 | 
        
           |  |  | 96 |             this.browserModal = modal;
 | 
        
           |  |  | 97 |             return modal;
 | 
        
           |  |  | 98 |         }.bind(this))
 | 
        
           |  |  | 99 |         .then(function() {
 | 
        
           |  |  | 100 |             // Because we have custom conditional modal trigger, we need to
 | 
        
           |  |  | 101 |             // handle the focus after closing ourselves, too.
 | 
        
           |  |  | 102 |             this.browserModal.getRoot().on(ModalEvents.hidden, function() {
 | 
        
           |  |  | 103 |                 this.browserTrigger.focus();
 | 
        
           |  |  | 104 |             }.bind(this));
 | 
        
           |  |  | 105 |   | 
        
           |  |  | 106 |             this.browserModal.getRoot().on(ModalEvents.save, function() {
 | 
        
           |  |  | 107 |                 this.saveBrowserModal();
 | 
        
           |  |  | 108 |             }.bind(this));
 | 
        
           |  |  | 109 |         }.bind(this));
 | 
        
           |  |  | 110 |   | 
        
           |  |  | 111 |     };
 | 
        
           |  |  | 112 |   | 
        
           |  |  | 113 |     /**
 | 
        
           |  |  | 114 |      * Create and set the tree in the browser modal's body.
 | 
        
           |  |  | 115 |      *
 | 
        
           |  |  | 116 |      * @method prepareBrowserTree
 | 
        
           |  |  | 117 |      * @returns {Promise}
 | 
        
           |  |  | 118 |      */
 | 
        
           |  |  | 119 |     FileTypes.prototype.prepareBrowserTree = function() {
 | 
        
           |  |  | 120 |   | 
        
           |  |  | 121 |         this.browserTrigger.on('click', function(e) {
 | 
        
           |  |  | 122 |             e.preventDefault();
 | 
        
           |  |  | 123 |   | 
        
           |  |  | 124 |             // We want to display the browser modal only when the associated input
 | 
        
           |  |  | 125 |             // field is not frozen (disabled).
 | 
        
           |  |  | 126 |             if (this.inputField.is('[disabled]')) {
 | 
        
           |  |  | 127 |                 return;
 | 
        
           |  |  | 128 |             }
 | 
        
           |  |  | 129 |   | 
        
           |  |  | 130 |             var bodyContent = this.loadBrowserModalBody();
 | 
        
           |  |  | 131 |   | 
        
           |  |  | 132 |             bodyContent.then(function() {
 | 
        
           |  |  | 133 |   | 
        
           |  |  | 134 |                 // Turn the list of groups and extensions into the tree.
 | 
        
           |  |  | 135 |                 this.browserTree = new Tree(this.browserModal.getBody());
 | 
        
           |  |  | 136 |   | 
        
           |  |  | 137 |                 // Override the behaviour of the Enter and Space keys to toggle our checkbox,
 | 
        
           |  |  | 138 |                 // rather than toggle the tree node expansion status.
 | 
        
           |  |  | 139 |                 this.browserTree.handleKeyDown = function(item, e) {
 | 
        
           |  |  | 140 |                     if (e.keyCode == this.browserTree.keys.enter || e.keyCode == this.browserTree.keys.space) {
 | 
        
           |  |  | 141 |                         e.preventDefault();
 | 
        
           |  |  | 142 |                         e.stopPropagation();
 | 
        
           |  |  | 143 |                         this.toggleCheckbox(item.attr('data-filetypesbrowserkey'));
 | 
        
           |  |  | 144 |                     } else {
 | 
        
           |  |  | 145 |                         Tree.prototype.handleKeyDown.call(this.browserTree, item, e);
 | 
        
           |  |  | 146 |                     }
 | 
        
           |  |  | 147 |                 }.bind(this);
 | 
        
           |  |  | 148 |   | 
        
           |  |  | 149 |                 if (this.allowAll) {
 | 
        
           |  |  | 150 |                     // Hide all other items if "All file types" is enabled.
 | 
        
           |  |  | 151 |                     this.hideOrShowItemsDependingOnAllowAll(this.browserModal.getRoot()
 | 
        
           |  |  | 152 |                         .find('input[type="checkbox"][data-filetypesbrowserkey="*"]').first());
 | 
        
           |  |  | 153 |                     // And do the same whenever we click that checkbox.
 | 
        
           |  |  | 154 |                     this.browserModal.getRoot().on('change', 'input[type="checkbox"][data-filetypesbrowserkey="*"]', function(e) {
 | 
        
           |  |  | 155 |                         this.hideOrShowItemsDependingOnAllowAll($(e.currentTarget));
 | 
        
           |  |  | 156 |                     }.bind(this));
 | 
        
           |  |  | 157 |                 }
 | 
        
           |  |  | 158 |   | 
        
           |  |  | 159 |                 // Synchronize checked status if the file extension is present in multiple groups.
 | 
        
           |  |  | 160 |                 this.browserModal.getRoot().on('change', 'input[type="checkbox"][data-filetypesbrowserkey]', function(e) {
 | 
        
           |  |  | 161 |                     var checkbox = $(e.currentTarget);
 | 
        
           |  |  | 162 |                     var key = checkbox.attr('data-filetypesbrowserkey');
 | 
        
           |  |  | 163 |                     this.browserModal.getRoot().find('input[type="checkbox"][data-filetypesbrowserkey="' + key + '"]')
 | 
        
           |  |  | 164 |                         .prop('checked', checkbox.prop('checked'));
 | 
        
           |  |  | 165 |                 }.bind(this));
 | 
        
           |  |  | 166 |   | 
        
           |  |  | 167 |             }.bind(this))
 | 
        
           |  |  | 168 |   | 
        
           |  |  | 169 |             .then(function() {
 | 
        
           |  |  | 170 |                 this.browserModal.show();
 | 
        
           |  |  | 171 |             }.bind(this));
 | 
        
           |  |  | 172 |   | 
        
           |  |  | 173 |             this.browserModal.setBody(bodyContent);
 | 
        
           |  |  | 174 |   | 
        
           |  |  | 175 |         }.bind(this));
 | 
        
           |  |  | 176 |   | 
        
           |  |  | 177 |         // Return a resolved promise.
 | 
        
           |  |  | 178 |         return $.when();
 | 
        
           |  |  | 179 |     };
 | 
        
           |  |  | 180 |   | 
        
           |  |  | 181 |     /**
 | 
        
           |  |  | 182 |      * Load the browser modal body contents.
 | 
        
           |  |  | 183 |      *
 | 
        
           |  |  | 184 |      * @returns {Promise}
 | 
        
           |  |  | 185 |      */
 | 
        
           |  |  | 186 |     FileTypes.prototype.loadBrowserModalBody = function() {
 | 
        
           |  |  | 187 |   | 
        
           |  |  | 188 |         var args = {
 | 
        
           |  |  | 189 |             onlytypes: this.onlyTypes.join(),
 | 
        
           |  |  | 190 |             allowall: this.allowAll,
 | 
        
           |  |  | 191 |             current: this.inputField.val()
 | 
        
           |  |  | 192 |         };
 | 
        
           |  |  | 193 |   | 
        
           |  |  | 194 |         return Ajax.call([{
 | 
        
           |  |  | 195 |             methodname: 'core_form_get_filetypes_browser_data',
 | 
        
           |  |  | 196 |             args: args
 | 
        
           |  |  | 197 |   | 
        
           |  |  | 198 |         }])[0].then(function(browserData) {
 | 
        
           |  |  | 199 |             return Templates.render('core_form/filetypes-browser', {
 | 
        
           |  |  | 200 |                 elementid: this.elementId,
 | 
        
           |  |  | 201 |                 groups: browserData.groups
 | 
        
           |  |  | 202 |             });
 | 
        
           |  |  | 203 |         }.bind(this));
 | 
        
           |  |  | 204 |     };
 | 
        
           |  |  | 205 |   | 
        
           |  |  | 206 |     /**
 | 
        
           |  |  | 207 |      * Change the checked status of the given file type (group or extension).
 | 
        
           |  |  | 208 |      *
 | 
        
           |  |  | 209 |      * @method toggleCheckbox
 | 
        
           |  |  | 210 |      * @param {String} key
 | 
        
           |  |  | 211 |      */
 | 
        
           |  |  | 212 |     FileTypes.prototype.toggleCheckbox = function(key) {
 | 
        
           |  |  | 213 |   | 
        
           |  |  | 214 |         var checkbox = this.browserModal.getRoot().find('input[type="checkbox"][data-filetypesbrowserkey="' + key + '"]').first();
 | 
        
           |  |  | 215 |   | 
        
           |  |  | 216 |         checkbox.prop('checked', !checkbox.prop('checked'));
 | 
        
           |  |  | 217 |     };
 | 
        
           |  |  | 218 |   | 
        
           |  |  | 219 |     /**
 | 
        
           |  |  | 220 |      * Update the associated input field with selected file types.
 | 
        
           |  |  | 221 |      *
 | 
        
           |  |  | 222 |      * @method saveBrowserModal
 | 
        
           |  |  | 223 |      */
 | 
        
           |  |  | 224 |     FileTypes.prototype.saveBrowserModal = function() {
 | 
        
           |  |  | 225 |   | 
        
           |  |  | 226 |         // Check the "All file types" first.
 | 
        
           |  |  | 227 |         if (this.allowAll) {
 | 
        
           |  |  | 228 |             var allcheckbox = this.browserModal.getRoot().find('input[type="checkbox"][data-filetypesbrowserkey="*"]');
 | 
        
           |  |  | 229 |             if (allcheckbox.length && allcheckbox.prop('checked')) {
 | 
        
           |  |  | 230 |                 this.inputField.val('*');
 | 
        
           |  |  | 231 |                 this.updateDescriptions(['*']);
 | 
        
           |  |  | 232 |                 return;
 | 
        
           |  |  | 233 |             }
 | 
        
           |  |  | 234 |         }
 | 
        
           |  |  | 235 |   | 
        
           |  |  | 236 |         // Iterate over all checked boxes and populate the list.
 | 
        
           |  |  | 237 |         var newvalue = [];
 | 
        
           |  |  | 238 |   | 
        
           |  |  | 239 |         this.browserModal.getRoot().find('input[type="checkbox"]').each(/** @this represents the checkbox */ function() {
 | 
        
           |  |  | 240 |             var checkbox = $(this);
 | 
        
           |  |  | 241 |             var key = checkbox.attr('data-filetypesbrowserkey');
 | 
        
           |  |  | 242 |   | 
        
           |  |  | 243 |             if (checkbox.prop('checked')) {
 | 
        
           |  |  | 244 |                 newvalue.push(key);
 | 
        
           |  |  | 245 |             }
 | 
        
           |  |  | 246 |         });
 | 
        
           |  |  | 247 |   | 
        
           |  |  | 248 |         // Remove duplicates (e.g. file types present in multiple groups).
 | 
        
           |  |  | 249 |         newvalue = newvalue.filter(function(x, i, a) {
 | 
        
           |  |  | 250 |             return a.indexOf(x) == i;
 | 
        
           |  |  | 251 |         });
 | 
        
           |  |  | 252 |   | 
        
           |  |  | 253 |         this.inputField.val(newvalue.join(' '));
 | 
        
           |  |  | 254 |         this.updateDescriptions(newvalue);
 | 
        
           |  |  | 255 |     };
 | 
        
           |  |  | 256 |   | 
        
           |  |  | 257 |     /**
 | 
        
           |  |  | 258 |      * Describe the selected filetypes in the form when saving the browser.
 | 
        
           |  |  | 259 |      *
 | 
        
           |  |  | 260 |      * @param {Array} keys List of keys to describe
 | 
        
           |  |  | 261 |      * @returns {Promise}
 | 
        
           |  |  | 262 |      */
 | 
        
           |  |  | 263 |     FileTypes.prototype.updateDescriptions = function(keys) {
 | 
        
           |  |  | 264 |   | 
        
           |  |  | 265 |         var descriptions = [];
 | 
        
           |  |  | 266 |   | 
        
           |  |  | 267 |         keys.forEach(function(key) {
 | 
        
           |  |  | 268 |             descriptions.push({
 | 
        
           |  |  | 269 |                 description: this.browserModal.getRoot().find('[data-filetypesname="' + key + '"]').first().text().trim(),
 | 
        
           |  |  | 270 |                 extensions: this.browserModal.getRoot().find('[data-filetypesextensions="' + key + '"]').first().text().trim()
 | 
        
           |  |  | 271 |             });
 | 
        
           |  |  | 272 |         }.bind(this));
 | 
        
           |  |  | 273 |   | 
        
           |  |  | 274 |         var templatedata = {
 | 
        
           |  |  | 275 |             hasdescriptions: (descriptions.length > 0),
 | 
        
           |  |  | 276 |             descriptions: descriptions
 | 
        
           |  |  | 277 |         };
 | 
        
           |  |  | 278 |   | 
        
           |  |  | 279 |         return Templates.render('core_form/filetypes-descriptions', templatedata)
 | 
        
           |  |  | 280 |             .then(function(html) {
 | 
        
           |  |  | 281 |                 this.wrapperDescriptions.html(html);
 | 
        
           |  |  | 282 |             }.bind(this));
 | 
        
           |  |  | 283 |     };
 | 
        
           |  |  | 284 |   | 
        
           |  |  | 285 |     /**
 | 
        
           |  |  | 286 |      * If "All file types" is checked, all other browser items are made hidden, and vice versa.
 | 
        
           |  |  | 287 |      *
 | 
        
           |  |  | 288 |      * @param {jQuery} allcheckbox The "All file types" checkbox.
 | 
        
           |  |  | 289 |      */
 | 
        
           |  |  | 290 |     FileTypes.prototype.hideOrShowItemsDependingOnAllowAll = function(allcheckbox) {
 | 
        
           |  |  | 291 |         var others = this.browserModal.getRoot().find('[role="treeitem"][data-filetypesbrowserkey!="*"]');
 | 
        
           |  |  | 292 |         if (allcheckbox.prop('checked')) {
 | 
        
           |  |  | 293 |             others.hide();
 | 
        
           |  |  | 294 |         } else {
 | 
        
           |  |  | 295 |             others.show();
 | 
        
           |  |  | 296 |         }
 | 
        
           |  |  | 297 |     };
 | 
        
           |  |  | 298 |   | 
        
           |  |  | 299 |     return {
 | 
        
           |  |  | 300 |         init: function(elementId, elementLabel, onlyTypes, allowAll) {
 | 
        
           |  |  | 301 |             new FileTypes(elementId, elementLabel, onlyTypes, allowAll);
 | 
        
           |  |  | 302 |         }
 | 
        
           |  |  | 303 |     };
 | 
        
           |  |  | 304 | });
 |