added attributes sorting

This commit is contained in:
azivner 2018-02-10 08:37:14 -05:00
parent e011b9ae63
commit c76e4faf5d
5 changed files with 49 additions and 24 deletions

View File

@ -0,0 +1 @@
ALTER TABLE attributes ADD COLUMN position INT NOT NULL DEFAULT 0;

View File

@ -1,8 +1,10 @@
"use strict"; "use strict";
const attributesDialog = (function() { const attributesDialog = (function() {
const dialogEl = $("#attributes-dialog"); const $dialog = $("#attributes-dialog");
const saveAttributesButton = $("#save-attributes-button"); const $saveAttributesButton = $("#save-attributes-button");
const $attributesBody = $('#attributes-table tbody');
const attributesModel = new AttributesModel(); const attributesModel = new AttributesModel();
let attributeNames = []; let attributeNames = [];
@ -24,10 +26,24 @@ const attributesDialog = (function() {
// attribute might not be rendered immediatelly so could not focus // attribute might not be rendered immediatelly so could not focus
setTimeout(() => $(".attribute-name:last").focus(), 100); setTimeout(() => $(".attribute-name:last").focus(), 100);
$attributesBody.sortable({
handle: '.handle',
containment: $attributesBody,
update: function() {
let position = 0;
$attributesBody.find('input[name="position"]').each(function() {
const attr = self.getTargetAttribute(this);
attr().position = position++;
});
}
});
}; };
this.deleteAttribute = function(data, event) { this.deleteAttribute = function(data, event) {
const attr = self.getTargetAttribute(event); const attr = self.getTargetAttribute(event.target);
const attrData = attr(); const attrData = attr();
if (attrData) { if (attrData) {
@ -53,7 +69,7 @@ const attributesDialog = (function() {
// we need to defocus from input (in case of enter-triggered save) because value is updated // we need to defocus from input (in case of enter-triggered save) because value is updated
// on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would // on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
// stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel. // stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
saveAttributesButton.focus(); $saveAttributesButton.focus();
if (!isValid()) { if (!isValid()) {
alert("Please fix all validation errors and try saving again."); alert("Please fix all validation errors and try saving again.");
@ -86,7 +102,8 @@ const attributesDialog = (function() {
attributeId: '', attributeId: '',
name: '', name: '',
value: '', value: '',
isDeleted: 0 isDeleted: 0,
position: 0
})); }));
} }
} }
@ -94,7 +111,7 @@ const attributesDialog = (function() {
this.attributeChanged = function (data, event) { this.attributeChanged = function (data, event) {
addLastEmptyRow(); addLastEmptyRow();
const attr = self.getTargetAttribute(event); const attr = self.getTargetAttribute(event.target);
attr.valueHasMutated(); attr.valueHasMutated();
}; };
@ -123,8 +140,8 @@ const attributesDialog = (function() {
return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== ""); return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== "");
}; };
this.getTargetAttribute = function(event) { this.getTargetAttribute = function(target) {
const context = ko.contextFor(event.target); const context = ko.contextFor(target);
const index = context.$index(); const index = context.$index();
return self.attributes()[index]; return self.attributes()[index];
@ -132,11 +149,11 @@ const attributesDialog = (function() {
} }
async function showDialog() { async function showDialog() {
glob.activeDialog = dialogEl; glob.activeDialog = $dialog;
await attributesModel.loadAttributes(); await attributesModel.loadAttributes();
dialogEl.dialog({ $dialog.dialog({
modal: true, modal: true,
width: 800, width: 800,
height: 500 height: 500

View File

@ -12,7 +12,7 @@ const attributes = require('../../services/attributes');
router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => {
const noteId = req.params.noteId; const noteId = req.params.noteId;
res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY dateCreated", [noteId])); res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId]));
})); }));
router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => {
@ -23,8 +23,8 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res,
await sql.doInTransaction(async () => { await sql.doInTransaction(async () => {
for (const attr of attributes) { for (const attr of attributes) {
if (attr.attributeId) { if (attr.attributeId) {
await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ?, isDeleted = ? WHERE attributeId = ?", await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ?, isDeleted = ?, position = ? WHERE attributeId = ?",
[attr.name, attr.value, now, attr.isDeleted, attr.attributeId]); [attr.name, attr.value, now, attr.isDeleted, attr.position, attr.attributeId]);
} }
else { else {
// if it was "created" and then immediatelly deleted, we just don't create it at all // if it was "created" and then immediatelly deleted, we just don't create it at all
@ -35,13 +35,14 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res,
attr.attributeId = utils.newAttributeId(); attr.attributeId = utils.newAttributeId();
await sql.insert("attributes", { await sql.insert("attributes", {
attributeId: attr.attributeId, attributeId: attr.attributeId,
noteId: noteId, noteId: noteId,
name: attr.name, name: attr.name,
value: attr.value, value: attr.value,
dateCreated: now, position: attr.position,
dateModified: now, dateCreated: now,
isDeleted: false dateModified: now,
isDeleted: false
}); });
} }
@ -49,7 +50,7 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res,
} }
}); });
res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY dateCreated", [noteId])); res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId]));
})); }));
router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) => { router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) => {

View File

@ -3,7 +3,7 @@
const build = require('./build'); const build = require('./build');
const packageJson = require('../../package'); const packageJson = require('../../package');
const APP_DB_VERSION = 73; const APP_DB_VERSION = 74;
module.exports = { module.exports = {
app_version: packageJson.version, app_version: packageJson.version,

View File

@ -383,6 +383,7 @@
<table id="attributes-table" class="table"> <table id="attributes-table" class="table">
<thead> <thead>
<tr> <tr>
<th></th>
<th>ID</th> <th>ID</th>
<th>Name</th> <th>Name</th>
<th>Value</th> <th>Value</th>
@ -390,8 +391,13 @@
</tr> </tr>
</thead> </thead>
<tbody data-bind="foreach: attributes"> <tbody data-bind="foreach: attributes">
<tr data-bind="if: isDeleted == 0"> <tr data-bind="if: isDeleted == 0">
<td data-bind="text: attributeId"></td> <td class="handle">
<span class="glyphicon glyphicon-resize-vertical"></span>
<input type="hidden" name="position" data-bind="value: position"/>
</td>
<!-- ID column has specific width because if it's empty its size can be deformed when dragging -->
<td data-bind="text: attributeId" style="width: 150px;"></td>
<td> <td>
<!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event --> <!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event -->
<input type="text" class="attribute-name" data-bind="value: name, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }"/> <input type="text" class="attribute-name" data-bind="value: name, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }"/>