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 var srcURL = this.value(),
232 absoluteURLPattern = new RegExp('^(?:[a-z]+:)?//', 'i'),
233 baseURL = editor.settings.document_base_url;
235 //Pattern test the src url and make sure we haven't already prepended the url
236 if (baseURL && !absoluteURLPattern.test(srcURL) && srcURL.substring(0, baseURL.length) !== baseURL) {
237 this.value(baseURL + srcURL);
240 getImageSize(this.value(), function(data) {
241 if (data.width && data.height && imageDimensions) {
243 height = data.height;
245 win.find('#width').value(width);
246 win.find('#height').value(height);
252 width = dom.getAttrib(imgElm, 'width');
253 height = dom.getAttrib(imgElm, 'height');
255 if (imgElm.nodeName == 'IMG' && !imgElm.getAttribute('data-mce-object') && !imgElm.getAttribute('data-mce-placeholder')) {
257 src: dom.getAttrib(imgElm, 'src'),
258 alt: dom.getAttrib(imgElm, 'alt'),
259 "class": dom.getAttrib(imgElm, 'class'),
265 editor.fire( 'wpLoadImageData', { imgData: { data: data, node: imgElm } } );
274 values: buildListItems(
277 item.value = editor.convertURL(item.value || item.url, 'src');
279 [{text: 'None', value: ''}]
281 value: data.src && editor.convertURL(data.src, 'src'),
282 onselect: function(e) {
283 var altCtrl = win.find('#alt');
285 if (!altCtrl.value() || (e.lastControl && altCtrl.value() == e.lastControl.text())) {
286 altCtrl.value(e.control.text());
289 win.find('#src').value(e.control.value()).fire('change');
291 onPostRender: function() {
292 imageListCtrl = this;
297 if (editor.settings.image_class_list) {
302 values: buildListItems(
303 editor.settings.image_class_list,
306 item.textStyle = function() {
307 return editor.formatter.getCssText({inline: 'img', classes: [item.value]});
315 // General settings shared between simple and advanced dialogs
316 var generalFormItems = [
328 if (editor.settings.image_description !== false) {
329 generalFormItems.push({name: 'alt', type: 'textbox', label: 'Image description'});
332 if (imageDimensions) {
333 generalFormItems.push({
341 {name: 'width', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Width'},
342 {type: 'label', text: 'x'},
343 {name: 'height', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Height'},
344 {name: 'constrain', type: 'checkbox', checked: true, text: 'Constrain proportions'}
349 generalFormItems.push(classListCtrl);
352 editor.fire( 'wpLoadImageForm', { data: generalFormItems } );
354 function updateStyle() {
355 function addPixelSuffix(value) {
356 if (value.length > 0 && /^[0-9]+$/.test(value)) {
363 if (!editor.settings.image_advtab) {
367 var data = win.toJSON();
368 var css = dom.parseStyle(data.style);
371 css['margin-top'] = css['margin-bottom'] = addPixelSuffix(data.vspace);
372 css['margin-left'] = css['margin-right'] = addPixelSuffix(data.hspace);
373 css['border-width'] = addPixelSuffix(data.border);
375 win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
378 if (editor.settings.image_advtab) {
379 // Parse styles from img
381 data.hspace = removePixelSuffix(imgElm.style.marginLeft || imgElm.style.marginRight);
382 data.vspace = removePixelSuffix(imgElm.style.marginTop || imgElm.style.marginBottom);
383 data.border = removePixelSuffix(imgElm.style.borderWidth);
384 data.style = editor.dom.serializeStyle(editor.dom.parseStyle(editor.dom.getAttrib(imgElm, 'style')));
387 // Advanced dialog shows general+advanced tabs
388 win = editor.windowManager.open({
389 title: 'Insert/edit image',
391 bodyType: 'tabpanel',
396 items: generalFormItems
415 alignH: ['left', 'right'],
419 onchange: updateStyle
422 {label: 'Vertical space', name: 'vspace'},
423 {label: 'Horizontal space', name: 'hspace'},
424 {label: 'Border', name: 'border'}
430 onSubmit: onSubmitForm
433 // Simple default dialog
434 win = editor.windowManager.open({
435 title: 'Insert/edit image',
437 body: generalFormItems,
438 onSubmit: onSubmitForm
443 editor.addButton('image', {
445 tooltip: 'Insert/edit image',
446 onclick: createImageList(showDialog),
447 stateSelector: 'img:not([data-mce-object],[data-mce-placeholder])'
450 editor.addMenuItem('image', {
452 text: 'Insert image',
453 onclick: createImageList(showDialog),
455 prependToContext: true
458 editor.addCommand('mceImage', createImageList(showDialog));