/*
* Aloha Image Plugin - Allow image manipulation in Aloha Editor
*
* Author & Copyright (c) 2011 Gentics Software GmbH
* aloha-sales@gentics.com
* Contributors
* Johannes Schüth - http://jotschi.de
* Nicolas karageuzian - http://nka.me/
* Benjamin Athur Lupton - http://www.balupton.com/
* Thomas Lete
* Nils Dehl
* Christopher Hlubek
* Edward Tsech
* Haymo Meran
*
* Licensed under the terms of http://www.aloha-editor.com/license.html
*/
define([
// js
'aloha/jquery',
'aloha/plugin',
'aloha/floatingmenu',
'i18n!aloha/nls/i18n',
'i18n!image/nls/i18n',
'jquery-plugin!image/vendor/ui/jquery-ui-1.8.10.custom.min',
'jquery-plugin!image/vendor/jcrop/jquery.jcrop.min',
'jquery-plugin!image/vendor/mousewheel/mousewheel',
// css
'css!image/css/image.css',
'css!image/vendor/ui/ui-lightness/jquery-ui-1.8.10.cropnresize.css',
'css!image/vendor/jcrop/jquery.jcrop.css'
],
function AlohaImagePlugin ( aQuery, Plugin, FloatingMenu, i18nCore, i18n ) {
var jQuery = aQuery;
var $ = aQuery;
var GENTICS = window.GENTICS, Aloha = window.Aloha;
// Attributes manipulation utilities
// Aloha team may want to factorize, it could be useful for other plugins
// Prototypes
String.prototype.toInteger = String.prototype.toInteger || function() {
return parseInt(String(this).replace(/px$/,'')||0,10);
};
String.prototype.toFloat = String.prototype.toInteger || function() {
return parseFloat(String(this).replace(/px$/,'')||0,10);
};
Number.prototype.toInteger = Number.prototype.toInteger || String.prototype.toInteger;
Number.prototype.toFloat = Number.prototype.toFloat || String.prototype.toFloat;
// Insert jQuery Prototypes
jQuery.extend(true, jQuery.fn, {
increase: jQuery.fn.increase || function(attr) {
var obj = jQuery(this), value, newValue;
// Check
if ( !obj.length ) {
return obj;
}
// Calculate
value = obj.css(attr).toFloat();
newValue = Math.round((value||1)*1.2);
// Apply
if (value == newValue) { // when value is 2, won't increase
newValue++;
}
// Apply
obj.css(attr,newValue);
// Chain
return obj;
},
decrease: jQuery.fn.decrease || function(attr) {
var obj = jQuery(this), value, newValue;
// Check
if ( !obj.length ) {
return obj;
}
// Calculate
value = obj.css(attr).toFloat();
newValue = Math.round((value||0)*0.8);
// Apply
if (value == newValue && newValue >0) { // when value is 2, won't increase
newValue--;
}
obj.css(attr,newValue);
// Chain
return obj;
}
});
// Create and register Image Plugin
return Plugin.create('image', {
languages: ['en', 'fr', 'de', 'ru', 'cz'],
defaultSettings: {
'maxWidth': 1600,
'minWidth': 3,
'maxHeight': 1200,
'minHeight': 3,
// This setting will correct manually values that are out of bounds
'autoCorrectManualInput': true,
// This setting will define a fixed aspect ratio for all resize actions
'fixedAspectRatio' : false,
// When enabled this setting will order the plugin to automatically resize images to given bounds
'autoResize': false,
//Image manipulation options - ONLY in default config section
ui: {
oneTab : false, //Place all ui components within one tab
insert : true,
reset : true,
aspectRatioToggle: true, // Toggle button for the aspect ratio
align : true, // Menu elements to show/hide in menu
resize : true, // Resize buttons
meta : true,
margin : true,
crop : true,
resizable : true, // Resizable ui-drag image
handles : 'ne, se, sw, nw'
},
/**
* Crop callback is triggered after the user clicked accept to accept his crop
* @param image jquery image object reference
* @param props cropping properties
*/
onCropped: function ($image, props) {
Aloha.Log.info('Default onCropped invoked', $image, props);
},
/**
* Reset callback is triggered before the internal reset procedure is applied
* if this function returns true, then the reset has been handled by the callback
* which means that no other reset will be applied
* if false is returned the internal reset procedure will be applied
* @param image jquery image object reference
* @return true if a reset has been applied, false otherwise
*/
onReset: function ($image) {
Aloha.Log.info('Default onReset invoked', $image);
return false;
},
/**
* Example callback method which gets called while the resize process is beeing executed.
*/
onResize: function ($image) {
Aloha.Log.info('Default onResize invoked', $image);
},
/**
* Resize callback is triggered after the internal resize procedure is applied.
*/
onResized: function ($image) {
Aloha.Log.info('Default onResized invoked', $image);
}
},
/**
* Internal callback hook which gets invoked when cropping has been finished
*/
_onCropped: function ($image, props) {
$('#' + this.imgResizeHeightField.id).val($image.height());
$('#' + this.imgResizeWidthField.id).val($image.width());
$('body').trigger('aloha-image-cropped', [$image, props]);
// Call the custom onCropped function
this.onCropped($image, props);
},
/**
* Internal callback hook which gets invoked when resetting images
*/
_onReset: function ($image) {
$('#' + this.imgResizeHeightField.id).val($image.height());
$('#' + this.imgResizeWidthField.id).val($image.width());
// No default behaviour defined besides event triggering
$('body').trigger('aloha-image-reset', $image);
// Call the custom resize function
return this.onReset($image);
},
/**
* Internal callback hook which gets invoked while the image is beeing resized
*/
_onResize: function ($image) {
$('#' + this.imgResizeHeightField.id).val($image.height());
$('#' + this.imgResizeWidthField.id).val($image.width());
// No default behaviour defined besides event triggering
$('body').trigger('aloha-image-resize', $image);
// Call the custom resize function
this.onResize($image);
},
/**
* Internal callback hook which gets invoked when the current resizing action has stopped
*/
_onResized: function ($image) {
$('#' + this.imgResizeHeightField.id).val($image.height());
$('#' + this.imgResizeWidthField.id).val($image.width());
$('body').trigger('aloha-image-resized', $image);
// Call the custom resize function
this.onResized($image);
},
/**
* The image that is currently edited
*/
imageObj: null,
/**
* The Jcrop API reference
* this is needed to be able to destroy the cropping frame later on
* the variable is linked to the api object whilst cropping, or set to null otherwise
* strange, but done as documented http://deepliquid.com/content/Jcrop_API.html
*/
jcAPI: null,
/**
* State variable for the aspect ratio toggle feature
*/
keepAspectRatio: false,
/**
* Variable that will hold the start aspect ratio. This ratio will be used once starResize will be called.
*/
startAspectRatio: false,
/**
* This will contain an image's original properties to be able to undo previous settings
*
* when an image is clicked for the first time, a new object will be added to the array
* {
* obj : [the image object reference],
* src : [the original src url],
* width : [initial width],
* height : [initial height]
* }
*
* when an image is clicked the second time, the array will be checked for the image object
* referenct, to prevent for double entries
*/
restoreProps: [],
objectTypeFilter: [],
/**
* Plugin initialization method
*/
init: function() {
var that = this;
var imagePluginUrl = Aloha.getPluginUrl('image');
// Extend the default settings with the custom ones (done by default)
this.startAspectRatio = this.settings.fixedAspectRatio;
this.config = this.defaultSettings;
this.settings = jQuery.extend(true, this.defaultSettings, this.settings);
that.initializeButtons();
that.bindInteractions();
that.subscribeEvents();
},
/**
* Create buttons
*/
initializeButtons: function() {
var that = this,
tabInsert = i18nCore.t('floatingmenu.tab.insert'),
tabImage = i18n.t('floatingmenu.tab.img'),
tabFormatting = i18n.t('floatingmenu.tab.formatting'),
tabCrop = i18n.t('floatingmenu.tab.crop'),
tabResize = i18n.t('floatingmenu.tab.resize');
FloatingMenu.createScope(this.name, 'Aloha.empty');
if (this.settings.ui.insert) {
var tabId = this.settings.ui.oneTab ? tabImage : tabInsert;
that._addUIInsertButton(tabId);
}
if (this.settings.ui.meta) {
var tabId = this.settings.ui.oneTab ? tabImage : tabImage;
that._addUIMetaButtons(tabId);
}
if (this.settings.ui.reset) {
var tabId = this.settings.ui.reset ? tabImage : tabImage;
that._addUIResetButton(tabId);
}
if (this.settings.ui.align) {
var tabId = this.settings.ui.oneTab ? tabImage : tabFormatting;
that._addUIAlignButtons(tabId);
}
if (this.settings.ui.margin) {
var tabId = this.settings.ui.oneTab ? tabImage : tabFormatting;
that._addUIMarginButtons(tabId);
}
if (this.settings.ui.crop) {
var tabId = this.settings.ui.oneTab ? tabImage : tabCrop;
that._addUICropButtons(tabId);
}
if (this.settings.ui.resize) {
var tabId = this.settings.ui.oneTab ? tabImage : tabResize;
that._addUIResizeButtons(tabId);
}
if (this.settings.ui.aspectRatioToggle) {
var tabId = this.settings.ui.oneTab ? tabImage : tabResize;
that.__addUIAspectRatioToggleButton(tabId);
}
// TODO fix the function and reenable this button
//that._addNaturalSizeButton();
},
/**
* Adds the aspect ratio toggle button to the floating menu
*/
__addUIAspectRatioToggleButton: function(tabId) {
var that = this;
var toggleButton = new Aloha.ui.Button({
'size' : 'small',
'tooltip' : i18n.t('button.toggle.tooltip'),
'toggle' : true,
'iconClass' : 'cnr-ratio',
'onclick' : function (btn, event) {
that.toggleKeepAspectRatio();
}
});
// If the setting has been set to a number or false we need to activate the
// toggle button to indicate that the aspect ratio will be preserved.
if (this.settings.fixedAspectRatio != false) {
toggleButton.pressed = true;
this.keepAspectRatio = true;
}
FloatingMenu.addButton(
that.name,
toggleButton,
tabId,
20
);
},
/**
* Adds the reset button to the floating menu for the given tab
*/
_addUIResetButton: function(tabId) {
var that = this;
// Reset button
var resetButton = new Aloha.ui.Button({
'size' : 'small',
'tooltip' : i18n.t('Reset'),
'toggle' : false,
'iconClass' : 'cnr-reset',
'onclick' : function (btn, event) {
that.reset();
}
});
FloatingMenu.addButton(
that.name,
resetButton,
tabId,
2
);
},
/**
* Adds the insert button to the floating menu
*/
_addUIInsertButton: function(tabId) {
var that = this;
this.insertImgButton = new Aloha.ui.Button({
'name' : 'insertimage',
'iconClass': 'aloha-button aloha-image-insert',
'size' : 'small',
'onclick' : function () { that.insertImg(); },
'tooltip' : i18n.t('button.addimg.tooltip'),
'toggle' : false
});
FloatingMenu.addButton(
'Aloha.continuoustext',
this.insertImgButton,
tabId,
1
);
},
/**
* Adds the ui meta fields (search, title) to the floating menu.
*/
_addUIMetaButtons: function(tabId) {
var that = this;
var imgSrcLabel = new Aloha.ui.Button({
'label': i18n.t('field.img.src.label'),
'tooltip': i18n.t('field.img.src.tooltip'),
'size': 'small'
});
this.imgSrcField = new Aloha.ui.AttributeField({'name' : 'imgsrc'});
this.imgSrcField.setObjectTypeFilter( this.objectTypeFilter );
// add the title field for images
var imgTitleLabel = new Aloha.ui.Button({
'label': i18n.t('field.img.title.label'),
'tooltip': i18n.t('field.img.title.tooltip'),
'size': 'small'
});
this.imgTitleField = new Aloha.ui.AttributeField();
this.imgTitleField.setObjectTypeFilter();
FloatingMenu.addButton(
this.name,
this.imgSrcField,
tabId,
1
);
},
/**
* Adds the ui align buttons to the floating menu
*/
_addUIAlignButtons: function(tabId) {
var that = this;
var alignLeftButton = new Aloha.ui.Button({
'iconClass': 'aloha-img aloha-image-align-left',
'size': 'small',
'onclick' : function() {
var el = jQuery(that.findImgMarkup());
el.add(el.parent()).css('float', 'left');
},
'tooltip': i18n.t('button.img.align.left.tooltip')
});
FloatingMenu.addButton(
that.name,
alignLeftButton,
tabId,
1
);
var alignRightButton = new Aloha.ui.Button({
'iconClass': 'aloha-img aloha-image-align-right',
'size': 'small',
'onclick' : function() {
var el = jQuery(that.findImgMarkup());
el.add(el.parent()).css('float', 'right');
},
'tooltip': i18n.t('button.img.align.right.tooltip')
});
FloatingMenu.addButton(
that.name,
alignRightButton,
tabId,
1
);
var alignNoneButton = new Aloha.ui.Button({
'iconClass': 'aloha-img aloha-image-align-none',
'size': 'small',
'onclick' : function() {
var el = jQuery(that.findImgMarkup());
el.add(el.parent()).css({
'float': 'none',
display: 'inline-block'
});
},
'tooltip': i18n.t('button.img.align.none.tooltip')
});
FloatingMenu.addButton(
that.name,
alignNoneButton,
tabId,
1
);
},
/**
* Adds the ui margin buttons to the floating menu
*/
_addUIMarginButtons: function(tabId) {
var that = this;
var incPadding = new Aloha.ui.Button({
iconClass: 'aloha-img aloha-image-padding-increase',
toggle: false,
size: 'small',
onclick: function() {
jQuery(that.findImgMarkup()).increase('padding');
},
tooltip: i18n.t('padding.increase')
});
FloatingMenu.addButton(
that.name,
incPadding,
tabId,
2
);
var decPadding = new Aloha.ui.Button({
iconClass: 'aloha-img aloha-image-padding-decrease',
toggle: false,
size: 'small',
onclick: function() {
jQuery(that.findImgMarkup()).decrease('padding');
},
tooltip: i18n.t('padding.decrease')
});
FloatingMenu.addButton(
that.name,
decPadding,
tabId,
2
);
},
/**
* Adds the crop buttons to the floating menu
*/
_addUICropButtons: function (tabId) {
var that = this;
FloatingMenu.createScope('Aloha.img', ['Aloha.global']);
this.cropButton = new Aloha.ui.Button({
'size' : 'small',
'tooltip' : i18n.t('Crop'),
'toggle' : true,
'iconClass' : 'cnr-crop',
'onclick' : function (btn, event) {
if (btn.pressed) {
that.crop();
} else {
that.endCrop();
}
}
});
FloatingMenu.addButton(
this.name,
this.cropButton,
tabId,
3
);
},
/**
* Adds the resize buttons to the floating menu
*/
_addUIResizeButtons: function (tabId) {
var that = this;
// Manual resize fields
this.imgResizeHeightField = new Aloha.ui.AttributeField();
this.imgResizeHeightField.maxValue = that.settings.maxHeight;
this.imgResizeHeightField.minValue = that.settings.minHeight;
this.imgResizeWidthField = new Aloha.ui.AttributeField();
this.imgResizeWidthField.maxValue = that.settings.maxWidth;
this.imgResizeWidthField.minValue = that.settings.minWidth;
this.imgResizeWidthField.width = 50;
this.imgResizeHeightField.width = 50;
var widthLabel = new Aloha.ui.Button({
'label': i18n.t('width'),
'tooltip': i18n.t('width'),
'size': 'small'
});
FloatingMenu.addButton(
this.name,
widthLabel,
tabId,
30
);
FloatingMenu.addButton(
this.name,
this.imgResizeWidthField,
tabId,
40
);
var heightLabel = new Aloha.ui.Button({
'label': i18n.t('height'),
'tooltip': i18n.t('height'),
'size': 'small'
});
FloatingMenu.addButton(
this.name,
heightLabel,
tabId,
50
);
FloatingMenu.addButton(
this.name,
this.imgResizeHeightField,
tabId,
60
);
},
/**
* Adds the natural size button to the floating menu
*/
_addNaturalSizeButton: function () {
var that = this;
var naturalSize = new Aloha.ui.Button({
iconClass: 'aloha-img aloha-image-size-natural',
size: 'small',
toggle: false,
onclick: function() {
var img = new Image();
img.onload = function() {
var myimage = that.findImgMarkup();
if (that.settings.ui.resizable) {
that.endResize();
}
jQuery(myimage).css({
'width': img.width + 'px',
'height': img.height + 'px',
'max-width': '',
'max-height': ''
});
if (that.settings.ui.resizable) {
that.resize();
}
};
img.src = that.findImgMarkup().src;
},
tooltip: i18n.t('size.natural')
});
FloatingMenu.addButton(
this.name,
naturalSize,
tabResize,
2
);
},
/**
* Bind plugin interactions
*/
bindInteractions: function () {
var that = this;
if (this.settings.ui.resizable) {
try {
// this will disable mozillas image resizing facilities
document.execCommand( 'enableObjectResizing', false, false );
} catch (e) {
Aloha.Log.error( e, 'Could not disable enableObjectResizing' );
// this is just for internet explorer, who will not support disabling enableObjectResizing
}
}
if (this.settings.ui.meta) {
// update image object when src changes
this.imgSrcField.addListener('keyup', function(obj, event) {
that.srcChange();
});
this.imgSrcField.addListener('blur', function(obj, event) {
// TODO remove image or do something usefull if the user leaves the
// image without defining a valid image src.
var img = jQuery(obj.getTargetObject());
if ( img.attr('src') === '' ) {
img.remove();
} // image removal when src field is blank
});
}
// Override the default method by using the given one
if (this.settings.onCropped && typeof this.settings.onCropped === "function") {
this.onCropped = this.settings.onCropped;
}
// Override the default method by using the given one
if (this.settings.onReset && typeof this.settings.onReset === "function") {
this.onReset = this.settings.onReset;
}
// Override the default method by using the given one
if (this.settings.onResized && typeof this.settings.onResized === "function") {
this.onResized = this.settings.onResized;
}
// Override the default method by using the given one
if (this.settings.onResize && typeof this.settings.onResize === "function") {
this.onResize = this.settings.onResize;
}
},
/**
* Subscribe to Aloha events and DragAndDropPlugin Event
*/
subscribeEvents: function() {
var that = this;
var config = this.settings;
jQuery('img').filter(config.globalselector).unbind();
jQuery('img').filter(config.globalselector).click(function(event) {
that.clickImage(event);
});
Aloha.bind('aloha-drop-files-in-editable', function(event, data) {
var img, len = data.filesObjs.length, fileObj, config;
while (--len >= 0) {
fileObj = data.filesObjs[len];
if (fileObj.file.type.match(/image\//)) {
config = that.getEditableConfig(data.editable);
// Prepare
img = jQuery('');
img.css({
"max-width": that.maxWidth,
"max-height": that.maxHeight
});
img.attr('id',fileObj.id);
if (typeof fileObj.src === 'undefined') {
img.attr('src', fileObj.data );
//fileObj.src = fileObj.data ;
} else {
img.attr('src',fileObj.src );
}
GENTICS.Utils.Dom.insertIntoDOM(img, data.range, jQuery(Aloha.activeEditable.obj));
}
}
});
/*
* Add the event handler for selection change
*/
Aloha.bind('aloha-selection-changed', function(event, rangeObject, originalEvent) {
var config, foundMarkup;
if (originalEvent && originalEvent.target) {
// Check if the element is currently beeing resized
if (that.settings.ui.resizable && !jQuery(originalEvent.target).hasClass('ui-resizable-handle')) {
that.endResize();
}
}
if (Aloha.activeEditable !== null) {
foundMarkup = that.findImgMarkup( rangeObject );
//var config = that.getEditableConfig(Aloha.activeEditable.obj);
config = that.getEditableConfig(Aloha.activeEditable.obj);
if (typeof config !== 'undefined' ) {
that.insertImgButton.show();
} else {
that.insertImgButton.hide();
return;
}
// Enable image specific ui components if the element is an image
if (foundMarkup) {
that.insertImgButton.hide();
FloatingMenu.setScope(that.name);
if (that.settings.ui.meta) {
that.imgSrcField.setTargetObject(foundMarkup, 'src');
that.imgTitleField.setTargetObject(foundMarkup, 'title');
}
that.imgSrcField.focus();
FloatingMenu.activateTabOfButton('imgsrc');
} else {
if (that.settings.ui.meta) {
that.imgSrcField.setTargetObject(null);
}
}
// TODO this should not be necessary here!
FloatingMenu.doLayout();
}
});
Aloha.bind('aloha-editable-created', function( event, editable) {
try {
// this will disable mozillas image resizing facilities
document.execCommand( 'enableObjectResizing', false, false);
} catch (e) {
Aloha.Log.error( e, 'Could not disable enableObjectResizing');
// this is just for others, who will not support disabling enableObjectResizing
}
// Inital click on images will be handled here
// editable.obj.find('img').attr('_moz_resizing', false);
// editable.obj.find('img').contentEditable(false);
editable.obj.delegate( 'img', 'mouseup', function (event) {
that.clickImage(event);
event.stopPropagation();
});
});
that._subscribeToResizeFieldEvents();
},
/**
* Automatically resize the image to fit into defined bounds.
*/
autoResize: function() {
var that = this;
var width = that.imageObj.width();
var height = that.imageObj.height();
// Only normalize the field values when the image exeeds the definded bounds
if (width < that.settings.minWidth || width > that.settings.maxWidth || height < that.settings.minHeight || height > that.settings.maxHeight) {
that._setNormalizedFieldValues('width');
that.setSizeByFieldValue();
return true;
} else {
return false;
}
},
/**
* Toggle the keep aspect ratio functionallity
*/
toggleKeepAspectRatio: function() {
this.keepAspectRatio = !this.keepAspectRatio;
this.endResize();
if (!this.keepAspectRatio) {
this.startAspectRatio = false;
} else {
// If no fixed aspect ratio was given we will calculate a new start
// aspect ratio that will be used for the next starResize action.
if ( typeof this.settings.fixedAspectRatio !== 'number' ) {
var currentRatio = this.imageObj.width() / this.imageObj.height();
this.startAspectRatio = currentRatio;
} else {
this.startAspectRatio = this.settings.fixedAspectRatio;
}
}
this.startResize();
},
/**
* Bind interaction events that are invoked on the resize fields
*/
_subscribeToResizeFieldEvents: function() {
var that = this;
/**
* Helper function that will update the fields
*/
function updateField( $field, delta, maxValue, minValue ) {
if ( typeof minValue === 'undefined' ) {
minValue = 0;
}
if ( typeof maxValue === 'undefined' ) {
maxValue = 8000;
}
// If the current value of the field can't be parsed we don't update it
var oldValue = parseInt( $field.val() );
if ( isNaN(oldValue) ) {
$field.css( 'background-color', 'red' );
return false;
}
var newValue = oldValue + delta;
// Exit if the newValue is above the maxValue limit (only if the user tries to increment)
if (delta>=0 && newValue > maxValue) {
// Auto correct out of bounds values
if (that.settings.autoCorrectManualInput) {
$field.val(maxValue);
return true;
} else {
$field.css('background-color','red');
return false;
}
// Exit if the newValue is below the minValue (only if the user tries to decrement)
} else if (delta<=0 && newValue= minValue) {
// Check if we are currently in cropping mode
if(typeof that.jcAPI !== 'undefined' && that.jcAPI != null) {
that.setCropAreaByFieldValue();
} else {
// 1. Normalize the size
that._setNormalizedFieldValues(fieldName);
// 2. Set the final size to the image
that.setSizeByFieldValue();
}
}
// 0-9 keys
} else if (e.keyCode <= 57 && e.keyCode >= 48 || e.keyCode <= 105 && e.keyCode >= 96 ) {
if($(this).val() >= minValue) {
// Check if we are currently in cropping mode
if(typeof that.jcAPI !== 'undefined' && that.jcAPI != null) {
that.setCropAreaByFieldValue();
} else {
// 1. Normalize the size
that._setNormalizedFieldValues(fieldName);
// 2. Set the final size to the image
that.setSizeByFieldValue();
}
}
} else {
var delta = 0;
if (e.keyCode == 38 || e.keyCode == 107) {
delta = +1;
} else if (e.keyCode == 40 || e.keyCode == 109) {
delta = -1;
}
// Handle key combinations
if ( e.shiftKey || e.metaKey || e.ctrlKey ) {
delta = delta * 10;
}
// Only resize when field values are ok
if(updateField($(this), delta, maxValue, minValue)) {
// Check if we are currently in cropping mode
if(typeof that.jcAPI !== 'undefined' && that.jcAPI != null) {
that.setCropAreaByFieldValue();
} else {
// 1. Normalize the size
that._setNormalizedFieldValues(fieldName);
// 2. Set the final size to the image
that.setSizeByFieldValue();
}
}
}
e.preventDefault();
return false;
};
/**
* Handle the mouse wheel event on the field
*/
function handleMouseWheelEventOnField(e, delta) {
var minValue = e.data.minValue;
var maxValue = e.data.maxValue;
var fieldName = e.data.fieldName;
// Handle key combinations
if ( e.shiftKey || e.metaKey || e.ctrlKey ) {
delta = delta * 10;
}
// Only resize when field values are ok
if(updateField($(this), delta, maxValue, minValue)) {
// Check if we are currently in cropping mode
if(typeof that.jcAPI !== 'undefined' && that.jcAPI != null) {
that.setCropAreaByFieldValue();
} else {
// 1. Normalize the size
that._setNormalizedFieldValues(fieldName);
// 2. Set the final size to the image
that.setSizeByFieldValue();
}
}
return false;
};
/**
* Handle mousewheel,keyup actions on both fields
*/
var $heightField = $('#' + that.imgResizeHeightField.id );
var heightEventData = {fieldName: 'height', maxValue: that.imgResizeHeightField.maxValue, minValue: that.imgResizeHeightField.minValue };
$heightField.live('keyup', heightEventData, handleKeyUpEventOnField);
$heightField.live('mousewheel', heightEventData, handleMouseWheelEventOnField);
var $widthField = $('#' + that.imgResizeWidthField.id );
var widthEventData = {fieldName: 'width', maxValue: that.imgResizeWidthField.maxValue , minValue: that.imgResizeWidthField.minValue };
$widthField.live('keyup',widthEventData , handleKeyUpEventOnField);
$widthField.live('mousewheel', widthEventData, handleMouseWheelEventOnField);
},
/**
* This helper function will keep the aspect ratio for the field with the given name.
*/
_setNormalizedFieldValues: function(primaryFieldName) {
var that = this;
var widthField = jQuery("#" + that.imgResizeWidthField.id);
var heightField = jQuery("#" + that.imgResizeHeightField.id);
var width = widthField.val();
var height = heightField.val();
var size = that._normalizeSize(width, height, primaryFieldName);
widthField.val(size.width);
heightField.val(size.height);
},
/**
* Manually set the given size for the current image
*/
setSize: function(width, height) {
var that = this;
this.imageObj.width(width);
this.imageObj.height(height);
var $wrapper = this.imageObj.closest('.Aloha_Image_Resize');
$wrapper.height(height);
$wrapper.width(width);
this._onResize(this.imageObj);
this._onResized(this.imageObj);
},
/**
* This method will handle the mouseUp event on images (eg. within editables).
* It will if enabled activate the resizing action.
*/
clickImage: function( e ) {
var that = this;
that.imageObj = jQuery(e.target);
var currentImage = that.imageObj;
FloatingMenu.setScope(that.name);
var editable = currentImage.closest('.aloha-editable');
// Disabling the content editable. This will disable the resizeHandles in internet explorer
jQuery(editable).contentEditable(false);
//Store the current props of the image
this.restoreProps.push({
obj : e.srcElement,
src : that.imageObj.attr('src'),
width : that.imageObj.width(),
height : that.imageObj.height()
});
// Update the resize input fields with the new width and height
$('#' + that.imgResizeHeightField.id).val(that.imageObj.height());
$('#' + that.imgResizeWidthField.id).val(that.imageObj.width());
if (this.settings.ui.resizable) {
this.startResize();
}
if (this.settings.autoResize) {
this.autoResize();
}
},
/**
* This method extracts determins if the range selection contains an image
*/
findImgMarkup: function ( range ) {
var that = this;
var config = this.config;
var result, targetObj;
if ( typeof range === 'undefined' ) {
range = Aloha.Selection.getRangeObject();
}
targetObj = jQuery(range.startContainer);
try {
if ( Aloha.activeEditable ) {
if (( typeof range.startContainer !== 'undefined'
&& typeof range.startContainer.childNodes !== 'undefined'
&& typeof range.startOffset !== 'undefined'
&& typeof range.startContainer.childNodes[range.startOffset] !== 'undefined'
&& range.startContainer.childNodes[range.startOffset].nodeName.toLowerCase() === 'img'
&& range.startOffset+1 === range.endOffset) ||
(targetObj.hasClass('Aloha_Image_Resize')))
{
result = targetObj.find('img')[0];
if (! result.css) {
result.css = '';
}
if (! result.title) {
result.title = '';
}
if (! result.src) {
result.src = '';
}
return result;
}
else {
return null;
}
}
} catch (e) {
Aloha.Log.debug(e, "Error finding img markup.");
}
return null;
},
/**
* This helper function will calculate the new width and height while keeping
* the aspect ratio when the keepAspectRatio flat is set to true. The primarySize
* can be 'width' or 'height'. The function will first try to normalize the oposite size.
*/
_normalizeSize: function(width, height, primarySize) {
var that = this;
// Convert string values to numbers
width = parseInt(width);
height = parseInt(height);
/**
* Inner function that calculates the new height by examining the width
*/
function handleHeight(callHandleWidth) {
// Check whether the value is within bounds
if (height > that.settings.maxHeight) {
// Throw a notification event
var eventProps = { 'org': height, 'new': that.settings.maxHeight};
$('body').trigger('aloha-image-resize-outofbounds', ["height", "max", eventProps]);
height = that.settings.maxHeight;
} else if (height < that.settings.minHeight) {
// Throw a notification event
var eventProps = { 'org': height, 'new': that.settings.minHeight};
$('body').trigger('aloha-image-resize-outofbounds', ["height", "min", eventProps]);
height = that.settings.minHeight;
}
if (that.keepAspectRatio) {
width = height * aspectRatio;
// We don't want to invoke handleWidth again. This would mess up our previously calculated width
if (callHandleWidth) {
handleWidth(false);
}
}
}
/**
* Inner function that calculates the new width by examining the width
*/
function handleWidth(callHandleHeight) {
// Check whether the value is within bounds
if (width > that.settings.maxWidth) {
// Throw a notification event
var eventProps = { 'org': width, 'new': that.settings.maxWidth};
$('body').trigger('aloha-image-resize-outofbounds', ["width", "max", eventProps]);
width = that.settings.maxWidth;
} else if (width < that.settings.minWidth) {
// Throw a notification event
var eventProps = { 'org': width, 'new': that.settings.minWidth};
$('body').trigger('aloha-image-resize-outofbounds', ["width", "min", eventProps]);
width = that.settings.minWidth;
}
// Calculate the new height
if (that.keepAspectRatio) {
height = width / aspectRatio;
// We don't want to invoke handleHeight again. This would mess up our previously calculated height
if (callHandleHeight) {
handleHeight(false);
}
}
}
// Load the aspect ratio and use the 4:3 ratio as default value.
var aspectRatio = 1.33333;
if (typeof that.startAspectRatio === 'number') {
aspectRatio = that.startAspectRatio;
}
// Determin which size should be handled
if (primarySize == 'width') {
handleWidth(true);
}
if (primarySize == 'height') {
handleHeight(true);
}
// Floor the values return them
return {'width': Math.floor(width), 'height': Math.floor(height)};
},
/**
* Helper function that will set the new image size using the field values
*/
setSizeByFieldValue: function() {
var that = this;
var width = $('#' + that.imgResizeWidthField.id ).val();
var height = $('#' + that.imgResizeHeightField.id ).val();
that.setSize(width, height);
},
/**
* Helper function that will set the new crop area width and height using the field values
*/
setCropAreaByFieldValue: function() {
var that = this;
var currentCropArea = that.jcAPI.tellSelect();
var width = $('#' + that.imgResizeWidthField.id ).val();
width = parseInt(width);
var height = $('#' + that.imgResizeHeightField.id ).val();
height = parseInt(height);
var selection = [currentCropArea['x'], currentCropArea['y'], currentCropArea['x'] + width,currentCropArea['y'] + height];
that.jcAPI.setSelect(selection);
},
/**
* This method will insert a new image dom element into the dom tree
*/
insertImg: function() {
var range = Aloha.Selection.getRangeObject(),
config = this.getEditableConfig(Aloha.activeEditable.obj),
imagePluginUrl = Aloha.getPluginUrl('image'),
imagestyle, imagetag, newImg;
if ( range.isCollapsed() ) {
// TODO I would suggest to call the srcChange method. So all image src
// changes are on one single point.
imagestyle = "max-width: " + config.maxWidth + "; max-height: " + config.maxHeight;
imagetag = '';
newImg = jQuery(imagetag);
// add the click selection handler
//newImg.click( Aloha.Image.clickImage ); - Using delegate now
GENTICS.Utils.Dom.insertIntoDOM(newImg, range, jQuery(Aloha.activeEditable.obj));
} else {
Aloha.Log.error('img cannot markup a selection');
// TODO the desired behavior could be me the selected content is
// replaced by an image.
// TODO it should be editor's choice, with an NON-Ext Dialog instead of alert
}
},
srcChange: function () {
// TODO the src changed. I suggest :
// 1. set an loading image (I suggest set src base64 enc) to show the user
// we are trying to load an image
// 2. start a request to get the image
// 3a. the image is ok change the src
// 3b. the image is not availbable show an error.
this.imageObj.attr('src', this.imgSrcField.getQueryValue() ); // (the img tag)
// jQuery(img).attr('src', this.imgSrcField.getQueryValue() ); // (the query value in the inputfield)
// this.imgSrcField.getItem(); // (optinal a selected resource item)
// TODO additionally implement an srcChange Handler to let implementer
// customize
},
/**
* Code imported from CropnResize Plugin
*
*/
initCropButtons: function() {
var that = this,
btns,
oldLeft = 0,
oldTop = 0;
jQuery('body').append(
'
\
\
\
'
);
btns = jQuery('#aloha-CropNResize-btns')
btns.find('.cnr-crop-apply').click(function () {
that.acceptCrop();
});
btns.find('.cnr-crop-cancel').click(function () {
that.endCrop();
});
this.interval = setInterval(function () {
var jt = jQuery('.jcrop-tracker:first'),
off = jt.offset(),
jtt = off.top,
jtl = off.left,
jth = jt.height(),
jtw = jt.width();
if (jth && jtw) {
btns.fadeIn('slow');
}
// move the icons to the bottom right side
jtt = parseInt(jtt + jth + 3, 10);
jtl = parseInt(jtl + jtw - 55, 10);
// comparison to old values hinders flickering bug in FF
if (oldLeft != jtl || oldTop != jtt) {
btns.offset({top: jtt, left: jtl});
}
oldLeft = jtl;
oldTop = jtt;
}, 10);
},
/**
* Destroy crop confirm and cancel buttons
*/
destroyCropButtons: function () {
jQuery('#aloha-CropNResize-btns').remove();
clearInterval(this.interval);
},
/**
* Helper function that will disable selectability of elements
*/
_disableSelection: function (el) {
el.find('*').attr('unselectable', 'on')
.css({
'-moz-user-select':'none',
'-webkit-user-select':'none',
'user-select':'none'
});
/*
.each(function() {
this.onselectstart = function () { return false; };
});
*/
},
/**
* Initiate a crop action
*/
crop: function () {
var that = this;
var config = this.config;
this.initCropButtons();
if (this.settings.ui.resizable) {
this.endResize();
}
this.jcAPI = jQuery.Jcrop(this.imageObj, {
onSelect : function () {
that._onCropSelect();
// ugly hack to keep scope :(
setTimeout(function () {
FloatingMenu.setScope(that.name);
}, 10);
}
});
that._disableSelection($('.jcrop-holder'));
that._disableSelection($('#imageContainer'));
that._disableSelection($('#aloha-CropNResize-btns'));
$('body').trigger('aloha-image-crop-start', [this.imageObj]);
},
/**
* Internal on crop select method
*/
_onCropSelect: function() {
var that = this;
// Update the width and height field using the intiial active crop area values
if(typeof that.jcAPI !== 'undefined' && that.jcAPI != null) {
var currentCropArea = that.jcAPI.tellSelect();
var widthField = jQuery("#" + that.imgResizeWidthField.id).val(currentCropArea['w']);
var heightField = jQuery("#" + that.imgResizeHeightField.id).val(currentCropArea['h']);
}
},
/**
* Terminates a crop
*/
endCrop: function () {
if ( this.jcAPI ) {
this.jcAPI.destroy();
this.jcAPI = null;
}
this.destroyCropButtons();
this.cropButton.extButton.toggle(false);
if ( this.settings.ui.resizable ) {
this.startResize();
}
if ( this.keepAspectRatio ) {
var currentRatio = this.imageObj.width() / this.imageObj.height();
this.startAspectRatio = currentRatio;
}
$('body').trigger('aloha-image-crop-stop', [this.imageObj]);
},
/**
* Accept the current cropping area and apply the crop
*/
acceptCrop: function () {
this._onCropped ( this.imageObj, this.jcAPI.tellSelect() );
this.endCrop ();
},
/**
* This method will activate the jquery-ui resize functionality for the current image
*/
startResize: function () {
var that = this;
var currentImageObj = this.imageObj;
currentImageObj = this.imageObj.css({
height : this.imageObj.height(),
width : this.imageObj.width(),
position : 'relative',
'max-height': '',
'max-width' : ''
});
currentImageObj.resizable({
maxHeight : that.settings.maxHeight,
minHeight : that.settings.minHeight,
maxWidth : that.settings.maxWidth,
minWidth : that.settings.minWidth,
aspectRatio : that.startAspectRatio,
handles: that.settings.handles,
grid : that.settings.grid,
resize: function(event, ui) {
that._onResize(that.imageObj);
},
stop : function (event, ui) {
that._onResized(that.imageObj);
// Workaround to finish cropping
if (this.enableCrop) {
setTimeout(function () {
FloatingMenu.setScope(that.name);
that.done(event);
}, 10);
}
}
});
currentImageObj.css('display', 'inline-block');
// this will prevent the user from resizing an image
// using IE's resize handles
// however I could not manage to hide them completely
jQuery('.ui-wrapper')
.attr('contentEditable', false)
.addClass('aloha-image-box-active Aloha_Image_Resize aloha')
.css({
position: 'relative',
display: 'inline-block',
'float': that.imageObj.css('float')
})
.bind('resizestart', function (e) {
e.preventDefault();
})
.bind('mouseup', function (e) {
e.originalEvent.stopSelectionUpdate = true;
});
},
/**
* This method will end resizing and toggle buttons accordingly and remove all markup that has been added for cropping
*/
endResize: function () {
// Find the nearest contenteditable and reenable it since resizing is finished
if (this.imageObj) {
var editable = this.imageObj.closest('.aloha-editable');
jQuery(editable).contentEditable(true);
}
if (this.imageObj) {
this.imageObj
.resizable('destroy')
.css({
top : 0,
left : 0
});
}
},
/**
* Reset the image to it's original properties
*/
reset: function() {
if (this.settings.ui.crop) {
this.endCrop();
}
if (this.settings.ui.resizable) {
this.endResize();
}
if (this._onReset(this.imageObj)) {
// the external reset procedure has already performed a reset, so there is no need to apply an internal reset
return;
}
for (var i=0;i