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 buildListItems(inputList, itemCallback, startItems) {
44 function appendItems(values, output) {
45 output = output || [];
47 tinymce.each(values, function(item) {
48 var menuItem = {text: item.text || item.title};
51 menuItem.menu = appendItems(item.menu);
53 menuItem.value = item.value;
54 itemCallback(menuItem);
57 output.push(menuItem);
63 return appendItems(inputList, startItems || []);
66 function createImageList(callback) {
68 var imageList = editor.settings.image_list;
70 if (typeof(imageList) == "string") {
71 tinymce.util.XHR.send({
73 success: function(text) {
74 callback(tinymce.util.JSON.parse(text));
77 } else if (typeof(imageList) == "function") {
85 function showDialog(imageList) {
86 var win, data = {}, dom = editor.dom, imgElm = editor.selection.getNode();
87 var width, height, imageListCtrl, classListCtrl, imageDimensions = editor.settings.image_dimensions !== false;
89 function recalcSize() {
90 var widthCtrl, heightCtrl, newWidth, newHeight;
92 widthCtrl = win.find('#width')[0];
93 heightCtrl = win.find('#height')[0];
95 if (!widthCtrl || !heightCtrl) {
99 newWidth = widthCtrl.value();
100 newHeight = heightCtrl.value();
102 if (win.find('#constrain')[0].checked() && width && height && newWidth && newHeight) {
103 if (width != newWidth) {
104 newHeight = Math.round((newWidth / width) * newHeight);
105 heightCtrl.value(newHeight);
107 newWidth = Math.round((newHeight / height) * newWidth);
108 widthCtrl.value(newWidth);
116 function onSubmitForm() {
117 function waitLoad(imgElm) {
118 function selectImage() {
119 imgElm.onload = imgElm.onerror = null;
121 if (editor.selection) {
122 editor.selection.select(imgElm);
123 editor.nodeChanged();
127 imgElm.onload = function() {
128 if (!data.width && !data.height && imageDimensions) {
129 dom.setAttribs(imgElm, {
130 width: imgElm.clientWidth,
131 height: imgElm.clientHeight
134 editor.fire( 'wpNewImageRefresh', { node: imgElm } );
140 imgElm.onerror = selectImage;
146 data = tinymce.extend(data, win.toJSON());
147 var caption = data.caption; // WP
153 if (data.width === '') {
157 if (data.height === '') {
165 // Setup new data excluding style properties
172 "class": data["class"]
175 editor.undoManager.transact(function() {
177 var eventData = { node: imgElm, data: data, caption: caption };
179 editor.fire( 'wpImageFormSubmit', { imgData: eventData } );
181 if ( eventData.cancel ) {
182 waitLoad( eventData.node );
191 editor.nodeChanged();
198 data.id = '__mcenew';
200 editor.selection.setContent(dom.createHTML('img', data));
201 imgElm = dom.get('__mcenew');
202 dom.setAttrib(imgElm, 'id', null);
204 dom.setAttribs(imgElm, data);
211 function removePixelSuffix(value) {
213 value = value.replace(/px$/, '');
219 function srcChange(e) {
220 var meta = e.meta || {};
223 imageListCtrl.value(editor.convertURL(this.value(), 'src'));
226 tinymce.each(meta, function(value, key) {
227 win.find('#' + key).value(value);
230 if (!meta.width && !meta.height) {
231 getImageSize(this.value(), function(data) {
232 if (data.width && data.height && imageDimensions) {
234 height = data.height;
236 win.find('#width').value(width);
237 win.find('#height').value(height);
243 width = dom.getAttrib(imgElm, 'width');
244 height = dom.getAttrib(imgElm, 'height');
246 if (imgElm.nodeName == 'IMG' && !imgElm.getAttribute('data-mce-object') && !imgElm.getAttribute('data-mce-placeholder')) {
248 src: dom.getAttrib(imgElm, 'src'),
249 alt: dom.getAttrib(imgElm, 'alt'),
250 "class": dom.getAttrib(imgElm, 'class'),
256 editor.fire( 'wpLoadImageData', { imgData: { data: data, node: imgElm } } );
265 values: buildListItems(
268 item.value = editor.convertURL(item.value || item.url, 'src');
270 [{text: 'None', value: ''}]
272 value: data.src && editor.convertURL(data.src, 'src'),
273 onselect: function(e) {
274 var altCtrl = win.find('#alt');
276 if (!altCtrl.value() || (e.lastControl && altCtrl.value() == e.lastControl.text())) {
277 altCtrl.value(e.control.text());
280 win.find('#src').value(e.control.value()).fire('change');
282 onPostRender: function() {
283 imageListCtrl = this;
288 if (editor.settings.image_class_list) {
293 values: buildListItems(
294 editor.settings.image_class_list,
297 item.textStyle = function() {
298 return editor.formatter.getCssText({inline: 'img', classes: [item.value]});
306 // General settings shared between simple and advanced dialogs
307 var generalFormItems = [
319 if (editor.settings.image_description !== false) {
320 generalFormItems.push({name: 'alt', type: 'textbox', label: 'Image description'});
323 if (imageDimensions) {
324 generalFormItems.push({
332 {name: 'width', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Width'},
333 {type: 'label', text: 'x'},
334 {name: 'height', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Height'},
335 {name: 'constrain', type: 'checkbox', checked: true, text: 'Constrain proportions'}
340 generalFormItems.push(classListCtrl);
343 editor.fire( 'wpLoadImageForm', { data: generalFormItems } );
345 function updateStyle() {
346 function addPixelSuffix(value) {
347 if (value.length > 0 && /^[0-9]+$/.test(value)) {
354 if (!editor.settings.image_advtab) {
358 var data = win.toJSON();
359 var css = dom.parseStyle(data.style);
362 css['margin-top'] = css['margin-bottom'] = addPixelSuffix(data.vspace);
363 css['margin-left'] = css['margin-right'] = addPixelSuffix(data.hspace);
364 css['border-width'] = addPixelSuffix(data.border);
366 win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
369 if (editor.settings.image_advtab) {
370 // Parse styles from img
372 data.hspace = removePixelSuffix(imgElm.style.marginLeft || imgElm.style.marginRight);
373 data.vspace = removePixelSuffix(imgElm.style.marginTop || imgElm.style.marginBottom);
374 data.border = removePixelSuffix(imgElm.style.borderWidth);
375 data.style = editor.dom.serializeStyle(editor.dom.parseStyle(editor.dom.getAttrib(imgElm, 'style')));
378 // Advanced dialog shows general+advanced tabs
379 win = editor.windowManager.open({
380 title: 'Insert/edit image',
382 bodyType: 'tabpanel',
387 items: generalFormItems
406 alignH: ['left', 'right'],
410 onchange: updateStyle
413 {label: 'Vertical space', name: 'vspace'},
414 {label: 'Horizontal space', name: 'hspace'},
415 {label: 'Border', name: 'border'}
421 onSubmit: onSubmitForm
424 // Simple default dialog
425 win = editor.windowManager.open({
426 title: 'Insert/edit image',
428 body: generalFormItems,
429 onSubmit: onSubmitForm
434 editor.addButton('image', {
436 tooltip: 'Insert/edit image',
437 onclick: createImageList(showDialog),
438 stateSelector: 'img:not([data-mce-object],[data-mce-placeholder])'
441 editor.addMenuItem('image', {
443 text: 'Insert image',
444 onclick: createImageList(showDialog),
446 prependToContext: true
449 editor.addCommand('mceImage', createImageList(showDialog));