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 the Form
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.
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.
(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);
returnthis.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) {
returnfunction(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.
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.
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.