/* <copyright> This file contains proprietary software owned by Motorola Mobility, Inc.<br/> No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/> (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. </copyright> */ /** @module "montage/ui/rich-text-resizer.js" @requires montage/core/core */ var Montage = require("montage/core/core").Montage, dom = require("montage/ui/dom"), Point = require("montage/core/geometry/point").Point; /** @class module:"montage/ui/rich-text-resizer.js".Resizer @extends module:montage/core/core.Montage */ exports.Resizer = Montage.create(Montage,/** @lends module:"montage/ui/rich-text-resizer.js".Resizer# */ { _editor: { value: null }, _element: { value: null }, element: { get: function() { return this._element; } }, initialize: { value: function(editor) { this._editor = editor; } }, show: { value: function(element) { // Remove the current resizer if (this._element) { this._removeResizer(element); } if (element) { this._addResizer(element); } this._element = element; } }, hide: { value: function() { this._removeResizer(this._element); this._element = null; } }, cleanup: { value: function(contentNode) { var cleanContentNode = contentNode, resizers = contentNode.getElementsByClassName("montage-resizer"), nbrResizers, resizer, i; if (resizers) { // We don't want to hide the resizer, just return a copy of the content without the resizer cleanContentNode = contentNode.cloneNode(true); resizers = cleanContentNode.getElementsByClassName("montage-resizer"); nbrResizers = resizers.length; // Note: We should not have more than one resizer, this is just in case... for (i = 0; i < nbrResizers; i ++) { resizer = resizers[0]; resizer.parentNode.removeChild(resizer); } } return cleanContentNode; } }, draw : { value: function() { var thisRef = this; if (this._draggedElement) { // Resize the resizer frame var frame = this._draggedElement.parentNode.firstChild, zero = Point.create().init(0, 0), framePosition = dom.convertPointFromNodeToPage(frame, zero), cursor = this._cursorPosition, direction = this._draggedElement.id.substring("editor-resizer-".length), info = this._resizerFrameInfo, ratio = info.ratio, height = frame.clientHeight, width = frame.clientWidth, top = parseFloat(frame.style.top, 10), left = parseFloat(frame.style.left, 10), minSize = 15; element = this._draggedElement.parentNode.previousSibling; if (direction == "n") { height += framePosition.y - cursor.y; top = info.top - (height - info.height); } else if (direction == "ne") { height += framePosition.y - cursor.y; width = Math.round(height * ratio); if (cursor.x > (framePosition.x + width)) { width = cursor.x - framePosition.x; height = Math.round(width / ratio); } top = info.top - (height - info.height); } else if (direction == "e") { width = cursor.x - framePosition.x; } else if (direction == "se") { height = cursor.y - framePosition.y; width = Math.round(height * ratio); if (cursor.x > (framePosition.x + width)) { width = cursor.x - framePosition.x; height = Math.round(width / ratio); } } else if (direction == "s") { height = cursor.y - framePosition.y; } else if (direction == "sw") { height = cursor.y - framePosition.y; width = Math.round(height * ratio); if (cursor.x <= framePosition.x - width + frame.clientWidth) { width = frame.clientWidth + framePosition.x - cursor.x; height = Math.round(width / ratio); } left = info.left - (width - info.width); } else if (direction == "w") { width += framePosition.x - cursor.x; left = info.left - (width - info.width); } else if (direction == "nw") { height += framePosition.y - cursor.y; width = Math.round(height * ratio); if (cursor.x <= framePosition.x - width + frame.clientWidth) { width = frame.clientWidth + framePosition.x - cursor.x; height = Math.round(width / ratio); } top = info.top - (height - info.height); left = info.left - (width - info.width); } //set the frame's new height and width if (height > minSize && width > minSize) { frame.style.height = height + "px"; frame.style.width = width + "px"; frame.style.top = top + "px"; frame.style.left = left + "px"; } if (this._finalizeDrag) { this._draggedElement.parentNode.classList.remove("dragged"); delete this._finalizeDrag; delete this._resizerFrameInfo; delete this._draggedElement; // Remove the resizer, we don't wont it in case of undo! this._removeResizer(element); // Prevent the editor to try to delete the resizer from now on due to a selection change this._editor._selectingResizer = true; // Take the element offline to modify it var div = document.createElement("div"), offlineElement, savedID; div.innerHTML = element ? element.outerHTML : ""; offlineElement = div.firstChild; // Resize the element now that it's offline offlineElement.width = (width + 1); offlineElement.height = (height + 1); offlineElement.style.removeProperty("width"); offlineElement.style.removeProperty("height"); savedID = offlineElement.id; offlineElement.id = "montage-editor-resized-image"; // Inject the resized element into the contentEditable using execCommand in order to be in the browser undo queue document.execCommand("inserthtml", false, div.innerHTML); element = document.getElementById(offlineElement.id); if (element && savedID !== undefined) { element.id = savedID; } this._element = element; // Add back the resizer this._addResizer(element); // Reset the selection (using the editor's internal offset = this._editor._nodeOffset(element); range = document.createRange(); range.setStart(element.parentNode, offset); range.setEnd(element.parentNode, offset + 1); this._editor._selectedRange = range; // Note: Chrome (and maybe other browsers) will fire 2 selectionchange event asynchronously, to work around it let's use a timer setTimeout(function() {delete thisRef._editor._selectingResizer;}, 0); } else { this._draggedElement.parentNode.classList.add("dragged"); } } } }, startUserAction: { value: function(event) { var element = event.target, frame; if (element.classList.contains("montage-resizer-handle")) { if (window.Touch) { this._editor._observePointer(target.id); document.addEventListener("touchmove", this); } else { this._editor._observePointer("mouse"); document.addEventListener("mousemove", this); } this._draggedElement = element; frame = element.parentNode.firstChild; this._resizerFrameInfo = { width: frame.clientWidth, height: frame.clientHeight, left: parseInt(frame.style.left, 10), top: parseInt(frame.style.top, 10), ratio: frame.clientWidth / frame.clientHeight }; this._cursorPosition = {x:event.pageX, y:event.pageY}; return true; } return false; } }, endUserAction: { value: function(event) { if (this._draggedElement && !this._finalizeDrag) { // We are dragging the resizer if (window.Touch) { document.removeEventListener("touchmove", this, false); } else { this._cursorPosition = {x:event.pageX, y:event.pageY}; document.removeEventListener("mousemove", this, false); } this._editor._releaseInterest(); this._finalizeDrag = true; this._editor.needsDraw = true; event.preventDefault(); event.stopPropagation(); return true; } return false; } }, handleMousemove: { value: function(event) { if (this._draggedElement) { // We are dragging the resizer this._cursorPosition = {x:event.pageX, y:event.pageY}; this._editor.needsDraw = true; event.preventDefault(); event.stopPropagation(); } } }, handleTouchmove: { value: function(event) { this.handleMousemove(event); } }, _addResizer: { enumerable: true, value: function(element) { var parentNode = element.parentNode, nextSibling = element.nextSibling, frame, w = element.offsetWidth -1, h = element.offsetHeight -1, l = element.offsetLeft, t = element.offsetTop, resizerFrameHtml = '<div id="montage-resizer" class="montage-resizer">' + '<div id="editor-resizer-frame" class="montage-resizer-frame" style="width:'+ w + 'px; height:' + h + 'px; left:' + l + 'px; top:' + t + 'px"></div>' + '<div id="editor-resizer-nw" class="montage-resizer-handle montage-resizer-nw" style="left:' + (l - 4) + 'px; top:' + (t - 4) + 'px"></div>' + '<div id="editor-resizer-n" class="montage-resizer-handle montage-resizer-n" style="left:' + (l-3+ (w/2)) + 'px; top:' + (t-4)+ 'px"></div>' + '<div id="editor-resizer-ne" class="montage-resizer-handle montage-resizer-ne" style="left:' + (l+w-2) + 'px; top:' + (t-4) + 'px"></div>' + '<div id="editor-resizer-w" class="montage-resizer-handle montage-resizer-w" style="left:' + (l-4) + 'px; top:' + (t-3 + (h/2)) + 'px"></div>' + '<div id="editor-resizer-e" class="montage-resizer-handle montage-resizer-e" style="left:' +(l+w-2) + 'px; top:' + (t-3+(h/2)) + 'px"></div>' + '<div id="editor-resizer-sw" class="montage-resizer-handle montage-resizer-sw" style="left:' +(l-4) + 'px; top:' + (t+h-2) + 'px"></div>' + '<div id="editor-resizer-s" class="montage-resizer-handle montage-resizer-s" style="left:' + (l-3+ (w/2)) + 'px; top:' + (t+h-2) + 'px"></div>' + '<div id="editor-resizer-se" class="montage-resizer-handle montage-resizer-se" style="left:' + (l+w-2) + 'px; top:' + (t+h-2) + 'px"></div>' + '</div>', i; // sanity check: make sure we don't already have a frame if (!nextSibling || nextSibling.tagName !== "DIV" || !nextSibling.classList.contains("montage-resizer")) { frame = document.createElement("DIV"); frame.innerHTML = resizerFrameHtml; parentNode.insertBefore(frame.firstChild, nextSibling); element.classList.add("montage-resizer-element"); } } }, _removeResizer: { enumerable: true, value: function(element) { var resizer; if (!element) { return; } resizer = element.nextSibling; if (resizer && resizer.tagName === "DIV" && resizer.classList.contains("montage-resizer")) { element.parentNode.removeChild(resizer) element.classList.remove("montage-resizer-element"); } else { // Handle case where the element has been removed from the DOM or the resizer is not in sync with the // element anymore (hapen after an undo) resizer = document.getElementById("montage-resizer"); if (resizer && resizer.tagName === "DIV" && resizer.classList.contains("montage-resizer")) { resizer.parentNode.removeChild(resizer); } } } } });