1 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * @copyright 2013 Damyon Wiese <damyon@moodle.com>
19 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 * @module moodle-atto_image_alignment-button
27 * Atto image selection tool.
29 * @namespace M.atto_image
31 * @extends M.editor_atto.EditorPlugin
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 ALIGNSETTINGS: 'atto_image_button'
53 INPUTURL: '.' + CSS.INPUTURL
56 // Vertical alignment.
58 name: 'verticalAlign',
63 name: 'verticalAlign',
64 str: 'alignment_middle',
68 name: 'verticalAlign',
69 str: 'alignment_bottom',
78 str: 'alignment_left',
83 str: 'alignment_right',
93 COMPONENTNAME = 'atto_image',
96 '<form class="atto_form">' +
98 // Add the repository browser button.
99 '{{#if showFilepicker}}' +
100 '<div class="m-b-1">' +
101 '<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
102 '<div class="input-group input-append w-100">' +
103 '<input class="form-control {{CSS.INPUTURL}}" type="url" ' +
104 'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
105 '<span class="input-group-append">' +
106 '<button class="btn btn-default {{CSS.IMAGEBROWSER}}" type="button">' +
107 '{{get_string "browserepositories" component}}</button>' +
112 '<div class="m-b-1">' +
113 '<label for="{{elementid}}_{{CSS.INPUTURL}}">{{get_string "enterurl" component}}</label>' +
114 '<input class="form-control fullwidth {{CSS.INPUTURL}}" type="url" ' +
115 'id="{{elementid}}_{{CSS.INPUTURL}}" size="32"/>' +
120 '<div style="display:none" role="alert" class="alert alert-warning m-b-1 {{CSS.IMAGEALTWARNING}}">' +
121 '{{get_string "presentationoraltrequired" component}}' +
123 '<div class="m-b-1">' +
124 '<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' +
125 '<input class="form-control fullwidth {{CSS.INPUTALT}}" type="text" value="" ' +
126 'id="{{elementid}}_{{CSS.INPUTALT}}" size="32"/>' +
128 // Add the presentation select box.
129 '<div class="form-check">' +
130 '<input type="checkbox" class="form-check-input {{CSS.IMAGEPRESENTATION}}" ' +
131 'id="{{elementid}}_{{CSS.IMAGEPRESENTATION}}"/>' +
132 '<label class="form-check-label" for="{{elementid}}_{{CSS.IMAGEPRESENTATION}}">' +
133 '{{get_string "presentation" component}}' +
138 // Add the size entry boxes.
139 '<div class="m-b-1">' +
140 '<label class="" for="{{elementid}}_{{CSS.INPUTSIZE}}">{{get_string "size" component}}</label>' +
141 '<div id="{{elementid}}_{{CSS.INPUTSIZE}}" class="form-inline {{CSS.INPUTSIZE}}">' +
142 '<label class="accesshide" for="{{elementid}}_{{CSS.INPUTWIDTH}}">{{get_string "width" component}}</label>' +
143 '<input type="text" class="form-control m-r-1 input-mini {{CSS.INPUTWIDTH}}" ' +
144 'id="{{elementid}}_{{CSS.INPUTWIDTH}}" size="4"/> x' +
146 // Add the height entry box.
147 '<label class="accesshide" for="{{elementid}}_{{CSS.INPUTHEIGHT}}">{{get_string "height" component}}</label>' +
148 '<input type="text" class="form-control m-l-1 input-mini {{CSS.INPUTHEIGHT}}" ' +
149 'id="{{elementid}}_{{CSS.INPUTHEIGHT}}" size="4"/>' +
151 // Add the constrain checkbox.
152 '<div class="form-check m-l-2">' +
153 '<input type="checkbox" class="form-check-input {{CSS.INPUTCONSTRAIN}}" ' +
154 'id="{{elementid}}_{{CSS.INPUTCONSTRAIN}}"/>' +
155 '<label class="form-check-label" for="{{elementid}}_{{CSS.INPUTCONSTRAIN}}">' +
156 '{{get_string "constrain" component}}</label>' +
161 // Add the alignment selector.
162 '<div class="form-inline m-b-1">' +
163 '<label class="for="{{elementid}}_{{CSS.INPUTALIGNMENT}}">{{get_string "alignment" component}}</label>' +
164 '<select class="custom-select {{CSS.INPUTALIGNMENT}}" id="{{elementid}}_{{CSS.INPUTALIGNMENT}}">' +
165 '{{#each alignments}}' +
166 '<option value="{{value}}">{{get_string str ../component}}</option>' +
170 // Hidden input to store custom styles.
171 '<input type="hidden" class="{{CSS.INPUTCUSTOMSTYLE}}"/>' +
174 // Add the image preview.
175 '<div class="mdl-align">' +
176 '<div class="{{CSS.IMAGEPREVIEWBOX}}">' +
177 '<img src="#" class="{{CSS.IMAGEPREVIEW}}" alt="" style="display: none;"/>' +
180 // Add the submit button and close the form.
181 '<button class="btn btn-default {{CSS.INPUTSUBMIT}}" type="submit">{{get_string "saveimage" component}}</button>' +
186 '<img src="{{url}}" alt="{{alt}}" ' +
187 '{{#if width}}width="{{width}}" {{/if}}' +
188 '{{#if height}}height="{{height}}" {{/if}}' +
189 '{{#if presentation}}role="presentation" {{/if}}' +
190 '{{#if customstyle}}style="{{customstyle}}" {{/if}}' +
191 '{{#if classlist}}class="{{classlist}}" {{/if}}' +
192 '{{#if id}}id="{{id}}" {{/if}}' +
195 Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
197 * A reference to the current selection at the time that the dialogue
200 * @property _currentSelection
204 _currentSelection: null,
207 * The most recently selected image.
209 * @param _selectedImage
213 _selectedImage: null,
216 * A reference to the currently open form.
225 * The dimensions of the raw image before we manipulate it.
227 * @param _rawImageDimensions
231 _rawImageDimensions: null,
233 initializer: function() {
236 icon: 'e/insert_edit_image',
237 callback: this._displayDialogue,
239 tagMatchRequiresAll: false
241 this.editor.delegate('dblclick', this._displayDialogue, 'img', this);
242 this.editor.delegate('click', this._handleClick, 'img', this);
243 this.editor.on('drop', this._handleDragDrop, this);
245 // e.preventDefault needed to stop the default event from clobbering the desired behaviour in some browsers.
246 this.editor.on('dragover', function(e) {
249 this.editor.on('dragenter', function(e) {
255 * Handle a drag and drop event with an image.
257 * @method _handleDragDrop
258 * @param {EventFacade} e
262 _handleDragDrop: function(e) {
265 host = this.get('host'),
266 template = Y.Handlebars.compile(IMAGETEMPLATE);
268 host.saveSelection();
271 // Only handle the event if an image file was dropped in.
272 var handlesDataTransfer = (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length);
273 if (handlesDataTransfer && /^image\//.test(e.dataTransfer.files[0].type)) {
275 var options = host.get('filepickeroptions').image,
276 savepath = (options.savepath === undefined) ? '/' : options.savepath,
277 formData = new FormData(),
280 xhr = new XMLHttpRequest(),
282 keys = Object.keys(options.repositories);
286 formData.append('repo_upload_file', e.dataTransfer.files[0]);
287 formData.append('itemid', options.itemid);
289 // List of repositories is an object rather than an array. This makes iteration more awkward.
290 for (var i = 0; i < keys.length; i++) {
291 if (options.repositories[keys[i]].type === 'upload') {
292 formData.append('repo_id', options.repositories[keys[i]].id);
296 formData.append('env', options.env);
297 formData.append('sesskey', M.cfg.sesskey);
298 formData.append('client_id', options.client_id);
299 formData.append('savepath', savepath);
300 formData.append('ctx_id', options.context.id);
302 // Insert spinner as a placeholder.
303 timestamp = new Date().getTime();
304 uploadid = 'moodleimage_' + Math.round(Math.random() * 100000) + '-' + timestamp;
306 host.restoreSelection();
307 imagehtml = template({
308 url: M.util.image_url("i/loading_small", 'moodle'),
309 alt: M.util.get_string('uploading', COMPONENTNAME),
312 host.insertContentAtFocusPoint(imagehtml);
315 // Kick off a XMLHttpRequest.
316 xhr.onreadystatechange = function() {
317 var placeholder = self.editor.one('#' + uploadid),
323 if (xhr.readyState === 4) {
324 if (xhr.status === 200) {
325 result = JSON.parse(xhr.responseText);
329 placeholder.remove(true);
331 return new M.core.ajaxException(result);
335 if (result.event && result.event === 'fileexists') {
336 // A file with this name is already in use here - rename to avoid conflict.
337 // Chances are, it's a different image (stored in a different folder on the user's computer).
338 // If the user wants to reuse an existing image, they can copy/paste it within the editor.
339 file = result.newfile;
342 // Replace placeholder with actual image.
347 newimage = Y.Node.create(newhtml);
349 placeholder.replace(newimage);
351 self.editor.appendChild(newimage);
356 Y.use('moodle-core-notification-alert', function() {
357 new M.core.alert({message: M.util.get_string('servererror', 'moodle')});
360 placeholder.remove(true);
365 xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true);
373 * Handle a click on an image.
375 * @method _handleClick
376 * @param {EventFacade} e
379 _handleClick: function(e) {
380 var image = e.target;
382 var selection = this.get('host').getSelectionFromNode(image);
383 if (this.get('host').getSelection() !== selection) {
384 this.get('host').setSelection(selection);
389 * Display the image editing tool.
391 * @method _displayDialogue
394 _displayDialogue: function() {
395 // Store the current selection.
396 this._currentSelection = this.get('host').getSelection();
397 if (this._currentSelection === false) {
401 // Reset the image dimensions.
402 this._rawImageDimensions = null;
404 var dialogue = this.getDialogue({
405 headerContent: M.util.get_string('imageproperties', COMPONENTNAME),
407 focusAfterHide: true,
408 focusOnShowSelector: SELECTORS.INPUTURL
411 // Set the dialogue content, and then show the dialogue.
412 dialogue.set('bodyContent', this._getDialogueContent())
417 * Set the inputs for width and height if they are not set, and calculate
418 * if the constrain checkbox should be checked or not.
420 * @method _loadPreviewImage
421 * @param {String} url
424 _loadPreviewImage: function(url) {
425 var image = new Image();
428 image.onerror = function() {
429 var preview = self._form.one('.' + CSS.IMAGEPREVIEW);
434 // Centre the dialogue when clearing the image preview.
435 self.getDialogue().centerDialogue();
438 image.onload = function() {
439 var input, currentwidth, currentheight, widthRatio, heightRatio;
441 self._rawImageDimensions = {
446 input = self._form.one('.' + CSS.INPUTWIDTH);
447 currentwidth = input.get('value');
448 if (currentwidth === '') {
449 input.set('value', this.width);
450 currentwidth = "" + this.width;
452 input = self._form.one('.' + CSS.INPUTHEIGHT);
453 currentheight = input.get('value');
454 if (currentheight === '') {
455 input.set('value', this.height);
456 currentheight = "" + this.height;
458 input = self._form.one('.' + CSS.IMAGEPREVIEW);
459 input.setAttribute('src', this.src);
464 input = self._form.one('.' + CSS.INPUTCONSTRAIN);
465 if (currentwidth.match(REGEX.ISPERCENT) && currentheight.match(REGEX.ISPERCENT)) {
466 input.set('checked', currentwidth === currentheight);
468 if (this.width === 0) {
471 if (this.height === 0) {
474 // This is the same as comparing to 3 decimal places.
475 widthRatio = Math.round(1000 * parseInt(currentwidth, 10) / this.width);
476 heightRatio = Math.round(1000 * parseInt(currentheight, 10) / this.height);
477 input.set('checked', widthRatio === heightRatio);
480 // Apply the image sizing.
481 self._autoAdjustSize(self);
483 // Centre the dialogue once the preview image has loaded.
484 self.getDialogue().centerDialogue();
491 * Return the dialogue content for the tool, attaching any required
494 * @method _getDialogueContent
495 * @return {Node} The content to place in the dialogue.
498 _getDialogueContent: function() {
499 var template = Y.Handlebars.compile(TEMPLATE),
500 canShowFilepicker = this.get('host').canShowFilepicker('image'),
501 content = Y.Node.create(template({
502 elementid: this.get('host').get('elementid'),
504 component: COMPONENTNAME,
505 showFilepicker: canShowFilepicker,
506 alignments: ALIGNMENTS
509 this._form = content;
511 // Configure the view of the current image.
512 this._applyImageProperties(this._form);
514 this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
515 this._form.one('.' + CSS.IMAGEPRESENTATION).on('change', this._updateWarning, this);
516 this._form.one('.' + CSS.INPUTALT).on('change', this._updateWarning, this);
517 this._form.one('.' + CSS.INPUTWIDTH).on('blur', this._autoAdjustSize, this);
518 this._form.one('.' + CSS.INPUTHEIGHT).on('blur', this._autoAdjustSize, this, true);
519 this._form.one('.' + CSS.INPUTCONSTRAIN).on('change', function(event) {
520 if (event.target.get('checked')) {
521 this._autoAdjustSize(event);
524 this._form.one('.' + CSS.INPUTURL).on('blur', this._urlChanged, this);
525 this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setImage, this);
527 if (canShowFilepicker) {
528 this._form.one('.' + CSS.IMAGEBROWSER).on('click', function() {
529 this.get('host').showFilepicker('image', this._filepickerCallback, this);
536 _autoAdjustSize: function(e, forceHeight) {
537 forceHeight = forceHeight || false;
539 var keyField = this._form.one('.' + CSS.INPUTWIDTH),
540 keyFieldType = 'width',
541 subField = this._form.one('.' + CSS.INPUTHEIGHT),
542 subFieldType = 'height',
543 constrainField = this._form.one('.' + CSS.INPUTCONSTRAIN),
544 keyFieldValue = keyField.get('value'),
545 subFieldValue = subField.get('value'),
546 imagePreview = this._form.one('.' + CSS.IMAGEPREVIEW),
550 // If we do not know the image size, do not do anything.
551 if (!this._rawImageDimensions) {
555 // Set the width back to default if it is empty.
556 if (keyFieldValue === '') {
557 keyFieldValue = this._rawImageDimensions[keyFieldType];
558 keyField.set('value', keyFieldValue);
559 keyFieldValue = keyField.get('value');
562 // Clear the existing preview sizes.
563 imagePreview.setStyles({
568 // Now update with the new values.
569 if (!constrainField.get('checked')) {
570 // We are not keeping the image proportion - update the preview accordingly.
573 if (keyFieldValue.match(REGEX.ISPERCENT)) {
574 rawPercentage = parseInt(keyFieldValue, 10);
575 rawSize = this._rawImageDimensions.width / 100 * rawPercentage;
576 imagePreview.setStyle('width', rawSize + 'px');
578 imagePreview.setStyle('width', keyFieldValue + 'px');
582 if (subFieldValue.match(REGEX.ISPERCENT)) {
583 rawPercentage = parseInt(subFieldValue, 10);
584 rawSize = this._rawImageDimensions.height / 100 * rawPercentage;
585 imagePreview.setStyle('height', rawSize + 'px');
587 imagePreview.setStyle('height', subFieldValue + 'px');
590 // We are keeping the image in proportion.
592 // By default we update based on width. Swap the key and sub fields around to achieve a height-based scale.
594 _temporaryValue = keyField;
596 subField = _temporaryValue;
598 _temporaryValue = keyFieldType;
599 keyFieldType = subFieldType;
600 subFieldType = _temporaryValue;
602 _temporaryValue = keyFieldValue;
603 keyFieldValue = subFieldValue;
604 subFieldValue = _temporaryValue;
607 if (keyFieldValue.match(REGEX.ISPERCENT)) {
608 // This is a percentage based change. Copy it verbatim.
609 subFieldValue = keyFieldValue;
611 // Set the width to the calculated pixel width.
612 rawPercentage = parseInt(keyFieldValue, 10);
613 rawSize = this._rawImageDimensions.width / 100 * rawPercentage;
615 // And apply the width/height to the container.
616 imagePreview.setStyle('width', rawSize);
617 rawSize = this._rawImageDimensions.height / 100 * rawPercentage;
618 imagePreview.setStyle('height', rawSize);
620 // Calculate the scaled subFieldValue from the keyFieldValue.
621 subFieldValue = Math.round((keyFieldValue / this._rawImageDimensions[keyFieldType]) *
622 this._rawImageDimensions[subFieldType]);
625 imagePreview.setStyles({
626 'width': subFieldValue,
627 'height': keyFieldValue
630 imagePreview.setStyles({
631 'width': keyFieldValue,
632 'height': subFieldValue
637 // Update the subField's value within the form to reflect the changes.
638 subField.set('value', subFieldValue);
643 * Update the dialogue after an image was selected in the File Picker.
645 * @method _filepickerCallback
646 * @param {object} params The parameters provided by the filepicker
647 * containing information about the image.
650 _filepickerCallback: function(params) {
651 if (params.url !== '') {
652 var input = this._form.one('.' + CSS.INPUTURL);
653 input.set('value', params.url);
655 // Auto set the width and height.
656 this._form.one('.' + CSS.INPUTWIDTH).set('value', '');
657 this._form.one('.' + CSS.INPUTHEIGHT).set('value', '');
659 // Load the preview image.
660 this._loadPreviewImage(params.url);
665 * Applies properties of an existing image to the image dialogue for editing.
667 * @method _applyImageProperties
671 _applyImageProperties: function(form) {
672 var properties = this._getSelectedImageProperties(),
673 img = form.one('.' + CSS.IMAGEPREVIEW);
675 if (properties === false) {
676 img.setStyle('display', 'none');
677 // Set the default alignment.
678 ALIGNMENTS.some(function(alignment) {
679 if (alignment.isDefault) {
680 form.one('.' + CSS.INPUTALIGNMENT).set('value', alignment.value);
690 if (properties.align) {
691 form.one('.' + CSS.INPUTALIGNMENT).set('value', properties.align);
693 if (properties.customstyle) {
694 form.one('.' + CSS.INPUTCUSTOMSTYLE).set('value', properties.customstyle);
696 if (properties.width) {
697 form.one('.' + CSS.INPUTWIDTH).set('value', properties.width);
699 if (properties.height) {
700 form.one('.' + CSS.INPUTHEIGHT).set('value', properties.height);
702 if (properties.alt) {
703 form.one('.' + CSS.INPUTALT).set('value', properties.alt);
705 if (properties.src) {
706 form.one('.' + CSS.INPUTURL).set('value', properties.src);
707 this._loadPreviewImage(properties.src);
709 if (properties.presentation) {
710 form.one('.' + CSS.IMAGEPRESENTATION).set('checked', 'checked');
713 // Update the image preview based on the form properties.
714 this._autoAdjustSize();
718 * Gets the properties of the currently selected image.
720 * The first image only if multiple images are selected.
722 * @method _getSelectedImageProperties
726 _getSelectedImageProperties: function() {
736 // Get the current selection.
737 images = this.get('host').getSelectedNodes(),
744 images = images.filter('img');
747 if (images && images.size()) {
748 image = this._removeLegacyAlignment(images.item(0));
749 this._selectedImage = image;
751 style = image.getAttribute('style');
752 properties.customstyle = style;
754 width = image.getAttribute('width');
755 if (!width.match(REGEX.ISPERCENT)) {
756 width = parseInt(width, 10);
758 height = image.getAttribute('height');
759 if (!height.match(REGEX.ISPERCENT)) {
760 height = parseInt(height, 10);
764 properties.width = width;
767 properties.height = height;
769 this._getAlignmentPropeties(image, properties);
770 properties.src = image.getAttribute('src');
771 properties.alt = image.getAttribute('alt') || '';
772 properties.presentation = (image.get('role') === 'presentation');
776 // No image selected - clean up.
777 this._selectedImage = null;
782 * Sets the alignment of a properties object.
784 * @method _getAlignmentPropeties
785 * @param {Node} image The image that the alignment properties should be found for
786 * @param {Object} properties The properties object that is created in _getSelectedImageProperties()
789 _getAlignmentPropeties: function(image, properties) {
790 var complete = false,
793 // Check for an alignment value.
794 complete = ALIGNMENTS.some(function(alignment) {
795 var classname = this._getAlignmentClass(alignment.value);
796 if (image.hasClass(classname)) {
797 properties.align = alignment.value;
798 Y.log('Found alignment ' + alignment.value, 'debug', 'atto_image-button');
803 if (alignment.isDefault) {
804 defaultAlignment = alignment.value;
810 if (!complete && defaultAlignment) {
811 properties.align = defaultAlignment;
816 * Update the form when the URL was changed. This includes updating the
817 * height, width, and image preview.
819 * @method _urlChanged
822 _urlChanged: function() {
823 var input = this._form.one('.' + CSS.INPUTURL);
825 if (input.get('value') !== '') {
826 // Load the preview image.
827 this._loadPreviewImage(input.get('value'));
832 * Update the image in the contenteditable.
835 * @param {EventFacade} e
838 _setImage: function(e) {
839 var form = this._form,
840 url = form.one('.' + CSS.INPUTURL).get('value'),
841 alt = form.one('.' + CSS.INPUTALT).get('value'),
842 width = form.one('.' + CSS.INPUTWIDTH).get('value'),
843 height = form.one('.' + CSS.INPUTHEIGHT).get('value'),
844 alignment = this._getAlignmentClass(form.one('.' + CSS.INPUTALIGNMENT).get('value')),
845 presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked'),
846 constrain = form.one('.' + CSS.INPUTCONSTRAIN).get('checked'),
848 customstyle = form.one('.' + CSS.INPUTCUSTOMSTYLE).get('value'),
850 host = this.get('host');
854 // Check if there are any accessibility issues.
855 if (this._updateWarning()) {
859 // Focus on the editor in preparation for inserting the image.
862 if (this._selectedImage) {
863 host.setSelection(host.getSelectionFromNode(this._selectedImage));
865 host.setSelection(this._currentSelection);
869 classlist.push(CSS.RESPONSIVE);
872 // Add the alignment class for the image.
873 classlist.push(alignment);
875 if (!width.match(REGEX.ISPERCENT) && isNaN(parseInt(width, 10))) {
876 form.one('.' + CSS.INPUTWIDTH).focus();
879 if (!height.match(REGEX.ISPERCENT) && isNaN(parseInt(height, 10))) {
880 form.one('.' + CSS.INPUTHEIGHT).focus();
884 var template = Y.Handlebars.compile(IMAGETEMPLATE);
885 imagehtml = template({
890 presentation: presentation,
891 customstyle: customstyle,
892 classlist: classlist.join(' ')
895 this.get('host').insertContentAtFocusPoint(imagehtml);
907 * Removes any legacy styles added by previous versions of the atto image button.
909 * @method _removeLegacyAlignment
910 * @param {Y.Node} imageNode
914 _removeLegacyAlignment: function(imageNode) {
915 if (!imageNode.getStyle('margin')) {
916 // There is no margin therefore this cannot match any known alignments.
920 ALIGNMENTS.some(function(alignment) {
921 if (imageNode.getStyle(alignment.name) !== alignment.value) {
922 // The name/value do not match. Skip.
926 var normalisedNode = Y.Node.create('<div>');
927 normalisedNode.setStyle('margin', alignment.margin);
928 if (imageNode.getStyle('margin') !== normalisedNode.getStyle('margin')) {
929 // The margin does not match.
933 Y.log('Legacy alignment found and removed.', 'info', 'atto_image-button');
934 imageNode.addClass(this._getAlignmentClass(alignment.value));
935 imageNode.setStyle(alignment.name, null);
936 imageNode.setStyle('margin', null);
944 _getAlignmentClass: function(alignment) {
945 return CSS.ALIGNSETTINGS + '_' + alignment;
949 * Update the alt text warning live.
951 * @method _updateWarning
952 * @return {boolean} whether a warning should be displayed.
955 _updateWarning: function() {
956 var form = this._form,
958 alt = form.one('.' + CSS.INPUTALT).get('value'),
959 presentation = form.one('.' + CSS.IMAGEPRESENTATION).get('checked');
960 if (alt === '' && !presentation) {
961 form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'block');
962 form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', true);
963 form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', true);
966 form.one('.' + CSS.IMAGEALTWARNING).setStyle('display', 'none');
967 form.one('.' + CSS.INPUTALT).setAttribute('aria-invalid', false);
968 form.one('.' + CSS.IMAGEPRESENTATION).setAttribute('aria-invalid', false);
971 this.getDialogue().centerDialogue();