4 * Copyright, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://www.tinymce.com/license
8 * Contributing: http://www.tinymce.com/contributing
11 /*global tinymce:true */
13 tinymce.PluginManager.add('image', function(editor) {
14 function getImageSize(url, callback) {
15 var img = document.createElement('img');
17 function done(width, height) {
19 img.parentNode.removeChild(img);
22 callback({width: width, height: height});
25 img.onload = function() {
26 done(img.clientWidth, img.clientHeight);
29 img.onerror = function() {
33 var style = img.style;
34 style.visibility = 'hidden';
35 style.position = 'fixed';
36 style.bottom = style.left = 0;
37 style.width = style.height = 'auto';
39 document.body.appendChild(img);
43 function applyPreview(items) {
44 tinymce.each(items, function(item) {
45 item.textStyle = function() {
46 return editor.formatter.getCssText({inline: 'img', classes: [item.value]});
53 function createImageList(callback) {
55 var imageList = editor.settings.image_list;
57 if (typeof(imageList) == "string") {
58 tinymce.util.XHR.send({
60 success: function(text) {
61 callback(tinymce.util.JSON.parse(text));
70 function showDialog(imageList) {
71 var win, data = {}, dom = editor.dom, imgElm = editor.selection.getNode();
72 var width, height, imageListCtrl, classListCtrl;
74 function buildValues(listSettingName, dataItemName, defaultItems) {
75 var selectedItem, items = [];
77 tinymce.each(editor.settings[listSettingName] || defaultItems, function(target) {
79 text: target.text || target.title,
85 if (data[dataItemName] === target.value || (!selectedItem && target.selected)) {
90 if (selectedItem && !data[dataItemName]) {
91 data[dataItemName] = selectedItem.value;
92 selectedItem.selected = true;
98 function buildImageList() {
99 var imageListItems = [{text: 'None', value: ''}];
101 tinymce.each(imageList, function(image) {
102 imageListItems.push({
103 text: image.text || image.title,
104 value: editor.convertURL(image.value || image.url, 'src'),
109 return imageListItems;
112 function recalcSize() {
113 var widthCtrl, heightCtrl, newWidth, newHeight;
115 widthCtrl = win.find('#width')[0];
116 heightCtrl = win.find('#height')[0];
118 newWidth = widthCtrl.value();
119 newHeight = heightCtrl.value();
121 if (win.find('#constrain')[0].checked() && width && height && newWidth && newHeight) {
122 if (width != newWidth) {
123 newHeight = Math.round((newWidth / width) * newHeight);
124 heightCtrl.value(newHeight);
126 newWidth = Math.round((newHeight / height) * newWidth);
127 widthCtrl.value(newWidth);
135 function onSubmitForm() {
136 function waitLoad(imgElm) {
137 function selectImage() {
138 imgElm.onload = imgElm.onerror = null;
139 editor.selection.select(imgElm);
140 editor.nodeChanged();
143 imgElm.onload = function() {
144 if (!data.width && !data.height) {
145 dom.setAttribs(imgElm, {
146 width: imgElm.clientWidth,
147 height: imgElm.clientHeight
150 editor.fire( 'wpNewImageRefresh', { node: imgElm } );
156 imgElm.onerror = selectImage;
162 data = tinymce.extend(data, win.toJSON());
163 var caption = data.caption; // WP
169 if (data.width === '') {
173 if (data.height === '') {
177 if (data.style === '') {
187 "class": data["class"]
190 if (!data["class"]) {
191 delete data["class"];
194 editor.undoManager.transact(function() {
196 var eventData = { node: imgElm, data: data, caption: caption };
198 editor.fire( 'wpImageFormSubmit', { imgData: eventData } );
200 if ( eventData.cancel ) {
201 waitLoad( eventData.node );
210 editor.nodeChanged();
217 data.id = '__mcenew';
219 editor.selection.setContent(dom.createHTML('img', data));
220 imgElm = dom.get('__mcenew');
221 dom.setAttrib(imgElm, 'id', null);
223 dom.setAttribs(imgElm, data);
230 function removePixelSuffix(value) {
232 value = value.replace(/px$/, '');
238 function srcChange() {
240 imageListCtrl.value(editor.convertURL(this.value(), 'src'));
243 getImageSize(this.value(), function(data) {
244 if (data.width && data.height) {
246 height = data.height;
248 win.find('#width').value(width);
249 win.find('#height').value(height);
254 width = dom.getAttrib(imgElm, 'width');
255 height = dom.getAttrib(imgElm, 'height');
257 if (imgElm.nodeName == 'IMG' && !imgElm.getAttribute('data-mce-object') && !imgElm.getAttribute('data-mce-placeholder')) {
259 src: dom.getAttrib(imgElm, 'src'),
260 alt: dom.getAttrib(imgElm, 'alt'),
261 "class": dom.getAttrib(imgElm, 'class'),
267 editor.fire( 'wpLoadImageData', { imgData: { data: data, node: imgElm } } );
276 values: buildImageList(),
277 value: data.src && editor.convertURL(data.src, 'src'),
278 onselect: function(e) {
279 var altCtrl = win.find('#alt');
281 if (!altCtrl.value() || (e.lastControl && altCtrl.value() == e.lastControl.text())) {
282 altCtrl.value(e.control.text());
285 win.find('#src').value(e.control.value());
287 onPostRender: function() {
288 imageListCtrl = this;
293 if (editor.settings.image_class_list) {
298 values: applyPreview(buildValues('image_class_list', 'class'))
302 // General settings shared between simple and advanced dialogs
303 var generalFormItems = [
304 {name: 'src', type: 'filepicker', filetype: 'image', label: 'Source', autofocus: true, onchange: srcChange},
308 if (editor.settings.image_description !== false) {
309 generalFormItems.push({name: 'alt', type: 'textbox', label: 'Image description'});
312 if (editor.settings.image_dimensions !== false) {
313 generalFormItems.push({
321 {name: 'width', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Width'},
322 {type: 'label', text: 'x'},
323 {name: 'height', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Height'},
324 {name: 'constrain', type: 'checkbox', checked: true, text: 'Constrain proportions'}
329 generalFormItems.push(classListCtrl);
332 editor.fire( 'wpLoadImageForm', { data: generalFormItems } );
334 function updateStyle() {
335 function addPixelSuffix(value) {
336 if (value.length > 0 && /^[0-9]+$/.test(value)) {
343 if (!editor.settings.image_advtab) {
347 var data = win.toJSON();
348 var css = dom.parseStyle(data.style);
351 css['margin-top'] = css['margin-bottom'] = addPixelSuffix(data.vspace);
352 css['margin-left'] = css['margin-right'] = addPixelSuffix(data.hspace);
353 css['border-width'] = addPixelSuffix(data.border);
355 win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
358 if (editor.settings.image_advtab) {
359 // Parse styles from img
361 data.hspace = removePixelSuffix(imgElm.style.marginLeft || imgElm.style.marginRight);
362 data.vspace = removePixelSuffix(imgElm.style.marginTop || imgElm.style.marginBottom);
363 data.border = removePixelSuffix(imgElm.style.borderWidth);
364 data.style = editor.dom.serializeStyle(editor.dom.parseStyle(editor.dom.getAttrib(imgElm, 'style')));
367 // Advanced dialog shows general+advanced tabs
368 win = editor.windowManager.open({
369 title: 'Insert/edit image',
371 bodyType: 'tabpanel',
376 items: generalFormItems
395 alignH: ['left', 'right'],
399 onchange: updateStyle
402 {label: 'Vertical space', name: 'vspace'},
403 {label: 'Horizontal space', name: 'hspace'},
404 {label: 'Border', name: 'border'}
410 onSubmit: onSubmitForm
413 // Simple default dialog
414 win = editor.windowManager.open({
415 title: 'Insert/edit image',
417 body: generalFormItems,
418 onSubmit: onSubmitForm
424 editor.addCommand( 'mceImage', function() {
425 createImageList( showDialog )();
428 editor.addButton('image', {
430 tooltip: 'Insert/edit image',
431 onclick: createImageList(showDialog),
432 stateSelector: 'img:not([data-mce-object],[data-mce-placeholder])'
435 editor.addMenuItem('image', {
437 text: 'Insert image',
438 onclick: createImageList(showDialog),
440 prependToContext: true