From 773c78585246dbbcdf8feb2fce0759181b4030b6 Mon Sep 17 00:00:00 2001 From: Jonathan Diehl Date: Thu, 26 Jul 2012 22:23:37 +0200 Subject: [PATCH 1/9] started refactoring the remote functions --- src/LiveDevelopment/Agents/RemoteFunctions.js | 428 +++++++++--------- 1 file changed, 211 insertions(+), 217 deletions(-) diff --git a/src/LiveDevelopment/Agents/RemoteFunctions.js b/src/LiveDevelopment/Agents/RemoteFunctions.js index 07006a94183..88e8099c48e 100644 --- a/src/LiveDevelopment/Agents/RemoteFunctions.js +++ b/src/LiveDevelopment/Agents/RemoteFunctions.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global define, $, window */ +/*global define, $, window, document */ /** * RemoteFunctions define the functions to be executed in the browser. This @@ -33,19 +33,17 @@ function RemoteFunctions() { "use strict"; - var _body = window.document.getElementsByTagName("body")[0]; // the document body - var _sourceHighlight; // the highlighted element in the source - var _highlight = []; // the highlighted elements - var _dialog; // the active dialog - var _target; // the active target element of a dialog - var _editor; // the editor - var _editText; // the original editor text - - // prevent an event - function _preventEventWhenMeta(event) { - if (event.metaKey) { - event.preventDefault(); - event.stopPropagation(); + // determine the color for a type + function _typeColor(type, highlight) { + switch (type) { + case "html": + return highlight ? "#eec" : "#ffe"; + case "css": + return highlight ? "#cee" : "#eff"; + case "js": + return highlight ? "#ccf" : "#eef"; + default: + return highlight ? "#ddd" : "#eee"; } } @@ -59,54 +57,6 @@ function RemoteFunctions() { return value; } - // hide dialog - function _hideDialog() { - if (_dialog) { - var dialog = _dialog; - _dialog = undefined; - _body.removeChild(dialog); - } - } - - // show dialog - function _createDialog(element, drawBackground) { - _hideDialog(); - - // compute the position on screen - var x = _screenOffset(element, "offsetLeft"); - var y = _screenOffset(element, "offsetTop") + element.offsetHeight; - var width = element.offsetWidth; - - // create the container - _dialog = window.document.createElement("div"); - _dialog.style.setProperty("z-index", 2147483647); - _dialog.style.setProperty("position", "absolute"); - _dialog.style.setProperty("left", x + "px"); - _dialog.style.setProperty("top", y + "px"); - _dialog.style.setProperty("font-size", "11pt"); - if (drawBackground) { - _dialog.style.setProperty("background", "#fff"); - _dialog.style.setProperty("border", "1px solid #888"); - _dialog.style.setProperty("-webkit-box-shadow", "2px 2px 6px 0px #ccc"); - _dialog.style.setProperty("border-radius", "6px"); - _dialog.style.setProperty("padding", "6px"); - } - - return _dialog; - } - - // row background color - function _backgroundColorForType(type, highlight) { - switch (type) { - case "html": - return highlight ? "#eec" : "#ffe"; - case "css": - return highlight ? "#cee" : "#eff"; - case "js": - return highlight ? "#ccf" : "#eef"; - } - } - // set an event on a element function _trigger(element, name, value, autoRemove) { var key = "data-ld-" + name; @@ -120,112 +70,212 @@ function RemoteFunctions() { } } - // low level node highlighting - function _highlightNode(info) { - if (!(info instanceof Array)) { - info = [info, "#ecc"]; - } - var prevColor = info[0].style.getPropertyValue("background-color"); - info[0].style.setProperty("background-color", info[1]); - return [info[0], prevColor]; - } + // construct the info menu + function Menu(element) { + this.element = element; - // editor key press - function _onEditKeyUp(event) { - switch (event.which) { - case 13: - // return - _editor.blur(); - break; - case 27: - // esc - _trigger(_target, "edit", _editText, true); - _editor.blur(); - break; - case 18: - // nothing - break; - default: - _trigger(_target, "edit", _editor.value); - } - } + // compute the position on screen + var x = _screenOffset(this.element, "offsetLeft"); + var y = _screenOffset(this.element, "offsetTop") + this.element.offsetHeight; - // open the editor - function _showEdit(text) { - _editText = text; - var dialog = _createDialog(_target); - _editor = window.document.createElement("input"); - _editor.setAttribute("value", text); - _editor.setAttribute("width", _target.attributes.width); - _editor.addEventListener("keyup", _onEditKeyUp); - _editor.addEventListener("blur", _hideDialog); - dialog.appendChild(_editor); - _body.appendChild(dialog); - _editor.focus(); + // create the container + this.body = document.createElement("div"); + this.body.style.setProperty("z-index", 2147483647); + this.body.style.setProperty("position", "absolute"); + this.body.style.setProperty("left", x + "px"); + this.body.style.setProperty("top", y + "px"); + this.body.style.setProperty("font-size", "11pt"); + + // draw the background + this.body.style.setProperty("background", "#fff"); + this.body.style.setProperty("border", "1px solid #888"); + this.body.style.setProperty("-webkit-box-shadow", "2px 2px 6px 0px #ccc"); + this.body.style.setProperty("border-radius", "6px"); + this.body.style.setProperty("padding", "6px"); + + _trigger(this.element, "showgoto", 1, true); } + Menu.prototype = { + onClick: function (url, event) { + this.hide(); + _trigger(event.target, "goto", url, true); + }, + + addItem: function (target) { + console.log("Add: " + target.name); + var item = document.createElement("div"); + item.style.setProperty("padding", "2px 6px"); + if (this.body.childNodes.length === 0) { + item.style.setProperty("border-top", "1px solid #ccc"); + } + item.style.setProperty("cursor", "pointer"); + item.style.setProperty("background", _typeColor(target.type)); + item.innerHTML = target.name; + item.addEventListener("click", this.onClick.bind(this, target.url)); - /** Event Handlers ***********************************************************/ + if (target.file) { + var file = document.createElement("i"); + file.style.setProperty("float", "right"); + file.style.setProperty("margin-left", "12px"); + file.innerHTML = " " + target.file; + item.appendChild(file); + } + this.body.appendChild(item); + }, - // remove highlight on release of the meta key - function _onKeyUp(event) { - if (!event.metaKey && _sourceHighlight) { - _trigger(_sourceHighlight[0], "highlight", 0, true); - _highlightNode(_sourceHighlight); - _sourceHighlight = undefined; + show: function () { + if (!this.body.parentNode) { + document.body.appendChild(this.body); + } + }, + + hide: function () { + if (this.body.parentNode) { + document.body.removeChild(this.body); + } } + + }; + + function Editor(element) { + this.onBlur = this.onBlur.bind(this); + this.onKeyPress = this.onKeyPress.bind(this); + + this.element = element; + this.element.setAttribute("contenteditable", "true"); + this.element.focus(); + this.element.addEventListener("blur", this.onBlur); + this.element.addEventListener("keypress", this.onKeyPress); + + this.revertText = this.element.innerHTML; } - // trigger highlight on mouse move - function _onMouse(event) { - if (!event.metaKey || event.type !== "mousemove" || !event.toElement) { - if (_sourceHighlight) { - _trigger(_sourceHighlight[0], "highlight", 0, true); - _highlightNode(_sourceHighlight); - _sourceHighlight = undefined; + Editor.prototype = { + onBlur: function (event) { + this.element.removeAttribute("contenteditable"); + this.element.removeEventListener("blur", this.onBlur); + this.element.removeEventListener("keypress", this.onKeyPress); + _trigger(this.element, "edit"); + }, + + onKeyPress: function (event) { + switch (event.which) { + case 13: // return + this.element.blur(); + break; + case 27: // esc + _trigger(this.element, "edit", this.revertText, true); + this.element.blur(); + break; } - return; + }, + + onChange: function (event) { + setTimeout(_trigger.bind(undefined, this.element, "edit", this.element.innerHTML)); } - if (_sourceHighlight) { - if (_sourceHighlight[0] === event.toElement) { + }; + + function Highlight(color, autoclear) { + this.color = color; + this.autoclear = !!autoclear; + this.elements = []; + this.orgColors = []; + } + + Highlight.prototype = { + _elementExists: function (element) { + var i; + for (i in this.elements) { + if (this.elements[i] === element) { + return true; + } + } + return false; + }, + + add: function (element) { + if (this._elementExists(element) || element === document) { return; } - _trigger(_sourceHighlight[0], "highlight"); - _highlightNode(_sourceHighlight); + if (this.autoclear) { + this.clear(); + } + this.elements.push(element); + this.orgColors.push(element.style.getPropertyValue("background-color")); + element.style.setProperty("background-color", this.color); + }, + + clear: function () { + var i; + for (i in this.elements) { + var e = this.elements[i]; + e.style.setProperty("background-color", this.orgColors[i]); + } + this.elements = []; + this.orgColors = []; + } + }; + + var _localHighlight; + var _remoteHighlight; + var _menu; + var _editor; + var _setup = false; + + + /** Event Handlers ***********************************************************/ + + function onMouseMove(event) { + if (event.metaKey) { + _localHighlight.add(event.target); + } else { + onKeyUp(event); } - _trigger(event.toElement, "highlight", 1); - _sourceHighlight = _highlightNode(event.toElement); } - // trigger goto on alt-click - function _onClick(event) { - _hideDialog(); - if (event.metaKey && event.toElement) { - event.preventDefault(); - event.stopPropagation(); - _target = event.toElement; - _trigger(_target, "showgoto", 1, true); - } else if (event.altKey && event.toElement && event.toElement.childNodes.length === 1) { + function onMouseOut(event) { + _localHighlight.clear(); + } + + function onClick(event) { + if (event.metaKey) { event.preventDefault(); event.stopPropagation(); - _target = event.toElement; - _showEdit(_target.childNodes[0].nodeValue); + if (_menu) { + _menu.hide(); + } + _menu = new Menu(event.target); + } else if (event.altKey) { + _editor = new Editor(event.target); } } - // Goto dialog highlighting - function _onGotoMouse(row, type, event) { - if (event.type === "mouseover") { - row.style.setProperty("background", _backgroundColorForType(type, true)); - } else { - row.style.setProperty("background", _backgroundColorForType(type)); + function onKeyUp(event) { + if (_setup && !event.metaKey && !event.altKey) { + document.removeEventListener("keyup", onKeyUp); + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseout", onMouseOut); + document.removeEventListener("click", onClick); + _localHighlight.clear(); + _localHighlight = undefined; + _setup = false; } } - // Goto dialog click - function _onGotoClick(url, event) { - _hideDialog(); - _trigger(_target, "goto", url, true); + function onKeyDown(event) { + if (!_setup && (event.metaKey || event.altKey)) { + document.addEventListener("keyup", onKeyUp); + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseout", onMouseOut); + document.addEventListener("click", onClick); + _localHighlight = new Highlight("#ecc"); + _setup = true; + } + + if (event.metaKey) { + _localHighlight.add(event.target, true); + } } @@ -233,103 +283,47 @@ function RemoteFunctions() { // show goto function showGoto(targets) { - var dialog = _createDialog(_target, true); - var i, target, row, file; + var i; for (i in targets) { - target = targets[i]; - row = window.document.createElement("div"); - row.style.setProperty("padding", "6px 12px"); - row.style.setProperty("display", "block"); - row.style.setProperty("border-bottom", "1px solid #ccc"); - row.style.setProperty("cursor", "pointer"); - row.style.setProperty("background", _backgroundColorForType(target.type)); - row.innerHTML = target.name; - row.addEventListener("click", _onGotoClick.bind(undefined, target.url)); - row.addEventListener("mouseover", _onGotoMouse.bind(undefined, row, target.type)); - row.addEventListener("mouseout", _onGotoMouse.bind(undefined, row, target.type)); - if (target.file) { - file = window.document.createElement("i"); - file.style.setProperty("float", "right"); - file.style.setProperty("margin-left", "12px"); - file.innerHTML = " " + target.file; - row.appendChild(file); - } - dialog.appendChild(row); + _menu.add(targets[i]); } - _body.appendChild(dialog); + _menu.show(); } // remove active highlights function hideHighlight() { - if (_highlight.length === 0) { - return; + if (_remoteHighlight) { + _remoteHighlight.clear(); } - var i; - for (i in _highlight) { - _highlightNode(_highlight[i]); - } - _highlight = []; } // highlight a node - function highlight(node) { - _highlight.push(_highlightNode(node)); + function highlight(node, clear) { + if (!_remoteHighlight) { + _remoteHighlight = new Highlight("#cfc"); + } + if (clear) { + _remoteHighlight.clear(); + } + _remoteHighlight.add(node); } // highlight a rule function highlightRule(rule) { hideHighlight(); - var i, nodes = window.document.querySelectorAll(rule); + var i, nodes = document.querySelectorAll(rule); for (i = 0; i < nodes.length; i++) { highlight(nodes[i]); } } - // insert a node - function insertNode(payload, parent, index) { - var node; - if (payload.value) { - node = window.document.createTextNode(payload.value); - } else { - node = window.document.createElement(payload.nodeName); - } - var sibling; - if (index !== undefined) { - sibling = parent.childNodes[index]; - } - if (sibling) { - parent.insertBefore(node, sibling); - } else { - parent.appendChild(node); - } - return node; - } - - // log something (for testing) - function log() { - var i; - for (i in arguments) { - console.info(arguments[i]); - } - } - // install event listeners - - /* FUTURE - window.document.addEventListener("keyup", _onKeyUp); - window.document.addEventListener("mousemove", _onMouse); - window.document.addEventListener("mouseout", _onMouse); - window.document.addEventListener("mousedown", _preventEventWhenMeta, true); - window.document.addEventListener("mouseup", _preventEventWhenMeta, true); - window.document.addEventListener("click", _onClick, true); - */ - + window.document.addEventListener("keydown", onKeyDown); + return { "showGoto": showGoto, "hideHighlight": hideHighlight, "highlight": highlight, - "highlightRule": highlightRule, - "insertNode": insertNode, - "log": log + "highlightRule": highlightRule }; } \ No newline at end of file From c71b221595e7ead5b56fdaa03307e02bf139992c Mon Sep 17 00:00:00 2001 From: Jonathan Diehl Date: Tue, 31 Jul 2012 12:26:50 +0200 Subject: [PATCH 2/9] done refactoring --- src/LiveDevelopment/Agents/GotoAgent.js | 2 +- src/LiveDevelopment/Agents/RemoteFunctions.js | 134 +++++++++++------- 2 files changed, 82 insertions(+), 54 deletions(-) diff --git a/src/LiveDevelopment/Agents/GotoAgent.js b/src/LiveDevelopment/Agents/GotoAgent.js index 467f17d23df..e702f4252ee 100644 --- a/src/LiveDevelopment/Agents/GotoAgent.js +++ b/src/LiveDevelopment/Agents/GotoAgent.js @@ -123,7 +123,7 @@ define(function GotoAgent(require, exports, module) { for (i in node.trace) { _makeJSTarget(targets, node.trace[i]); } - for (i in res.matchedCSSRules) { + for (i in res.matchedCSSRules.reverse()) { _makeCSSTarget(targets, res.matchedCSSRules[i]); } RemoteAgent.call("showGoto", targets); diff --git a/src/LiveDevelopment/Agents/RemoteFunctions.js b/src/LiveDevelopment/Agents/RemoteFunctions.js index 88e8099c48e..79cdef9fe00 100644 --- a/src/LiveDevelopment/Agents/RemoteFunctions.js +++ b/src/LiveDevelopment/Agents/RemoteFunctions.js @@ -73,40 +73,45 @@ function RemoteFunctions() { // construct the info menu function Menu(element) { this.element = element; - - // compute the position on screen - var x = _screenOffset(this.element, "offsetLeft"); - var y = _screenOffset(this.element, "offsetTop") + this.element.offsetHeight; - - // create the container - this.body = document.createElement("div"); - this.body.style.setProperty("z-index", 2147483647); - this.body.style.setProperty("position", "absolute"); - this.body.style.setProperty("left", x + "px"); - this.body.style.setProperty("top", y + "px"); - this.body.style.setProperty("font-size", "11pt"); - - // draw the background - this.body.style.setProperty("background", "#fff"); - this.body.style.setProperty("border", "1px solid #888"); - this.body.style.setProperty("-webkit-box-shadow", "2px 2px 6px 0px #ccc"); - this.body.style.setProperty("border-radius", "6px"); - this.body.style.setProperty("padding", "6px"); - _trigger(this.element, "showgoto", 1, true); + window.setTimeout(window.remoteShowGoto); } Menu.prototype = { onClick: function (url, event) { - this.hide(); - _trigger(event.target, "goto", url, true); + _trigger(this.element, "goto", url, true); + this.remove(); + }, + + createBody: function () { + if (this.body) { + return; + } + + // compute the position on screen + var x = _screenOffset(this.element, "offsetLeft"); + var y = _screenOffset(this.element, "offsetTop") + this.element.offsetHeight; + + // create the container + this.body = document.createElement("div"); + this.body.style.setProperty("z-index", 2147483647); + this.body.style.setProperty("position", "absolute"); + this.body.style.setProperty("left", x + "px"); + this.body.style.setProperty("top", y + "px"); + this.body.style.setProperty("font-size", "11pt"); + + // draw the background + this.body.style.setProperty("background", "#fff"); + this.body.style.setProperty("border", "1px solid #888"); + this.body.style.setProperty("-webkit-box-shadow", "2px 2px 6px 0px #ccc"); + this.body.style.setProperty("border-radius", "6px"); + this.body.style.setProperty("padding", "6px"); }, addItem: function (target) { - console.log("Add: " + target.name); var item = document.createElement("div"); item.style.setProperty("padding", "2px 6px"); - if (this.body.childNodes.length === 0) { + if (this.body.childNodes.length > 0) { item.style.setProperty("border-top", "1px solid #ccc"); } item.style.setProperty("cursor", "pointer"); @@ -125,13 +130,16 @@ function RemoteFunctions() { }, show: function () { + if (!this.body) { + this.body = this.createBody(); + } if (!this.body.parentNode) { document.body.appendChild(this.body); } }, - hide: function () { - if (this.body.parentNode) { + remove: function () { + if (this.body && this.body.parentNode) { document.body.removeChild(this.body); } } @@ -172,7 +180,7 @@ function RemoteFunctions() { }, onChange: function (event) { - setTimeout(_trigger.bind(undefined, this.element, "edit", this.element.innerHTML)); + window.setTimeout(_trigger.bind(undefined, this.element, "edit", this.element.innerHTML)); } }; @@ -217,45 +225,64 @@ function RemoteFunctions() { } }; + var _currentEditor; + function _toggleEditor(element) { + _currentEditor = new Editor(element); + } + + var _currentMenu; + function _toggleMenu(element) { + if (_currentMenu) { + _currentMenu.remove(); + } + _currentMenu = new Menu(element); + } + var _localHighlight; var _remoteHighlight; - var _menu; - var _editor; var _setup = false; /** Event Handlers ***********************************************************/ - function onMouseMove(event) { - if (event.metaKey) { - _localHighlight.add(event.target); - } else { - onKeyUp(event); + function onMouseOver(event) { + if (!event.metaKey) { + return; } + _localHighlight.add(event.target); } function onMouseOut(event) { + if (!event.metaKey) { + return; + } _localHighlight.clear(); } + function onMouseMove(event) { + onMouseOver(event); + document.removeEventListener("mousemove", onMouseMove); + } + function onClick(event) { - if (event.metaKey) { - event.preventDefault(); - event.stopPropagation(); - if (_menu) { - _menu.hide(); - } - _menu = new Menu(event.target); - } else if (event.altKey) { - _editor = new Editor(event.target); + if (!event.metaKey) { + return; + } + event.preventDefault(); + event.stopPropagation(); + if (event.altKey) { + _toggleEditor(event.target); + } else { + _toggleMenu(event.target); } } function onKeyUp(event) { - if (_setup && !event.metaKey && !event.altKey) { + if (_setup && !event.metaKey) { document.removeEventListener("keyup", onKeyUp); - document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseover", onMouseOver); document.removeEventListener("mouseout", onMouseOut); + document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("click", onClick); _localHighlight.clear(); _localHighlight = undefined; @@ -264,18 +291,15 @@ function RemoteFunctions() { } function onKeyDown(event) { - if (!_setup && (event.metaKey || event.altKey)) { + if (!_setup && event.metaKey) { document.addEventListener("keyup", onKeyUp); - document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseover", onMouseOver); document.addEventListener("mouseout", onMouseOut); + document.addEventListener("mousemove", onMouseMove); document.addEventListener("click", onClick); _localHighlight = new Highlight("#ecc"); _setup = true; } - - if (event.metaKey) { - _localHighlight.add(event.target, true); - } } @@ -283,11 +307,15 @@ function RemoteFunctions() { // show goto function showGoto(targets) { + if (!_currentMenu) { + return; + } + _currentMenu.createBody(); var i; for (i in targets) { - _menu.add(targets[i]); + _currentMenu.addItem(targets[i]); } - _menu.show(); + _currentMenu.show(); } // remove active highlights From f39cee0b268d651cf54962ac672e031659b3447b Mon Sep 17 00:00:00 2001 From: Dennis Kehrig Date: Wed, 1 Aug 2012 14:07:28 +0200 Subject: [PATCH 3/9] For LiveDevelopment errors: Remove the prefix "Uncaught" to prevent the development tools from popping up --- src/LiveDevelopment/LiveDevelopment.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/LiveDevelopment/LiveDevelopment.js b/src/LiveDevelopment/LiveDevelopment.js index 10eb07b3868..ecf02360c66 100644 --- a/src/LiveDevelopment/LiveDevelopment.js +++ b/src/LiveDevelopment/LiveDevelopment.js @@ -283,6 +283,9 @@ define(function LiveDevelopment(require, exports, module) { function _onError(error) { var message = error.message; + // Remove the prefix "Uncaught" to prevent the development tools from popping up + message = message.replace(/^uncaught\s/i, ""); + // Additional information, like exactly which parameter could not be processed. var data = error.data; if (Array.isArray(data)) { From a34f46762053174150dcc962ae4cc701c379195e Mon Sep 17 00:00:00 2001 From: Jonathan Diehl Date: Wed, 1 Aug 2012 14:15:46 +0200 Subject: [PATCH 4/9] Revert "For LiveDevelopment errors: Remove the prefix "Uncaught" to prevent the development tools from popping up" This reverts commit f39cee0b268d651cf54962ac672e031659b3447b. --- src/LiveDevelopment/LiveDevelopment.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/LiveDevelopment/LiveDevelopment.js b/src/LiveDevelopment/LiveDevelopment.js index ecf02360c66..10eb07b3868 100644 --- a/src/LiveDevelopment/LiveDevelopment.js +++ b/src/LiveDevelopment/LiveDevelopment.js @@ -283,9 +283,6 @@ define(function LiveDevelopment(require, exports, module) { function _onError(error) { var message = error.message; - // Remove the prefix "Uncaught" to prevent the development tools from popping up - message = message.replace(/^uncaught\s/i, ""); - // Additional information, like exactly which parameter could not be processed. var data = error.data; if (Array.isArray(data)) { From 7512f7a01efc78d8428919c937318f48ac68f457 Mon Sep 17 00:00:00 2001 From: Jonathan Diehl Date: Thu, 30 Aug 2012 11:21:24 +0200 Subject: [PATCH 5/9] some minor bug fixes on the highlighting and menu --- src/LiveDevelopment/Agents/RemoteFunctions.js | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/LiveDevelopment/Agents/RemoteFunctions.js b/src/LiveDevelopment/Agents/RemoteFunctions.js index 8c18f54e438..375490e816e 100644 --- a/src/LiveDevelopment/Agents/RemoteFunctions.js +++ b/src/LiveDevelopment/Agents/RemoteFunctions.js @@ -30,7 +30,7 @@ * modules should define a single function that returns an object of all * exported functions. */ -function RemoteFunctions() { +function RemoteFunctions(experimental) { "use strict"; // determine the color for a type @@ -75,10 +75,12 @@ function RemoteFunctions() { this.element = element; _trigger(this.element, "showgoto", 1, true); window.setTimeout(window.remoteShowGoto); + this.remove = this.remove.bind(this); } Menu.prototype = { onClick: function (url, event) { + event.preventDefault(); _trigger(this.element, "goto", url, true); this.remove(); }, @@ -136,12 +138,14 @@ function RemoteFunctions() { if (!this.body.parentNode) { document.body.appendChild(this.body); } + document.addEventListener("click", this.remove); }, remove: function () { if (this.body && this.body.parentNode) { document.body.removeChild(this.body); } + document.removeEventListener("click", this.remove); } }; @@ -157,6 +161,8 @@ function RemoteFunctions() { this.element.addEventListener("keypress", this.onKeyPress); this.revertText = this.element.innerHTML; + + _trigger(this.element, "edit", 1); } Editor.prototype = { @@ -164,7 +170,7 @@ function RemoteFunctions() { this.element.removeAttribute("contenteditable"); this.element.removeEventListener("blur", this.onBlur); this.element.removeEventListener("keypress", this.onKeyPress); - _trigger(this.element, "edit"); + _trigger(this.element, "edit", 0, true); }, onKeyPress: function (event) { @@ -173,20 +179,16 @@ function RemoteFunctions() { this.element.blur(); break; case 27: // esc - _trigger(this.element, "edit", this.revertText, true); + this.element.innerHTML = this.revertText; this.element.blur(); break; } - }, - - onChange: function (event) { - window.setTimeout(_trigger.bind(undefined, this.element, "edit", this.element.innerHTML)); } }; - function Highlight(color, autoclear) { + function Highlight(color, trigger) { this.color = color; - this.autoclear = !!autoclear; + this.trigger = !!trigger; this.elements = []; this.orgColors = []; } @@ -206,8 +208,8 @@ function RemoteFunctions() { if (this._elementExists(element) || element === document) { return; } - if (this.autoclear) { - this.clear(); + if (this.trigger) { + _trigger(element, "highlight", 1); } this.elements.push(element); this.orgColors.push(element.style.getPropertyValue("background-color")); @@ -219,6 +221,7 @@ function RemoteFunctions() { for (i in this.elements) { var e = this.elements[i]; e.style.setProperty("background-color", this.orgColors[i]); + _trigger(e, "highlight", 0, true); } this.elements = []; this.orgColors = []; @@ -297,7 +300,7 @@ function RemoteFunctions() { document.addEventListener("mouseout", onMouseOut); document.addEventListener("mousemove", onMouseMove); document.addEventListener("click", onClick); - _localHighlight = new Highlight("#ecc"); + _localHighlight = new Highlight("#ecc", true); _setup = true; } } @@ -345,10 +348,10 @@ function RemoteFunctions() { } } - // install event listeners - /* FUTURE - window.document.addEventListener("keydown", onKeyDown); - */ + // init + if (experimental) { + window.document.addEventListener("keydown", onKeyDown); + } return { "showGoto": showGoto, From be1919bfeaa956317ecad772f2260819ed4bfdb2 Mon Sep 17 00:00:00 2001 From: Jonathan Diehl Date: Thu, 30 Aug 2012 11:21:42 +0200 Subject: [PATCH 6/9] pass the experimental flag to the remote functions --- src/LiveDevelopment/Agents/RemoteAgent.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/LiveDevelopment/Agents/RemoteAgent.js b/src/LiveDevelopment/Agents/RemoteAgent.js index 04da263ddfa..33921bf2be4 100644 --- a/src/LiveDevelopment/Agents/RemoteAgent.js +++ b/src/LiveDevelopment/Agents/RemoteAgent.js @@ -37,6 +37,7 @@ define(function RemoteAgent(require, exports, module) { var $exports = $(exports); + var LiveDevelopment = require("LiveDevelopment/LiveDevelopment"); var Inspector = require("LiveDevelopment/Inspector/Inspector"); var _load; // deferred load @@ -48,7 +49,7 @@ define(function RemoteAgent(require, exports, module) { var request = new XMLHttpRequest(); request.open("GET", "LiveDevelopment/Agents/RemoteFunctions.js"); request.onload = function onLoad() { - var run = "window._LD=" + request.response + "()"; + var run = "window._LD=" + request.response + "(" + LiveDevelopment.config.experimental + ")"; Inspector.Runtime.evaluate(run, function onEvaluate(res) { console.assert(!res.wasThrown, res.result.description); _objectId = res.result.objectId; From cf7169a7fabf323239e3900d7bcaa16135305693 Mon Sep 17 00:00:00 2001 From: Jonathan Diehl Date: Thu, 30 Aug 2012 11:23:02 +0200 Subject: [PATCH 7/9] updated the edit agent to listen to dom characterdatamodified events instead of data-ld-edit events --- src/LiveDevelopment/Agents/DOMAgent.js | 6 +++- src/LiveDevelopment/Agents/EditAgent.js | 45 +++++++++++++++++++------ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/LiveDevelopment/Agents/DOMAgent.js b/src/LiveDevelopment/Agents/DOMAgent.js index ff1ae55db30..634c8bd5080 100644 --- a/src/LiveDevelopment/Agents/DOMAgent.js +++ b/src/LiveDevelopment/Agents/DOMAgent.js @@ -42,6 +42,7 @@ define(function DOMAgent(require, exports, module) { var Inspector = require("LiveDevelopment/Inspector/Inspector"); var RemoteAgent = require("LiveDevelopment/Agents/RemoteAgent"); + var EditAgent = require("LiveDevelopment/Agents/EditAgent"); var DOMNode = require("LiveDevelopment/Agents/DOMNode"); var DOMHelpers = require("LiveDevelopment/Agents/DOMHelpers"); @@ -269,7 +270,10 @@ define(function DOMAgent(require, exports, module) { value += text; value += node.value.substr(to - node.location); node.value = value; - Inspector.DOM.setNodeValue(node.nodeId, node.value); + if (!EditAgent.isEditing) { + // only update the DOM if the change was not caused by the edit agent + Inspector.DOM.setNodeValue(node.nodeId, node.value); + } } else { console.warn("Changing non-text nodes not supported."); } diff --git a/src/LiveDevelopment/Agents/EditAgent.js b/src/LiveDevelopment/Agents/EditAgent.js index e0144a2c84a..5d13d9fa602 100644 --- a/src/LiveDevelopment/Agents/EditAgent.js +++ b/src/LiveDevelopment/Agents/EditAgent.js @@ -39,6 +39,8 @@ define(function EditAgent(require, exports, module) { var EditorManager = require("editor/EditorManager"); + var _editedNode; + /** Find changed characters * @param {string} old value * @param {string} changed value @@ -74,28 +76,51 @@ define(function EditAgent(require, exports, module) { return { from: index, to: index + length, text: value }; } - // Remote Event: Go to the given source node - function _onRemoteEdit(event, res) { - // res = {nodeId, name, value} - var node = DOMAgent.nodeWithId(res.nodeId); - node = node.children[0]; - if (!node.location) { + // WebInspector Event: DOM.characterDataModified + function _onCharacterDataModified(event, res) { + // res = {nodeId, characterData} + if (_editedNode.nodeId !== res.nodeId) { return; } + GotoAgent.open(DOMAgent.url); var editor = EditorManager.getCurrentFullEditor(); var codeMirror = editor._codeMirror; - var change = _findChangedCharacters(node.value, res.value); + var change = _findChangedCharacters(_editedNode.value, res.characterData); if (change) { - var from = codeMirror.posFromIndex(node.location + change.from); - var to = codeMirror.posFromIndex(node.location + change.to); + var from = codeMirror.posFromIndex(_editedNode.location + change.from); + var to = codeMirror.posFromIndex(_editedNode.location + change.to); + exports.isEditing = true; editor.document.replaceRange(change.text, from, to); + exports.isEditing = false; - var newPos = codeMirror.posFromIndex(node.location + change.from + change.text.length); + var newPos = codeMirror.posFromIndex(_editedNode.location + change.from + change.text.length); editor.setCursorPos(newPos.line, newPos.ch); } } + // Remote Event: Go to the given source node + function _onRemoteEdit(event, res) { + // res = {nodeId, name, value} + + // detach from DOM change events + if (res.value === "0") { + $(Inspector.DOM).off(".EditAgent"); + return; + } + + // find and store the edited node + var node = DOMAgent.nodeWithId(res.nodeId); + node = node.children[0]; + if (!node.location) { + return; + } + _editedNode = node; + + // attach to character data modified events + $(Inspector.DOM).on("characterDataModified.EditAgent", _onCharacterDataModified); + } + /** Initialize the agent */ function load() { $(RemoteAgent).on("edit.EditAgent", _onRemoteEdit); From 8ad1b9d9594ee51df34f39d095aef87e22a68fdf Mon Sep 17 00:00:00 2001 From: Jonathan Diehl Date: Thu, 30 Aug 2012 11:24:30 +0200 Subject: [PATCH 8/9] several bug fixes in live development --- src/LiveDevelopment/Agents/HighlightAgent.js | 2 +- src/LiveDevelopment/Documents/CSSDocument.js | 33 +++++++++---------- src/LiveDevelopment/Documents/HTMLDocument.js | 2 +- src/LiveDevelopment/Documents/JSDocument.js | 2 +- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/LiveDevelopment/Agents/HighlightAgent.js b/src/LiveDevelopment/Agents/HighlightAgent.js index 3267e001e01..961eeb02b64 100644 --- a/src/LiveDevelopment/Agents/HighlightAgent.js +++ b/src/LiveDevelopment/Agents/HighlightAgent.js @@ -46,7 +46,7 @@ define(function HighlightAgent(require, exports, module) { if (res.value === "1") { node = DOMAgent.nodeWithId(res.nodeId); } - $(exports).triggerHandler("highlight", node); + $(exports).triggerHandler("highlight", [node]); } /** Hide in-browser highlighting */ diff --git a/src/LiveDevelopment/Documents/CSSDocument.js b/src/LiveDevelopment/Documents/CSSDocument.js index 241f5c09f75..ca786214aec 100644 --- a/src/LiveDevelopment/Documents/CSSDocument.js +++ b/src/LiveDevelopment/Documents/CSSDocument.js @@ -59,15 +59,16 @@ define(function CSSDocumentModule(require, exports, module) { var CSSDocument = function CSSDocument(doc, editor, inspector) { this.doc = doc; - // FUTURE: Highlighting is currently disabled, since this code doesn't yet know - // how to deal with different editors pointing at the same document. -/* + // only enable highlighting if this document is attached to the main editor this.editor = editor; this._highlight = []; - this.onHighlight = this.onHighlight.bind(this); - this.onCursorActivity = this.onCursorActivity.bind(this); - $(HighlightAgent).on("highlight", this.onHighlight); -*/ + if (this.editor) { + this.onHighlight = this.onHighlight.bind(this); + this.onCursorActivity = this.onCursorActivity.bind(this); + $(HighlightAgent).on("highlight", this.onHighlight); + $(this.editor).on("cursorActivity", this.onCursorActivity); + this.onCursorActivity(); + } // Add a ref to the doc since we're listening for change events this.doc.addRef(); @@ -76,11 +77,6 @@ define(function CSSDocumentModule(require, exports, module) { $(this.doc).on("change", this.onChange); $(this.doc).on("deleted", this.onDeleted); -/* - $(this.editor).on("cursorActivity", this.onCursorActivity); - this.onCursorActivity(); -*/ - // get the style sheet this.styleSheet = CSSAgent.styleForURL(this.doc.url); @@ -131,11 +127,12 @@ define(function CSSDocumentModule(require, exports, module) { $(this.doc).off("change", this.onChange); $(this.doc).off("deleted", this.onDeleted); this.doc.releaseRef(); -/* - $(HighlightAgent).off("highlight", this.onHighlight); - $(this.editor).off("cursorActivity", this.onCursorActivity); - this.onHighlight(); -*/ + + if (this.editor) { + $(HighlightAgent).off("highlight", this.onHighlight); + $(this.editor).off("cursorActivity", this.onCursorActivity); + this.onHighlight(); + } }; // find a rule in the given rules @@ -183,7 +180,7 @@ define(function CSSDocumentModule(require, exports, module) { }; /** Triggered by the HighlightAgent to highlight a node in the editor */ - CSSDocument.prototype.onHighlight = function onHighlight(node) { + CSSDocument.prototype.onHighlight = function onHighlight(event, node) { // clear an existing highlight var i; for (i in this._highlight) { diff --git a/src/LiveDevelopment/Documents/HTMLDocument.js b/src/LiveDevelopment/Documents/HTMLDocument.js index 19736008588..54cb5c36d70 100644 --- a/src/LiveDevelopment/Documents/HTMLDocument.js +++ b/src/LiveDevelopment/Documents/HTMLDocument.js @@ -104,7 +104,7 @@ define(function HTMLDocumentModule(require, exports, module) { }; /** Triggered by the HighlightAgent to highlight a node in the editor */ - HTMLDocument.prototype.onHighlight = function onHighlight(node) { + HTMLDocument.prototype.onHighlight = function onHighlight(event, node) { if (!node || !node.location) { if (this._highlight) { this._highlight.clear(); diff --git a/src/LiveDevelopment/Documents/JSDocument.js b/src/LiveDevelopment/Documents/JSDocument.js index c6c6db29f58..82e8b03b7af 100644 --- a/src/LiveDevelopment/Documents/JSDocument.js +++ b/src/LiveDevelopment/Documents/JSDocument.js @@ -99,7 +99,7 @@ define(function JSDocumentModule(require, exports, module) { }; /** Triggered by the HighlightAgent to highlight a node in the editor */ - JSDocument.prototype.onHighlight = function onHighlight(node) { + JSDocument.prototype.onHighlight = function onHighlight(event, node) { // clear an existing highlight var codeMirror = this.editor._codeMirror; var i; From 04ae860b196adb1ac77a358d373d9200ae8e0976 Mon Sep 17 00:00:00 2001 From: Jonathan Diehl Date: Thu, 30 Aug 2012 17:12:48 +0200 Subject: [PATCH 9/9] report the event, not its children --- src/LiveDevelopment/Agents/GotoAgent.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/LiveDevelopment/Agents/GotoAgent.js b/src/LiveDevelopment/Agents/GotoAgent.js index 3ba8580a321..6497a7042df 100644 --- a/src/LiveDevelopment/Agents/GotoAgent.js +++ b/src/LiveDevelopment/Agents/GotoAgent.js @@ -127,9 +127,7 @@ define(function GotoAgent(require, exports, module) { } for (i in node.events) { var trace = node.events[i]; - if (trace.children.length > 0) { - _makeJSTarget(targets, trace.children[0].callFrames[0]); - } + _makeJSTarget(targets, trace.callFrames[0]); } for (i in res.matchedCSSRules.reverse()) { _makeCSSTarget(targets, res.matchedCSSRules[i]);