Overview
This article introduces how to create a Custom View on a Kintone App, where records can be resorted with drag and drop. The resorting feature is accomplished with
SortableJs
, a JavaScript library for reorderable drag-and-drop lists
Sample Image
When the user navigates to the Custom View, a list of records is displayed in a custom made table. Users can click and drag rows of the table to change the sort order of the records.
Note the order of the first two records in the screenshot below:
The rows can be placed into a different location by using drag-and-drop.
The result is then saved with the new record order.
Prepare the App
Create an App
with the following field and settings. Save the form when finished.
Field Type |
Field Name |
Field Code |
Remarks |
Record number |
Record number |
recordId |
|
Number |
Sort order |
orderNum |
Initial value: 0 |
Text |
Participant Name |
part_name |
|
Text |
State |
state |
|
Create the View
From the View settings, create a new View and select Custom View. Check the Enable Pagination option. Type the following code into the HTML Code option.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
<table class="sortable_recordlist">
<thead id="sortable_thead">
<tr><th>Record Number</th><th>Participant Name</th><th>State</th></tr>
</thead>
<tbody id="sortable_tbody"></tbody>
</table>
<style type="text/css">
.sortable_recordlist {
width: 100%;
font-size: 14px;
border-radius: 3px;
}
#sortable_thead th {
background: #ffffff;
text-align: left;
}
#sortable_tbody th {
background: #f5f5f5;
}
#sortable_tbody tr:nth-child(even) {
background: #ffffff;
}
#sortable_tbody tr:nth-child(odd) {
background: #f5f5f5;
}
.sortable_recordlist tr
{
padding : 10px;
border: 1px solid #e3e7e8;
}
.sortable_recordlist th,
.sortable_recordlist td
{
padding: 10px;
border-right: 1px solid #e3e7e8;
}
.ghost {
opacity: 0.5;
background: #c8ebfb;
}
</style>
|
The completed Custom View settings page should look as follows.
Save the View settings and apply the changes to the App. Navigate to the new Custom View through the blue drop-down list. The view should be displayed with no data inside.
Set the Libraries
This sample uses
jQuery
v3.6.0 and
SortableJS
. Set the following URLs into the App's
JavaScript and CSS Customization settings
.
- https://code.jquery.com/jquery-3.6.0.min.js
- https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js
Sample Code
Type the following code into a text editor and save it as a JavaScript file. Upload it to the App's
JavaScript and CSS Customization settings
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
(function() {
'use strict';
/**
* Display sortable view
*/
function SortableRecordsManager(records) {
this.records = records;
this.recordOrderMap = records.map((recordData) => {
// Initialize sort order as record number when orderNum is 0
if (recordData.orderNum.value === '0') {
recordData.orderNum.value = recordData.recordId.value;
}
return {'recordId': recordData.recordId.value, 'orderNum': recordData.orderNum.value};
});
// Compare the rearranged id's array with the previous id's array and get the record number and ordering pair
this.update = function(updatedArray) {
const tmp = $.extend(true, {}, this.recordOrderMap);
const updateRecordOrderMap = $.extend(true, {}, this.recordOrderMap);
const len = updatedArray.length;
for (let i = 0; i < len; i++) {
const prev = tmp[i].recordId;
const cur = updatedArray.shift();
if (prev !== cur) {
updateRecordOrderMap[i].recordId = cur;
}
}
this.recordOrderMap = $.extend(true, {}, updateRecordOrderMap);
return this.recordOrderMap;
};
// Add rows for all records
this.createTableRecords = function() {
const tb = document.getElementById('sortable_tbody');
// Number of additional records
const len = this.records.length;
// Add rows for all records
for (let i = 0; i < len; i++) {
const record = this.records[i];
// Create row
const row = tb.insertRow(tb.rows.length);
row.id = record.recordId.value;
row.className = 'row';
const cell1 = row.insertCell(0);
const cell2 = row.insertCell(1);
const cell3 = row.insertCell(2);
const cellText1 = document.createTextNode(record.recordId.value);
const cellText2 = document.createTextNode(record.part_name.value);
const cellText3 = document.createTextNode(record.state.value);
cell1.appendChild(cellText1);
cell2.appendChild(cellText2);
cell3.appendChild(cellText3);
}
};
// Clear the table
this.destroyTableRecords = function() {
document.getElementById('sortable_tbody').innerHTML = '';
};
}
/**
* Communicate with Kintone
*/
function KintoneRecordManager() {
this.records = [];
this.appId = kintone.app.getId();
this.query = '';
this.limit = 100;
this.offset = 0;
// Update all sort order
this.updateOrderNums = function(recordOrderNumArray) {
const records = [];
for (const key in recordOrderNumArray) {
if (Object.prototype.hasOwnProperty.call(recordOrderNumArray, key)) {
records.push(
{
id: recordOrderNumArray[key].recordId,
record: {
orderNum: {
value: recordOrderNumArray[key].orderNum
}
}
}
);
}
}
kintone.api('/k/v1/records', 'PUT', {
app: this.appId,
records: records
});
};
// Get all sorted records in "Sort order"
this.getSortedRecords = function(callback) {
this.query = kintone.app.getQueryCondition() + 'order by orderNum asc';
this.getRecords(callback);
};
// Get all records
this.getRecords = function(callback) {
kintone.api('/k/v1/records', 'GET', {
app: this.appId,
query: this.query + (' limit ' + this.limit + ' offset ' + this.offset)
}, (function(_this) {
return function(res) {
Array.prototype.push.apply(_this.records, res.records);
const len = res.records.length;
_this.offset += len;
if (len < _this.limit) {
_this.ready = true;
if (callback !== null) {
callback(_this.records);
}
} else {
_this.getRecords(callback);
}
};
})(this));
};
}
// Record list view
kintone.events.on('app.record.index.show', (event) => {
if (event.viewId !== 6160528) {
return event;
}
const kintoneRecordManager = new KintoneRecordManager();
let sortableRecordsManager = null;
kintoneRecordManager.getSortedRecords((sortedRecords) => {
sortableRecordsManager = new SortableRecordsManager(sortedRecords);
sortableRecordsManager.destroyTableRecords();
sortableRecordsManager.createTableRecords();
});
const sortElement = document.getElementById('sortable_tbody');
new Sortable(sortElement, {
animation: 150,
ghostClass: 'ghost',
onUpdate:
function() {
// Get an array of id of sorted elements
const trElement = sortElement.querySelectorAll('tr');
const updated = [];
for (const key in trElement) {
if (Object.prototype.hasOwnProperty.call(trElement, key)) {
updated.push(
trElement[key].id
);
}
}
// Compare the rearranged id's array with the previous id's array and get the record number and ordering pair
const result = sortableRecordsManager.update(updated);
// Display the result in Kintone
kintoneRecordManager.updateOrderNums(result);
}
});
return event;
});
})();
|
The JavaScript and CSS Customization settings should look like the following:
Attention
Caution:
The order in which JavaScript and CSS are uploaded to an app matters. In this example, ensure that the jQuery and SortableJS libraries are uploaded before the sample JavaScript file. The order of the uploads can be changed by clicking and dragging on the arrows for each item on the Upload JavaScript / CSS page.
Code Explanation
SortableRecordsManager
A constructor function called SortableRecordsManager is created that is used to display the Sortable enhanced custom view. This constructor function is then given three methods: update
, createTableRecords
, and destroyTableRecords
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
function SortableRecordsManager(records) {
this.records = records;
this.update = function(updatedArray) {
// ...
};
this.createTableRecords = function() {
// ...
};
this.destroyTableRecords = function() {
// ...
};
}
|
The update
method takes an array of sorted element IDs as its argument. It compares it with the previous array of IDs and order numbers to get an array of objects with each row's record number and ordering pair. The image below explains this process.
The createTableRecords
method references the HTML table in the custom view called sortable_tbody and creates a row in the table for each record.
The destroyTableRecords
method then replaces the HTML table contents with an empty string to delete all of the rows in the table.
KintoneRecordManager
Another constructor function called KintoneRecordManager is created. This function is used to organize the functions that utilize Kintone APIs to retrieve and update data. This function also has three methods: updateOrderNums
, getSortedRecords
, and getRecords
.
The updateOrderNums
method takes the array of objects created by the update
method of the SortableRecordsManager constructor function as its argument. It then uses the
Update Records REST API to update the values of the Sort order field with the sort order values from the array.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
function KintoneRecordManager() {
this.records = [];
this.appId = kintone.app.getId();
this.query = '';
this.limit = 100;
this.offset = 0;
this.updateOrderNums = function(recordOrderNumArray) {
// ...
};
this.getSortedRecords = function(callback) {
// ...
};
this.getRecords = function(callback) {
// ...
};
}
|
The getSortedRecords
method takes a callback function as its argument. It uses the
kintone.app.getQueryCondition() method to retrieve the filter settings for the current view. It then adds the specification to order the records by ascending orderNum, and sets the value into this.query. The callback function is then passed to the getRecords
method.
The getRecords
method also takes the callback function that was passed as the getSortedRecords argument, as its own argument. It uses the query that was created in the getSortedRecords
method to call the
Get Records REST API. If the number of retrieved records is less than the set limit of 100, the callback function runs. If the number of retrieved records is exactly 100, the getRecords
method is called again and continues to be called until the number of retrieved records is less than 100.
app.record.index.show Event
The last section of the code uses the previously created SortableRecordsManager and KintoneRecordManager constructor functions when the record list is loaded.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
kintone.events.on('app.record.index.show', function(event) {
var kintoneRecordManager = new KintoneRecordManager();
var sortableRecordsManager = null;
kintoneRecordManager.getSortedRecords(function(sortedRecords) {
sortableRecordsManager = new SortableRecordsManager(sortedRecords);
sortableRecordsManager.destroyTableRecords();
sortableRecordsManager.createTableRecords();
});
$('#sortable_tbody').sortable({
update: function(_event, ui) {
var updated = $('#sortable_tbody').sortable('toArray');
var result = sortableRecordsManager.update(updated);
kintoneRecordManager.updateOrderNums(result);
}
});
});
|
The
app.record.index.show event is declared so that the code within the event declaration is automatically run when the record list is loaded. A new instance of KintoneRecordManager is created and passed to the variable kintoneRecordManager. The variable sortableRecordsManager is also prepared, but is passed null for its value.
The getSortedRecords
method is run with the callback function creating a new instance of SortableRecordsManager and passes the value to the prepared sortableRecordsManager variable. The destroyTableRecords
method is used to reset the table. The createTableRecords
method is used to rebuild the table with the necessary number of rows.
Finally, SortableJS's sortable
method is called on the element with the ID of sortable_tbody. With the sortable
method, a function is given to the update property, which is an event that is triggered when the rearrangement of the sortable element has ended. In this case, sortable's
toArray
method is used to get an array of the sorted ID elements. The array is then passed to the update
method of the sortableRecordsManager instance to create the updated result. To update the Sort order field values in the actual Kintone records, the updateOrderNums
method of the kintoneRecordManager instance is called. With this, the new sort order is maintained even when the page is refreshed.
Reference