Skip to content

Commit 2c3ce9f

Browse files
durranimlucas
authored andcommitted
INT-1588: CRUD Usability Improvements. (#432)
* INT-1588: Fix tabbing order in all edit/insert situations * INT-1588: Tab to next input on : * INT-1588: Better usability on ESC * INT-1588: Usability improvements on hotspot * INT-1588: Fix input sizes * INT-1588: Bump compile cache to 0.2.0 * INT-1588: Fixing hotspot functionality * INT-1589: Add help entries for CRUD * INT-1588: Remove feature flag, fix insert
1 parent 3e0b685 commit 2c3ce9f

17 files changed

+316
-101
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ report.json
1313
.compiled-sources/
1414
src/app/compiled-less/
1515
expansions.yml
16+
.nvmrc

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@
102102
"get-object-path": "azer/get-object-path#74eb42de0cfd02c14ffdd18552f295aba723d394",
103103
"hadron-action": "^0.0.4",
104104
"hadron-auto-update-manager": "^0.0.12",
105-
"hadron-compile-cache": "^0.1.0",
105+
"hadron-compile-cache": "^0.2.0",
106106
"hadron-component-registry": "^0.4.0",
107-
"hadron-document": "^0.13.0",
107+
"hadron-document": "^0.14.0",
108108
"hadron-ipc": "^0.0.7",
109109
"hadron-module-cache": "^0.0.3",
110110
"hadron-package-manager": "0.1.0",
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
title: Cloning a Single Document
3+
tags:
4+
- crud
5+
related:
6+
- inserting-a-single-document
7+
section: CRUD
8+
---
9+
10+
A single document can be cloned by clicking on the clone icon
11+
<i class='fa fa-clone' aria-hidden='true'></i> on the right
12+
side of the document in the document list. The Insert Document
13+
modal will appear with all elements in the document cloned with
14+
the exception of the `_id` element.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: Deleting a Single Document
3+
tags:
4+
- crud
5+
section: CRUD
6+
---
7+
8+
A single document can be deleted by clicking on the delete icon
9+
<i class='fa fa-trash-o' aria-hidden='true'></i> on the right
10+
side of the document in the document list. Clicking on this button puts
11+
the document into a pending delete mode, but the delete command will
12+
not be sent to the server until the user confirms the edits by clicking 'Delete'.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
title: Editing a Single Document
3+
tags:
4+
- crud
5+
section: CRUD
6+
---
7+
8+
A single document can be edited by clicking on the edit icon
9+
<i class='fa fa-pencil' aria-hidden='true'></i> on the right
10+
side of the document in the document list. Clicking on this button puts
11+
the document into edit mode, but changes will not be sent to the server
12+
until the user confirms the edits by clicking 'Update'.
13+
14+
In edit mode, the document panel behaves similar to the CSS editor in
15+
most modern web browers' development tools.
16+
17+
### Editing an Element
18+
19+
Clicking on an element key or value allows the user to change the key name
20+
or the value of the element. The element's type can also be changed by
21+
selecting a new type from the dropdown on the right side. Only types that
22+
the value can currently be cast to will be visible in the list. Duplicate
23+
key names will cause the key field to be highlighted in red.
24+
25+
### Adding an Element
26+
27+
A new element can be added to the document or any embedded document by
28+
either clicking on the right side of the element or tabbing off the last
29+
element's value field if the element is the last element in the document
30+
or sub document. Clicking to the right of an element will also remove any
31+
subsequent extra empty elements.
32+
33+
### Deleting an Element
34+
35+
An element can be deleted by clicking on the <i class='fa fa-times-circle' aria-hidden='true'></i>
36+
icon to the left of the element's line number.
37+
38+
### Reverting a Change
39+
40+
A change to an element can be reverted by clicking the revert icon
41+
<i class='fa fa-rotate-left' aria-hidden='true'></i>
42+
to the left of the element's line number.
43+
44+
### Persisting Changes
45+
46+
The changes to a document may be persisted by clicking on the 'Update'
47+
button in the footer of the document panel. Clicking this button will
48+
execute a `$findAndModify` on the server and update the document in the
49+
list.
50+
51+
### Canceling Changes
52+
53+
To exit edit mode and cancel all pending changes, click the 'Cancel' button.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
title: Inserting a Single Document
3+
tags:
4+
- crud
5+
related:
6+
- editing-a-single-document
7+
section: CRUD
8+
---
9+
10+
A single document can be inserted by clicking on the 'Insert' button at
11+
the top of the document list. An insert modal will open and the user
12+
may edit the new document using the same behaviour provided by document
13+
editing.

src/internal-packages/crud/lib/component/document-actions.jsx

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
'use strict';
22

33
const React = require('react');
4-
const app = require('ampersand-app');
54
const IconButton = require('./icon-button');
65

7-
/**
8-
* The feature flag.
9-
*/
10-
const FEATURE = 'singleDocumentCrud';
11-
126
/**
137
* Component for actions on the document.
148
*/
@@ -29,26 +23,21 @@ class DocumentActions extends React.Component {
2923
* @returns {Component} The actions component.
3024
*/
3125
render() {
32-
if (app.isFeatureEnabled(FEATURE)) {
33-
return (
34-
<div className='document-actions'>
35-
<IconButton
36-
title='Edit Document'
37-
iconClassName='fa fa-pencil'
38-
clickHandler={this.props.edit} />
39-
<IconButton
40-
title='Delete Document'
41-
iconClassName='fa fa-trash-o'
42-
clickHandler={this.props.remove} />
43-
<IconButton
44-
title='Clone Document'
45-
iconClassName='fa fa-clone'
46-
clickHandler={this.props.clone} />
47-
</div>
48-
);
49-
}
5026
return (
51-
<div className='document-actions'></div>
27+
<div className='document-actions'>
28+
<IconButton
29+
title='Edit Document'
30+
iconClassName='fa fa-pencil'
31+
clickHandler={this.props.edit} />
32+
<IconButton
33+
title='Delete Document'
34+
iconClassName='fa fa-trash-o'
35+
clickHandler={this.props.remove} />
36+
<IconButton
37+
title='Clone Document'
38+
iconClassName='fa fa-clone'
39+
clickHandler={this.props.clone} />
40+
</div>
5241
);
5342
}
5443
}

src/internal-packages/crud/lib/component/document.jsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use strict';
22

3-
const _ = require('lodash');
43
const app = require('ampersand-app');
54
const React = require('react');
65
const Reflux = require('reflux');
@@ -270,14 +269,15 @@ class Document extends React.Component {
270269
* @returns {Array} The editable elements.
271270
*/
272271
editableElements() {
273-
var components = _.map(this.state.doc.elements, (element) => {
274-
return (
275-
<EditableElement key={element.uuid} element={element} />
276-
);
277-
});
272+
var components = [];
273+
for (let element of this.state.doc.elements) {
274+
components.push(<EditableElement key={element.uuid} element={element} />)
275+
}
276+
// Add the hotspot to the end. In the case of insert, we need to guard against
277+
// No elements being present.
278278
var lastComponent = components[components.length - 1];
279-
var lastElement = lastComponent ? lastComponent.props.element : null;
280-
components.push(<Hotspot key='hotspot' doc={this.state.doc} element={lastElement} />);
279+
var lastElement = lastComponent ? lastComponent.props.element : this.state.doc;
280+
components.push(<Hotspot key='hotspot' element={lastElement} />);
281281
return components;
282282
}
283283

src/internal-packages/crud/lib/component/editable-element.jsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,10 @@ class EditableElement extends React.Component {
116116
<li className={this.style()}>
117117
<div className='line-number'></div>
118118
{this.renderAction()}
119-
<EditableKey element={this.element} insertIndex={this.props.insertIndex} />
119+
<EditableKey element={this.element} index={this.props.index} />
120120
:
121121
{this.renderValue()}
122+
<Hotspot key='hotspot' element={this.element} />
122123
<Types element={this.element} />
123124
</li>
124125
);
@@ -149,7 +150,7 @@ class EditableElement extends React.Component {
149150
<div className='line-number' onClick={this.toggleExpandable.bind(this)}></div>
150151
{this.renderAction()}
151152
<div className={CARET} onClick={this.toggleExpandable.bind(this)}></div>
152-
<EditableKey element={this.element} />
153+
<EditableKey element={this.element} index={this.props.index} />
153154
:
154155
<div className={LABEL_CLASS} onClick={this.toggleExpandable.bind(this)}>
155156
{this.element.currentType}
@@ -168,12 +169,12 @@ class EditableElement extends React.Component {
168169
* @returns {Array} The components.
169170
*/
170171
elementComponents() {
171-
var components = _.map(this.element.elements, (element) => {
172-
return (<EditableElement key={element.uuid} element={element} />);
173-
});
174-
// var lastComponent = components[components.length - 1];
175-
// var lastElement = lastComponent ? lastComponent.props.element : null;
176-
// components.push(<Hotspot key='hotspot' doc={this.element} element={lastElement} />);
172+
var components = [];
173+
var index = 0;
174+
for (let element of this.element.elements) {
175+
components.push(<EditableElement key={element.uuid} element={element} index={index} />);
176+
index++;
177+
}
177178
return components;
178179
}
179180

src/internal-packages/crud/lib/component/editable-key.jsx

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const React = require('react');
4+
const inputSize = require('./utils').inputSize;
45

56
/**
67
* The editing class constant.
@@ -17,6 +18,16 @@ const DUPLICATE = 'duplicate';
1718
*/
1819
const KEY_CLASS = 'editable-key';
1920

21+
/**
22+
* Escape key code.
23+
*/
24+
const ESC = 27;
25+
26+
/**
27+
* Colon key code.
28+
*/
29+
const COLON = 186;
30+
2031
/**
2132
* General editable key component.
2233
*/
@@ -38,15 +49,8 @@ class EditableKey extends React.Component {
3849
* to the value field.
3950
*/
4051
componentDidMount() {
41-
if (this.element.isAdded()) {
42-
if (this.props.insertIndex) {
43-
// Focus for inserting new documents.
44-
if (this.props.insertIndex === 1 && this.element.currentKey === '') {
45-
this._node.focus();
46-
}
47-
} else if (!this.isEditable() && this._node) {
48-
this._node.focus();
49-
}
52+
if (this.isAutoFocusable()) {
53+
this._node.focus();
5054
}
5155
}
5256

@@ -61,16 +65,25 @@ class EditableKey extends React.Component {
6165
type='text'
6266
className={this.style()}
6367
ref={(c) => this._node = c}
64-
size={this.element.currentKey.length}
68+
size={inputSize(this.renderValue())}
69+
tabIndex={this.isEditable() ? 0 : -1}
6570
onBlur={this.handleBlur.bind(this)}
6671
onFocus={this.handleFocus.bind(this)}
6772
onChange={this.handleChange.bind(this)}
6873
onKeyDown={this.handleKeyDown.bind(this)}
69-
value={this.element.currentKey}
74+
onKeyUp={this.handleKeyUp.bind(this)}
75+
value={this.renderValue()}
7076
title={this.renderTitle()} />
7177
);
7278
}
7379

80+
/**
81+
* Render the value of the key.
82+
*/
83+
renderValue() {
84+
return this.element.parent.currentType === 'Array' ? this.props.index : this.element.currentKey;
85+
}
86+
7487
/**
7588
* Render the title.
7689
*
@@ -88,8 +101,27 @@ class EditableKey extends React.Component {
88101
* @param {Event} evt - The event.
89102
*/
90103
handleKeyDown(evt) {
91-
if (evt.keyCode === 27) {
92-
this._node.blur();
104+
var value = evt.target.value;
105+
if (evt.keyCode === ESC) {
106+
if (value.length === 0) {
107+
this.element.remove();
108+
} else {
109+
this._node.blur();
110+
}
111+
}
112+
}
113+
114+
/**
115+
* If they key is a colon, tab to the next input.
116+
*/
117+
handleKeyUp(evt) {
118+
if (evt.keyCode === COLON) {
119+
var value = evt.target.value;
120+
if (value !== ':') {
121+
this.element.rename(value.replace(':', ''));
122+
evt.target.value = '';
123+
this._node.nextSibling.nextSibling.focus();
124+
}
93125
}
94126
}
95127

@@ -100,7 +132,7 @@ class EditableKey extends React.Component {
100132
*/
101133
handleChange(evt) {
102134
var value = evt.target.value;
103-
this._node.size = value.length;
135+
this._node.size = inputSize(value);
104136
if (this.isEditable()) {
105137
if (this.element.isDuplicateKey(value)) {
106138
this.setState({ duplicate: true });
@@ -130,12 +162,30 @@ class EditableKey extends React.Component {
130162
}
131163

132164
/**
133-
* Is this component editable?
165+
* Is this component auto focusable?
166+
*
167+
* This is true if:
168+
* - When a new element has been added and is a normal element.
169+
* - When not being tabbed into.
170+
*
171+
* Is false if:
172+
* - When a new array value has been added.
173+
* - When the key is _id
134174
*
135175
* @returns {Boolean} If the component is editable.
136176
*/
177+
isAutoFocusable() {
178+
return this.element.isAdded() && this.isEditable();
179+
}
180+
181+
/**
182+
* Is the key able to be edited?
183+
*
184+
* @returns {Boolean} If the key can be edited.
185+
*/
137186
isEditable() {
138-
return this.element.isKeyEditable() && this.element.parentElement.currentType !== 'Array';
187+
return this.element.isKeyEditable() &&
188+
this.element.parent.currentType !== 'Array';
139189
}
140190

141191
/**

0 commit comments

Comments
 (0)