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