From 8d5b39382e0545e2df27570498894cfa65311798 Mon Sep 17 00:00:00 2001 From: Ed Hager Date: Fri, 28 Jun 2013 13:00:35 -0700 Subject: [PATCH] Fix #496: Maintain focus through a row update. Also added code to clean up focus settings on blur. This was not being done before. I needed it for my update and it seems like a good thing to have in general. --- Keyboard.js | 105 +++++++++++++++++++++++++++++------- test/Focus_row_updates.html | 98 +++++++++++++++++++++++++++++++++ test/data/index.json | 6 +++ 3 files changed, 191 insertions(+), 18 deletions(-) create mode 100644 test/Focus_row_updates.html diff --git a/Keyboard.js b/Keyboard.js index 96fe5d608..25835e004 100644 --- a/Keyboard.js +++ b/Keyboard.js @@ -149,6 +149,53 @@ var Keyboard = declare(null, { }); } enableNavigation(this.contentNode); + + // When a row is updated, one row is removed and another is inserted. + // Aspect removeRow to record info about the old row so focus can be restored to the new node. + aspect.before(this, "removeRow", function(row){ + // Looking for focused rows not headers + var focusedNode = grid._focusedNode; + if(focusedNode){ + var focusedRow = grid.row(focusedNode); + // Is the focused node in the row being removed? + if(focusedRow && focusedRow.element === row){ + // Save the row id. + grid._restoreFocusData = { + rowId: row.getAttribute("id") + }; + // If focus is on a cell, record the column id as well. + if(this.cellNavigation){ + var column = grid.cell(focusedNode).column; + if(column){ + grid._restoreFocusData.columnId = column.id; + } + } + }else{ + grid._restoreFocusData = undefined; + } + } + }, true); + + aspect.after(this, "insertRow", function(row){ + var restoreFocus = grid._restoreFocusData; + if(restoreFocus && restoreFocus.rowId === row.getAttribute("id")){ + if(restoreFocus.columnId == null){ + grid.focus(row); + }else{ + grid.focus(grid.cell(row, restoreFocus.columnId).element) + } + } + return row; + }); + }, + + destroy: function(){ + // Clean up the focus signal if it is still hanging around. + var signal = this._focusSignal; + if(signal){ + signal.destroy(); + this._focusSignal = undefined; + } }, addKeyHandler: function(key, callback, isHeader){ @@ -178,11 +225,22 @@ var Keyboard = declare(null, { input, numInputs, inputFocused, - i; - + i, + grid = this; + + // Remove any saved focus restore info. + this._restoreFocusData = undefined; + element = cell && cell.element; if(!element){ return; } - + + // Set up a listener for blur events to clean up any styles and emit custom events. + this._focusSignal = on(element, "blur", function(event){ + grid._focusSignal.remove(); + grid._focusSignal = undefined; + grid._handleBlur(element, isHeader); + }); + if(this.cellNavigation){ inputs = element.getElementsByTagName("input"); for(i = 0, numInputs = inputs.length; i < numInputs; i++){ @@ -207,20 +265,6 @@ var Keyboard = declare(null, { // Opera throws if you try to set it to true if it is already true. event.bubbles = true; } - if(focusedNode){ - // Clean up previously-focused element - // Remove the class name and the tabIndex attribute - put(focusedNode, "!dgrid-focus[!tabIndex]"); - if(has("ie") < 8){ - // Clean up after workaround below (for non-input cases) - focusedNode.style.position = ""; - } - - // Expose object representing focused cell or row losing focus, via - // event.cell or event.row; which is set depends on cellNavigation. - event[cellOrRowType] = this[cellOrRowType](focusedNode); - on.emit(element, "dgrid-cellfocusout", event); - } focusedNode = this[focusedNodeProperty] = element; // Expose object representing focused cell or row gaining focus, via @@ -239,10 +283,35 @@ var Keyboard = declare(null, { element.tabIndex = this.tabIndex; element.focus(); } + put(element, ".dgrid-focus"); on.emit(focusedNode, "dgrid-cellfocusin", event); }, - + + _handleBlur: function(element, isHeader){ + var focusedNodeProperty = "_focused" + (isHeader ? "Header" : "") + "Node", + focusedNode = this[focusedNodeProperty], + cellOrRowType = this.cellNavigation ? "cell" : "row"; + + // Clean up previously-focused element + // Remove the class name and the tabIndex attribute + put(element, "!dgrid-focus[!tabIndex]"); + if(has("ie") < 8){ + // Clean up after workaround below (for non-input cases) + element.style.position = ""; + } + + // Expose object representing focused cell or row losing focus, via + // event.cell or event.row; which is set depends on cellNavigation. + event[cellOrRowType] = this[cellOrRowType](element); + on.emit(element, "dgrid-cellfocusout", event); + + // If focus has not moved to another cell/row, then remove the property. + if(focusedNode === element){ + this[focusedNodeProperty] = undefined; + } + }, + focusHeader: function(element){ this._focusOnNode(element || this._focusedHeaderNode, true); }, diff --git a/test/Focus_row_updates.html b/test/Focus_row_updates.html new file mode 100644 index 000000000..cf815d6f1 --- /dev/null +++ b/test/Focus_row_updates.html @@ -0,0 +1,98 @@ + + + + GH #226: Row loses selection after .notify + + + + + + + + + +
+
Grid row updates with Observable store and row focus
+
+
ID 5 updates every 3 seconds. Set focus there and make sure it remains.
+ +
+
Grid row updates with Observable store and cell focus
+
+
ID 5 updates every 3 seconds. Set focus there and make sure it remains.
+ + + diff --git a/test/data/index.json b/test/data/index.json index 965c800f9..b8ddf2675 100644 --- a/test/data/index.json +++ b/test/data/index.json @@ -285,6 +285,12 @@ "title": "Test Selection", "parent": "" }, + { + "name": "Focus_row_updates.html", + "url": "Focus_row_updates.html", + "title": "Test Focus with store notifications", + "parent": "" + }, { "name": "selector.html", "url": "selector.html",