Implemented keyboard shortcuts to select multiple fields

This commit is contained in:
jos 2015-12-30 20:29:45 +01:00
parent fe5a413f23
commit 96c4204b1c
4 changed files with 76 additions and 28 deletions

View File

@ -5,7 +5,8 @@
Key | Description Key | Description
----------------------- | ------------------------------------------------ ----------------------- | ------------------------------------------------
Alt+Arrows | Move the caret up/down/left/right between fields Alt+Arrows | Move the caret up/down/left/right between fields
Shift+Alt+Arrows | Move field up/down/left/right Shift+Alt+Arrows | Move current field or selected fields up/down/left/right
Shift+Arrow Up/Down | Select multiple fields
Ctrl+D | Duplicate field Ctrl+D | Duplicate field
Ctrl+Del | Remove field Ctrl+Del | Remove field
Ctrl+Enter | Open link when on a field containing an url Ctrl+Enter | Open link when on a field containing an url

View File

@ -233,7 +233,8 @@ tr.jsoneditor-selected button.jsoneditor-contextmenu {
visibility: hidden; visibility: hidden;
} }
tr.jsoneditor-selected.jsoneditor-first button { tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea,
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu {
visibility: visible; visibility: visible;
} }

View File

@ -2004,6 +2004,8 @@ Node.prototype.onKeyDown = function (event) {
var editable = this.editor.options.mode === 'tree'; var editable = this.editor.options.mode === 'tree';
var oldSelection; var oldSelection;
var oldBeforeNode; var oldBeforeNode;
var nodes;
var multiselection;
// util.log(ctrlKey, keynum, event.charCode); // TODO: cleanup // util.log(ctrlKey, keynum, event.charCode); // TODO: cleanup
if (keynum == 13) { // Enter if (keynum == 13) { // Enter
@ -2027,7 +2029,10 @@ Node.prototype.onKeyDown = function (event) {
} }
else if (keynum == 68) { // D else if (keynum == 68) { // D
if (ctrlKey && editable) { // Ctrl+D if (ctrlKey && editable) { // Ctrl+D
Node.onDuplicate(this); nodes = this.editor.multiselection.nodes.length > 0
? this.editor.multiselection.nodes
: this;
Node.onDuplicate(nodes);
handled = true; handled = true;
} }
} }
@ -2046,7 +2051,10 @@ Node.prototype.onKeyDown = function (event) {
} }
else if (keynum == 46 && editable) { // Del else if (keynum == 46 && editable) { // Del
if (ctrlKey) { // Ctrl+Del if (ctrlKey) { // Ctrl+Del
Node.onRemove(this); nodes = this.editor.multiselection.nodes.length > 0
? this.editor.multiselection.nodes
: this;
Node.onRemove(nodes);
handled = true; handled = true;
} }
} }
@ -2127,10 +2135,25 @@ Node.prototype.onKeyDown = function (event) {
// find the previous node // find the previous node
prevNode = this._previousNode(); prevNode = this._previousNode();
if (prevNode) { if (prevNode) {
this.editor.deselect(true);
prevNode.focus(Node.focusElement || this._getElementName(target)); prevNode.focus(Node.focusElement || this._getElementName(target));
} }
handled = true; handled = true;
} }
else if (!altKey && shiftKey) { // Shift + Arrow Up
// select multiple nodes
prevNode = this._previousNode();
if (prevNode) {
multiselection = this.editor.multiselection;
multiselection.start = multiselection.start || this;
multiselection.end = prevNode;
nodes = this.editor._findTopLevelNodes(multiselection.start, multiselection.end);
this.editor.select(nodes);
prevNode.focus('field'); // select field as we know this always exists
}
handled = true;
}
else if (altKey && shiftKey) { // Alt + Shift + Arrow Up else if (altKey && shiftKey) { // Alt + Shift + Arrow Up
// find the previous node // find the previous node
prevNode = this._previousNode(); prevNode = this._previousNode();
@ -2191,10 +2214,25 @@ Node.prototype.onKeyDown = function (event) {
// find the next node // find the next node
nextNode = this._nextNode(); nextNode = this._nextNode();
if (nextNode) { if (nextNode) {
this.editor.deselect(true);
nextNode.focus(Node.focusElement || this._getElementName(target)); nextNode.focus(Node.focusElement || this._getElementName(target));
} }
handled = true; handled = true;
} }
else if (!altKey && shiftKey) { // Shift + Arrow Down
// select multiple nodes
nextNode = this._nextNode();
if (nextNode) {
multiselection = this.editor.multiselection;
multiselection.start = multiselection.start || this;
multiselection.end = nextNode;
nodes = this.editor._findTopLevelNodes(multiselection.start, multiselection.end);
this.editor.select(nodes);
nextNode.focus('field'); // select field as we know this always exists
}
handled = true;
}
else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Down else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Down
// find the 2nd next node and move before that one // find the 2nd next node and move before that one
if (this.expanded) { if (this.expanded) {
@ -2310,7 +2348,7 @@ Node.onDuplicate = function(nodes) {
var parent = lastNode.parent; var parent = lastNode.parent;
var editor = lastNode.editor; var editor = lastNode.editor;
editor.deselect(editor.multiselect.nodes); editor.deselect(editor.multiselection.nodes);
// duplicate the nodes // duplicate the nodes
var oldSelection = editor.getSelection(); var oldSelection = editor.getSelection();

View File

@ -36,7 +36,9 @@ treemode.create = function (container, options) {
this.dom = {}; this.dom = {};
this.highlighter = new Highlighter(); this.highlighter = new Highlighter();
this.selection = undefined; // will hold the last input selection this.selection = undefined; // will hold the last input selection
this.multiselection = []; this.multiselection = {
nodes: []
};
this._setOptions(options); this._setOptions(options);
@ -422,7 +424,7 @@ treemode.getSelection = function () {
return { return {
dom: domFocus, dom: domFocus,
range: range, range: range,
nodes: this.multiselection.slice(0), nodes: this.multiselection.nodes.slice(0),
scrollTop: this.content ? this.content.scrollTop : 0 scrollTop: this.content ? this.content.scrollTop : 0
}; };
}; };
@ -656,7 +658,7 @@ treemode._onEvent = function (event) {
if (event.type == 'mousedown') { if (event.type == 'mousedown') {
// drag multiple nodes // drag multiple nodes
Node.onDragStart(this.multiselection, event); Node.onDragStart(this.multiselection.nodes, event);
} }
} }
else { else {
@ -682,10 +684,10 @@ treemode._onEvent = function (event) {
treemode._onMultiSelectStart = function (event) { treemode._onMultiSelectStart = function (event) {
var node = Node.getNodeFromTarget(event.target); var node = Node.getNodeFromTarget(event.target);
this.multiselection = []; this.multiselection = {
this.drag = {
start: node || null, start: node || null,
end: null end: null,
nodes: []
}; };
var editor = this; var editor = this;
@ -708,27 +710,28 @@ treemode._onMultiSelect = function (event) {
var node = Node.getNodeFromTarget(event.target); var node = Node.getNodeFromTarget(event.target);
if (node) { if (node) {
if (this.drag.start == null) { if (this.multiselection.start == null) {
this.drag.start = node; this.multiselection.start = node;
} }
this.drag.end = node; this.multiselection.end = node;
} }
// deselect previous selection // deselect previous selection
this.deselect(); this.deselect();
// find the selected nodes in the range from first to last // find the selected nodes in the range from first to last
var start = this.drag.start; var start = this.multiselection.start;
var end = this.drag.end || this.drag.start; var end = this.multiselection.end || this.multiselection.start;
if (start && end) { if (start && end) {
// find the top level childs, all having the same parent // find the top level childs, all having the same parent
this.multiselection = this._findTopLevelNodes(start, end); this.multiselection.nodes = this._findTopLevelNodes(start, end);
this.select(this.multiselection); this.select(this.multiselection.nodes);
} }
}; };
treemode._onMultiSelectEnd = function (event) { treemode._onMultiSelectEnd = function (event) {
delete this.drag; this.multiselection.start = null;
this.multiselection.end = null;
// cleanup global event listeners // cleanup global event listeners
if (this.mousemove) { if (this.mousemove) {
@ -743,13 +746,18 @@ treemode._onMultiSelectEnd = function (event) {
/** /**
* deselect currently selected nodes * deselect currently selected nodes
* @param {boolean} [clearStartAndEnd=false] If true, the `start` and `end`
* state is cleared too.
*/ */
treemode.deselect = function () { treemode.deselect = function (clearStartAndEnd) {
if (this.multiselection) { this.multiselection.nodes.forEach(function (node) {
this.multiselection.forEach(function (node) {
node.setSelected(false); node.setSelected(false);
}); });
this.multiselection = []; this.multiselection.nodes = [];
if (clearStartAndEnd) {
this.multiselection.start = null;
this.multiselection.end = null;
} }
}; };
@ -765,7 +773,7 @@ treemode.select = function (nodes) {
if (nodes) { if (nodes) {
this.deselect(); this.deselect();
this.multiselection = nodes.slice(0); this.multiselection.nodes = nodes.slice(0);
var first = nodes[0]; var first = nodes[0];
nodes.forEach(function (node) { nodes.forEach(function (node) {
@ -935,7 +943,7 @@ treemode.showContextMenu = function (anchor, onClose) {
title: 'Duplicate selected fields (Ctrl+D)', title: 'Duplicate selected fields (Ctrl+D)',
className: 'jsoneditor-duplicate', className: 'jsoneditor-duplicate',
click: function () { click: function () {
Node.onDuplicate(editor.multiselection); Node.onDuplicate(editor.multiselection.nodes);
} }
}); });
@ -945,7 +953,7 @@ treemode.showContextMenu = function (anchor, onClose) {
title: 'Remove selected fields (Ctrl+Del)', title: 'Remove selected fields (Ctrl+Del)',
className: 'jsoneditor-remove', className: 'jsoneditor-remove',
click: function () { click: function () {
Node.onRemove(editor.multiselection); Node.onRemove(editor.multiselection.nodes);
} }
}); });