[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 YUI.add('moodle-atto_image-button', function (Y, NAME) { 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /* 19 * @package atto_image 20 * @copyright 2013 Damyon Wiese <damyon@moodle.com> 21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 */ 23 24 /** 25 * @module moodle-atto_image_alignment-button 26 */ 27 28 /** 29 * Atto image selection tool. 30 * 31 * @namespace M.atto_image 32 * @class Button 33 * @extends M.editor_atto.EditorPlugin 34 */ 35 36 var CSS = { 37 RESPONSIVE: 'img-responsive', 38 INPUTALIGNMENT: 'atto_image_alignment', 39 INPUTALT: 'atto_image_altentry', 40 INPUTHEIGHT: 'atto_image_heightentry', 41 INPUTSUBMIT: 'atto_image_urlentrysubmit', 42 INPUTURL: 'atto_image_urlentry', 43 INPUTSIZE: 'atto_image_size', 44 INPUTWIDTH: 'atto_image_widthentry', 45 IMAGEALTWARNING: 'atto_image_altwarning', 46 IMAGEBROWSER: 'openimagebrowser', 47 IMAGEPRESENTATION: 'atto_image_presentation', 48 INPUTCONSTRAIN: 'atto_image_constrain', 49 INPUTCUSTOMSTYLE: 'atto_image_customstyle', 50 IMAGEPREVIEW: 'atto_image_preview', 51 IMAGEPREVIEWBOX: 'atto_image_preview_box' 52 }, 53 SELECTORS = { 54 INPUTURL: '.' + CSS.INPUTURL 55 }, 56 ALIGNMENTS = [ 57 // Vertical alignment. 58 { 59 name: 'text-top', 60 str: 'alignment_top', 61 value: 'vertical-align', 62 margin: '0 .5em' 63 }, { 64 name: 'middle', 65 str: 'alignment_middle', 66 value: 'vertical-align', 67 margin: '0 .5em' 68 }, { 69 name: 'text-bottom', 70 str: 'alignment_bottom', 71 value: 'vertical-align', 72 margin: '0 .5em', 73 isDefault: true 74 }, 75 76 // Floats. 77 { 78 name: 'left', 79 str: 'alignment_left', 80 value: 'float', 81 margin: '0 .5em 0 0' 82 }, { 83 name: 'right', 84 str: 'alignment_right', 85 value: 'float', 86 margin: '0 0 0 .5em' 87 }, { 88 name: 'customstyle', 89 str: 'customstyle', 90 value: 'style' 91 } 92 ], 93 94 REGEX = { 95 ISPERCENT: /\d+%/ 96 }, 97 98 COMPONENTNAME = 'atto_image', 99 100 TEMPLATE = '' + 101 '<form class="atto_form">' + 102 '<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' + 103 '<input class="fullwidth {{CSS.INPUTURL}}" type="url" id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' + 104 '<br/>' + 105 106 // Add the repository browser button. 107 '{{#if showFilepicker}}' + 108 '<button class="{{CSS.IMAGEBROWSER}}" type="button">{{get_string "browserepositories" component}}</button>' + 109 '{{/if}}' + 110 111 // Add the Alt box. 112 '<div style="display:none" role="alert" class="warning {{CSS.IMAGEALTWARNING}}">' + 113 '{{get_string "presentationoraltrequired" component}}' + 114 '</div>' + 115 '<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' + 116 '<input class="fullwidth {{CSS.INPUTALT}}" type="text" value="" id="{{elementid}}_{{CSS.INPUTALT}}" size="32"/>' + 117 '<br/>' + 118 119 // Add the presentation select box. 120 '<input type="checkbox" class="{{CSS.IMAGEPRESENTATION}}" id="{{elementid}}_{{CSS.IMAGEPRESENTATION}}"/>' + 121 '<label class="sameline" for="{{elementid}}_{{CSS.IMAGEPRESENTATION}}">' + 122 '{{get_string "presentation" component}}' + 123 '</label>' + 124 '<br/>' + 125 126 // Add the size entry boxes. 127 '<label class="sameline" for="{{elementid}}_{{CSS.INPUTSIZE}}">{{get_string "size" component}}</label>' + 128 '<div id="{{elementid}}_{{CSS.INPUTSIZE}}" class="{{CSS.INPUTSIZE}}">' + 129 '<label class="accesshide" for="{{elementid}}_{{CSS.INPUTWIDTH}}">{{get_string "width" component}}</label>' + 130 '<input type="text" class="{{CSS.INPUTWIDTH}} input-mini" id="{{elementid}}_{{CSS.INPUTWIDTH}}" size="4"/> x ' + 131 132 // Add the height entry box. 133 '<label class="accesshide" for="{{elementid}}_{{CSS.INPUTHEIGHT}}">{{get_string "height" component}}</label>' + 134 '<input type="text" class="{{CSS.INPUTHEIGHT}} input-mini" id="{{elementid}}_{{CSS.INPUTHEIGHT}}" size="4"/>' + 135 136 // Add the constrain checkbox. 137 '<input type="checkbox" class="{{CSS.INPUTCONSTRAIN}} sameline" id="{{elementid}}_{{CSS.INPUTCONSTRAIN}}"/>' + 138 '<label for="{{elementid}}_{{CSS.INPUTCONSTRAIN}}">{{get_string "constrain" component}}</label>' + 139 '</div>' + 140 141 // Add the alignment selector. 142 '<label class="sameline" for="{{elementid}}_{{CSS.INPUTALIGNMENT}}">{{get_string "alignment" component}}</label>' + 143 '<select class="{{CSS.INPUTALIGNMENT}}" id="{{elementid}}_{{CSS.INPUTALIGNMENT}}">' + 144 '{{#each alignments}}' + 145 '<option value="{{value}}:{{name}};">{{get_string str ../component}}</option>' + 146 '{{/each}}' + 147 '</select>' + 148 // Hidden input to store custom styles. 149 '<input type="hidden" class="{{CSS.INPUTCUSTOMSTYLE}}"/>' + 150 '<br/>' + 151 152 // Add the image preview. 153 '<div class="mdl-align">' + 154 '<div class="{{CSS.IMAGEPREVIEWBOX}}">' + 155 '<img src="#" class="{{CSS.IMAGEPREVIEW}}" alt="" style="display: none;"/>' + 156 '</div>' + 157 158 // Add the submit button and close the form. 159 '<button class="{{CSS.INPUTSUBMIT}}" type="submit">{{get_string "saveimage" component}}</button>' + 160 '</div>' + 161 '</form>', 162 163 IMAGETEMPLATE = '' + 164 '<img src="{{url}}" alt="{{alt}}" ' + 165 '{{#if width}}width="{{width}}" {{/if}}' + 166 '{{#if height}}height="{{height}}" {{/if}}' + 167 '{{#if presentation}}role="presentation" {{/if}}' + 168 'style="{{alignment}}{{margin}}{{customstyle}}"' + 169 '{{#if classlist}}class="{{classlist}}" {{/if}}' + 170 '{{#if id}}id="{{id}}" {{/if}}' + 171 '/>'; 172 173 Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { 174 /** 175 * A reference to the current selection at the time that the dialogue 176 * was opened. 177 * 178 * @property _currentSelection 179 * @type Range 180 * @private 181 */ 182 _currentSelection: null, 183 184 /** 185 * The most recently selected image. 186 * 187 * @param _selectedImage 188 * @type Node 189 * @private 190 */ 191 _selectedImage: null, 192 193 /** 194 * A reference to the currently open form. 195 * 196 * @param _form 197 * @type Node 198 * @private 199 */ 200 _form: null, 201 202 /** 203 * The dimensions of the raw image before we manipulate it. 204 * 205 * @param _rawImageDimensions 206 * @type Object 207 * @private 208 */ 209 _rawImageDimensions: null, 210 211 initializer: function() { 212 213 this.addButton({ 214 icon: 'e/insert_edit_image', 215 callback: this._displayDialogue, 216 tags: 'img', 217 tagMatchRequiresAll: false 218 }); 219 this.editor.delegate('dblclick', this._displayDialogue, 'img', this); 220 this.editor.delegate('click', this._handleClick, 'img', this); 221 this.editor.on('drop', this._handleDragDrop, this); 222 223 // e.preventDefault needed to stop the default event from clobbering the desired behaviour in some browsers. 224 this.editor.on('dragover', function(e) { 225 e.preventDefault(); 226 }, this); 227 this.editor.on('dragenter', function(e) { 228 e.preventDefault(); 229 }, this); 230 }, 231 232 /** 233 * Handle a drag and drop event with an image. 234 * 235 * @method _handleDragDrop 236 * @param {EventFacade} e 237 * @private 238 */ 239 _handleDragDrop: function(e) { 240 241 var self = this, 242 host = this.get('host'), 243 template = Y.Handlebars.compile(IMAGETEMPLATE); 244 245 host.saveSelection(); 246 e = e._event; 247 248 // Only handle the event if an image file was dropped in. 249 var handlesDataTransfer = (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length); 250 if (handlesDataTransfer && /^image\//.test(e.dataTransfer.files[0].type)) { 251 252 var options = host.get('filepickeroptions').image, 253 savepath = (options.savepath === undefined) ? '/' : options.savepath, 254 formData = new FormData(), 255 timestamp = 0, 256 uploadid = "", 257 xhr = new XMLHttpRequest(), 258 imagehtml = "", 259 keys = Object.keys(options.repositories); 260 261 e.preventDefault(); 262 e.stopPropagation(); 263 formData.append('repo_upload_file', e.dataTransfer.files[0]); 264 formData.append('itemid', options.itemid); 265 266 // List of repositories is an object rather than an array. This makes iteration more awkward. 267 for (var i = 0; i < keys.length; i++) { 268 if (options.repositories[keys[i]].type === 'upload') { 269 formData.append('repo_id', options.repositories[keys[i]].id); 270 break; 271 } 272 } 273 formData.append('env', options.env); 274 formData.append('sesskey', M.cfg.sesskey); 275 formData.append('client_id', options.client_id); 276 formData.append('savepath', savepath); 277 formData.append('ctx_id', options.context.id); 278 279 // Insert spinner as a placeholder. 280 timestamp = new Date().getTime(); 281 uploadid = 'moodleimage_' + Math.round(Math.random() * 100000) + '-' + timestamp; 282 host.focus(); 283 host.restoreSelection(); 284 imagehtml = template({ 285 url: M.util.image_url("i/loading_small", 'moodle'), 286 alt: M.util.get_string('uploading', COMPONENTNAME), 287 id: uploadid 288 }); 289 host.insertContentAtFocusPoint(imagehtml); 290 self.markUpdated(); 291 292 // Kick off a XMLHttpRequest. 293 xhr.onreadystatechange = function() { 294 var placeholder = self.editor.one('#' + uploadid), 295 result, 296 file, 297 newhtml, 298 newimage; 299 300 if (xhr.readyState === 4) { 301 if (xhr.status === 200) { 302 result = JSON.parse(xhr.responseText); 303 if (result) { 304 if (result.error) { 305 if (placeholder) { 306 placeholder.remove(true); 307 } 308 return new M.core.ajaxException(result); 309 } 310 311 file = result; 312 if (result.event && result.event === 'fileexists') { 313 // A file with this name is already in use here - rename to avoid conflict. 314 // Chances are, it's a different image (stored in a different folder on the user's computer). 315 // If the user wants to reuse an existing image, they can copy/paste it within the editor. 316 file = result.newfile; 317 } 318 319 // Replace placeholder with actual image. 320 newhtml = template({ 321 url: file.url, 322 presentation: true 323 }); 324 newimage = Y.Node.create(newhtml); 325 if (placeholder) { 326 placeholder.replace(newimage); 327 } else { 328 self.editor.appendChild(newimage); 329 } 330 self.markUpdated(); 331 } 332 } else { 333 Y.use('moodle-core-notification-alert', function() { 334 new M.core.alert({message: M.util.get_string('servererror', 'moodle')}); 335 }); 336 if (placeholder) { 337 placeholder.remove(true); 338 } 339 } 340 } 341 }; 342 xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true); 343 xhr.send(formData); 344 return false; 345 } 346 347 }, 348 349 /** 350 * Handle a click on an image. 351 * 352 * @method _handleClick 353 * @param {EventFacade} e 354 * @private 355 */ 356 _handleClick: function(e) { 357 var image = e.target; 358 359 var selection = this.get('host').getSelectionFromNode(image); 360 if (this.get('host').getSelection() !== selection) { 361 this.get('host').setSelection(selection); 362 } 363 }, 364 365 /** 366 * Display the image editing tool. 367 * 368 * @method _displayDialogue 369 * @private 370 */ 371 _displayDialogue: function() { 372 // Store the current selection. 373 this._currentSelection = this.get('host').getSelection(); 374 if (this._currentSelection === false) { 375 return; 376 } 377 378 // Reset the image dimensions. 379 this._rawImageDimensions = null; 380 381 var dialogue = this.getDialogue({ 382 headerContent: M.util.get_string('imageproperties', COMPONENTNAME), 383 width: '480px', 384 focusAfterHide: true, 385 focusOnShowSelector: SELECTORS.INPUTURL 386 }); 387 388 // Set the dialogue content, and then show the dialogue. 389 dialogue.set('bodyContent', this._getDialogueContent()) 390 .show(); 391 }, 392 393 /** 394 * Set the inputs for width and height if they are not set, and calculate 395 * if the constrain checkbox should be checked or not. 396 * 397 * @method _loadPreviewImage 398 * @param {String} url 399 * @private 400 */ 401 _loadPreviewImage: function(url) { 402 var image = new Image(); 403 var self = this; 404 405 image.onerror = function() { 406 var preview = self._form.one('.' + CSS.IMAGEPREVIEW); 407 preview.setStyles({ 408 'display': 'none' 409 }); 410 411 // Centre the dialogue when clearing the image preview. 412 self.getDialogue().centerDialogue(); 413 }; 414 415 image.onload = function() { 416 var input, currentwidth, currentheight, widthRatio, heightRatio; 417 418 self._rawImageDimensions = { 419 width: this.width, 420 height: this.height 421 }; 422 423 input = self._form.one('.' + CSS.INPUTWIDTH); 424 currentwidth = input.get('value'); 425 if (currentwidth === '') { 426 input.set('value', this.width); 427 currentwidth = "" + this.width; 428 } 429 input = self._form.one('.' + CSS.INPUTHEIGHT); 430 currentheight = input.get('value'); 431 if (currentheight === '') { 432 input.set('value', this.height); 433 currentheight = "" + this.height; 434 } 435 input = self._form.one('.' + CSS.IMAGEPREVIEW); 436 input.setAttribute('src', this.src); 437 input.setStyles({ 438 'display': 'inline' 439 }); 440 441 input = self._form.one('.' + CSS.INPUTCONSTRAIN); 442 if (currentwidth.match(REGEX.ISPERCENT) && currentheight.match(REGEX.ISPERCENT)) { 443 input.set('checked', currentwidth === currentheight); 444 } else { 445 if (this.width === 0) { 446 this.width = 1; 447 } 448 if (this.height === 0) { 449 this.height = 1; 450 } 451 // This is the same as comparing to 3 decimal places. 452 widthRatio = Math.round(1000 * parseInt(currentwidth, 10) / this.width); 453 heightRatio = Math.round(1000 * parseInt(currentheight, 10) / this.height); 454 input.set('checked', widthRatio === heightRatio); 455 } 456 457 // Apply the image sizing. 458 self._autoAdjustSize(self); 459 460 // Centre the dialogue once the preview image has loaded. 461 self.getDialogue().centerDialogue(); 462 }; 463 464 image.src = url; 465 }, 466 467 /** 468 * Return the dialogue content for the tool, attaching any required 469 * events. 470 * 471 * @method _getDialogueContent 472 * @return {Node} The content to place in the dialogue. 473 * @private 474 */ 475 _getDialogueContent: function() { 476 var template = Y.Handlebars.compile(TEMPLATE), 477 canShowFilepicker = this.get('host').canShowFilepicker('image'), 478 content = Y.Node.create(template({ 479 elementid: this.get('host').get('elementid'), 480 CSS: CSS, 481 component: COMPONENTNAME, 482 showFilepicker: canShowFilepicker, 483 alignments: ALIGNMENTS 484 })); 485 486 this._form = content; 487 488 // Configure the view of the current image. 489 this._applyImageProperties(this._form); 490 491 this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this); 492 this._form.one('.' + CSS.IMAGEPRESENTATION).on('change', this._updateWarning, this); 493 this._form.one('.' + CSS.INPUTALT).on('change', this._updateWarning, this); 494 this._form.one('.' + CSS.INPUTWIDTH).on('blur', this._autoAdjustSize, this); 495 this._form.one('.' + CSS.INPUTHEIGHT).on('blur', this._autoAdjustSize, this, true); 496 this._form.one('.' + CSS.INPUTCONSTRAIN).on('change', function(event) { 497 if (event.target.get('checked')) { 498 this._autoAdjustSize(event); 499 } 500 }, this); 501 this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this); 502 this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this); 503 504 if (canShowFilepicker) { 505 this._form.one('.' + CSS.IMAGEBROWSER).on('click', function() { 506 this.get('host').showFilepicker('image', this._filepickerCallback, this); 507 }, this); 508 } 509 510 return content; 511 }, 512 513 _autoAdjustSize: function(e, forceHeight) { 514 forceHeight = forceHeight || false; 515 516 var keyField = this._form.one('.' + CSS.INPUTWIDTH), 517 keyFieldType = 'width', 518 subField = this._form.one('.' + CSS.INPUTHEIGHT), 519 subFieldType = 'height', 520 constrainField = this._form.one('.' + CSS.INPUTCONSTRAIN), 521 keyFieldValue = keyField.get('value'), 522 subFieldValue = subField.get('value'), 523 imagePreview = this._form.one('.' + CSS.IMAGEPREVIEW), 524 rawPercentage, 525 rawSize; 526 527 // If we do not know the image size, do not do anything. 528 if (!this._rawImageDimensions) { 529 return; 530 } 531 532 // Set the width back to default if it is empty. 533 if (keyFieldValue === '') { 534 keyFieldValue = this._rawImageDimensions[keyFieldType]; 535 keyField.set('value', keyFieldValue); 536 keyFieldValue = keyField.get('value'); 537 } 538 539 // Clear the existing preview sizes. 540 imagePreview.setStyles({ 541 width: null, 542 height: null 543 }); 544 545 // Now update with the new values. 546 if (!constrainField.get('checked')) { 547 // We are not keeping the image proportion - update the preview accordingly. 548 549 // Width. 550 if (keyFieldValue.match(REGEX.ISPERCENT)) { 551 rawPercentage = parseInt(keyFieldValue, 10); 552 rawSize = this._rawImageDimensions.width / 100 * rawPercentage; 553 imagePreview.setStyle('width', rawSize + 'px'); 554 } else { 555 imagePreview.setStyle('width', keyFieldValue + 'px'); 556 } 557 558 // Height. 559 if (subFieldValue.match(REGEX.ISPERCENT)) { 560 rawPercentage = parseInt(subFieldValue, 10); 561 rawSize = this._rawImageDimensions.height / 100 * rawPercentage; 562 imagePreview.setStyle('height', rawSize + 'px'); 563 } else { 564 imagePreview.setStyle('height', subFieldValue + 'px'); 565 } 566 } else { 567 // We are keeping the image in proportion. 568 if (forceHeight) { 569 // By default we update based on width. Swap the key and sub fields around to achieve a height-based scale. 570 var _temporaryValue; 571 _temporaryValue = keyField; 572 keyField = subField; 573 subField = _temporaryValue; 574 575 _temporaryValue = keyFieldType; 576 keyFieldType = subFieldType; 577 subFieldType = _temporaryValue; 578 579 _temporaryValue = keyFieldValue; 580 keyFieldValue = subFieldValue; 581 subFieldValue = _temporaryValue; 582 } 583 584 if (keyFieldValue.match(REGEX.ISPERCENT)) { 585 // This is a percentage based change. Copy it verbatim. 586 subFieldValue = keyFieldValue; 587 588 // Set the width to the calculated pixel width. 589 rawPercentage = parseInt(keyFieldValue, 10); 590 rawSize = this._rawImageDimensions.width / 100 * rawPercentage; 591 592 // And apply the width/height to the container. 593 imagePreview.setStyle('width', rawSize); 594 rawSize = this._rawImageDimensions.height / 100 * rawPercentage; 595 imagePreview.setStyle('height', rawSize); 596 } else { 597 // Calculate the scaled subFieldValue from the keyFieldValue. 598 subFieldValue = Math.round((keyFieldValue / this._rawImageDimensions[keyFieldType]) * 599 this._rawImageDimensions[subFieldType]); 600 601 if (forceHeight) { 602 imagePreview.setStyles({ 603 'width': subFieldValue, 604 'height': keyFieldValue 605 }); 606 } else { 607 imagePreview.setStyles({ 608 'width': keyFieldValue, 609 'height': subFieldValue 610 }); 611 } 612 } 613 614 // Update the subField's value within the form to reflect the changes. 615 subField.set('value', subFieldValue); 616 } 617 }, 618 619 /** 620 * Update the dialogue after an image was selected in the File Picker. 621 * 622 * @method _filepickerCallback 623 * @param {object} params The parameters provided by the filepicker 624 * containing information about the image. 625 * @private 626 */ 627 _filepickerCallback: function(params) { 628 if (params.url !== '') { 629 var input = this._form.one('.' + CSS.INPUTURL); 630 input.set('value', params.url); 631 632 // Auto set the width and height. 633 this._form.one('.' + CSS.INPUTWIDTH).set('value', ''); 634 this._form.one('.' + CSS.INPUTHEIGHT).set('value', ''); 635 636 // Load the preview image. 637 this._loadPreviewImage(params.url); 638 } 639 }, 640 641 /** 642 * Applies properties of an existing image to the image dialogue for editing. 643 * 644 * @method _applyImageProperties 645 * @param {Node} form 646 * @private 647 */ 648 _applyImageProperties: function(form) { 649 var properties = this._getSelectedImageProperties(), 650 img = form.one('.' + CSS.IMAGEPREVIEW), 651 i, 652 css; 653 654 if (properties === false) { 655 img.setStyle('display', 'none'); 656 // Set the default alignment. 657 for (i in ALIGNMENTS) { 658 if (ALIGNMENTS[i].isDefault === true) { 659 css = ALIGNMENTS[i].value + ':' + ALIGNMENTS[i].name + ';'; 660 form.one('.' + CSS.INPUTALIGNMENT).set('value', css); 661 } 662 } 663 // Remove the custom style option if this is a new image. 664 form.one('.' + CSS.INPUTALIGNMENT).getDOMNode().options.remove(ALIGNMENTS.length - 1); 665 return; 666 } 667 668 if (properties.align) { 669 form.one('.' + CSS.INPUTALIGNMENT).set('value', properties.align); 670 // Remove the custom style option if we have a standard alignment. 671 form.one('.' + CSS.INPUTALIGNMENT).getDOMNode().options.remove(ALIGNMENTS.length - 1); 672 } else { 673 form.one('.' + CSS.INPUTALIGNMENT).set('value', 'style:customstyle;'); 674 } 675 if (properties.customstyle) { 676 form.one('.' + CSS.INPUTCUSTOMSTYLE).set('value', properties.customstyle); 677 } 678 if (properties.width) { 679 form.one('.' + CSS.INPUTWIDTH).set('value', properties.width); 680 } 681 if (properties.height) { 682 form.one('.' + CSS.INPUTHEIGHT).set('value', properties.height); 683 } 684 if (properties.alt) { 685 form.one('.' + CSS.INPUTALT).set('value', properties.alt); 686 } 687 if (properties.src) { 688 form.one('.' + CSS.INPUTURL).set('value', properties.src); 689 this._loadPreviewImage(properties.src); 690 } 691 if (properties.presentation) { 692 form.one('.' + CSS.IMAGEPRESENTATION).set('checked', 'checked'); 693 } 694 695 // Update the image preview based on the form properties. 696 this._autoAdjustSize(); 697 }, 698 699 /** 700 * Gets the properties of the currently selected image. 701 * 702 * The first image only if multiple images are selected. 703 * 704 * @method _getSelectedImageProperties 705 * @return {object} 706 * @private 707 */ 708 _getSelectedImageProperties: function() { 709 var properties = { 710 src: null, 711 alt: null, 712 width: null, 713 height: null, 714 align: '', 715 presentation: false 716 }, 717 718 // Get the current selection. 719 images = this.get('host').getSelectedNodes(), 720 i, 721 width, 722 height, 723 style, 724 css, 725 image, 726 margin; 727 728 if (images) { 729 images = images.filter('img'); 730 } 731 732 if (images && images.size()) { 733 image = images.item(0); 734 this._selectedImage = image; 735 736 style = image.getAttribute('style'); 737 properties.customstyle = style; 738 style = style.replace(/ /g, ''); 739 740 width = image.getAttribute('width'); 741 if (!width.match(REGEX.ISPERCENT)) { 742 width = parseInt(width, 10); 743 } 744 height = image.getAttribute('height'); 745 if (!height.match(REGEX.ISPERCENT)) { 746 height = parseInt(height, 10); 747 } 748 749 if (width !== 0) { 750 properties.width = width; 751 } 752 if (height !== 0) { 753 properties.height = height; 754 } 755 for (i in ALIGNMENTS) { 756 css = ALIGNMENTS[i].value + ':' + ALIGNMENTS[i].name + ';'; 757 if (style.indexOf(css) !== -1) { 758 margin = 'margin:' + ALIGNMENTS[i].margin + ';'; 759 margin = margin.replace(/ /g, ''); 760 // Must match alignment and margins - otherwise custom style is selected. 761 if (style.indexOf(margin) !== -1) { 762 properties.align = css; 763 break; 764 } 765 } 766 } 767 properties.src = image.getAttribute('src'); 768 properties.alt = image.getAttribute('alt') || ''; 769 properties.presentation = (image.get('role') === 'presentation'); 770 return properties; 771 } 772 773 // No image selected - clean up. 774 this._selectedImage = null; 775 return false; 776 }, 777 778 /** 779 * Update the form when the URL was changed. This includes updating the 780 * height, width, and image preview. 781 * 782 * @method _urlChanged 783 * @private 784 */ 785 _urlChanged: function() { 786 var input = this._form.one('.' + CSS.INPUTURL); 787 788 if (input.get('value') !== '') { 789 // Load the preview image. 790 this._loadPreviewImage(input.get('value')); 791 } 792 }, 793 794 /** 795 * Update the image in the contenteditable. 796 * 797 * @method _setImage 798 * @param {EventFacade} e 799 * @private 800 */ 801 _setImage: function(e) { 802 var form = this._form, 803 url = form.one('.' + CSS.INPUTURL).get('value'), 804 alt = form.one('.' + CSS.INPUTALT).get('value'), 805 width = form.one('.' + CSS.INPUTWIDTH).get('value'), 806 height = form.one('.' + CSS.INPUTHEIGHT).get('value'), 807 alignment = form.one('.' + CSS.INPUTALIGNMENT).get('value'), 808 margin = '', 809 presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked'), 810 constrain = form.one('.' + CSS.INPUTCONSTRAIN).get('checked'), 811 imagehtml, 812 customstyle = '', 813 i, 814 css, 815 classlist = [], 816 host = this.get('host'); 817 818 e.preventDefault(); 819 820 // Check if there are any accessibility issues. 821 if (this._updateWarning()) { 822 return; 823 } 824 825 // Focus on the editor in preparation for inserting the image. 826 host.focus(); 827 if (url !== '') { 828 if (this._selectedImage) { 829 host.setSelection(host.getSelectionFromNode(this._selectedImage)); 830 } else { 831 host.setSelection(this._currentSelection); 832 } 833 834 if (alignment === 'style:customstyle;') { 835 alignment = ''; 836 customstyle = form.one('.' + CSS.INPUTCUSTOMSTYLE).get('value'); 837 } else { 838 for (i in ALIGNMENTS) { 839 css = ALIGNMENTS[i].value + ':' + ALIGNMENTS[i].name + ';'; 840 if (alignment === css) { 841 margin = ' margin: ' + ALIGNMENTS[i].margin + ';'; 842 } 843 } 844 } 845 846 if (constrain) { 847 classlist.push(CSS.RESPONSIVE); 848 } 849 850 if (!width.match(REGEX.ISPERCENT) && isNaN(parseInt(width, 10))) { 851 form.one('.' + CSS.INPUTWIDTH).focus(); 852 return; 853 } 854 if (!height.match(REGEX.ISPERCENT) && isNaN(parseInt(height, 10))) { 855 form.one('.' + CSS.INPUTHEIGHT).focus(); 856 return; 857 } 858 859 var template = Y.Handlebars.compile(IMAGETEMPLATE); 860 imagehtml = template({ 861 url: url, 862 alt: alt, 863 width: width, 864 height: height, 865 presentation: presentation, 866 alignment: alignment, 867 margin: margin, 868 customstyle: customstyle, 869 classlist: classlist.join(' ') 870 }); 871 872 this.get('host').insertContentAtFocusPoint(imagehtml); 873 874 this.markUpdated(); 875 } 876 877 this.getDialogue({ 878 focusAfterHide: null 879 }).hide(); 880 881 }, 882 883 /** 884 * Update the alt text warning live. 885 * 886 * @method _updateWarning 887 * @return {boolean} whether a warning should be displayed. 888 * @private 889 */ 890 _updateWarning: function() { 891 var form = this._form, 892 state = true, 893 alt = form.one('.' + CSS.INPUTALT).get('value'), 894 presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked'); 895 if (alt === '' && !presentation) { 896 form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'block'); 897 form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', true); 898 form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true); 899 state = true; 900 } else { 901 form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'none'); 902 form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', false); 903 form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false); 904 state = false; 905 } 906 this.getDialogue().centerDialogue(); 907 return state; 908 } 909 }); 910 911 912 }, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |