autocomplete for attribute names, issue #31

This commit is contained in:
azivner 2018-02-04 19:27:27 -05:00
parent bc4aa3e40a
commit a3b31fab54
6 changed files with 47 additions and 12 deletions

View File

@ -3,6 +3,7 @@
const attributesDialog = (function() { const attributesDialog = (function() {
const dialogEl = $("#attributes-dialog"); const dialogEl = $("#attributes-dialog");
const attributesModel = new AttributesModel(); const attributesModel = new AttributesModel();
let attributeNames = [];
function AttributesModel() { function AttributesModel() {
const self = this; const self = this;
@ -17,6 +18,10 @@ const attributesDialog = (function() {
self.attributes(attributes.map(ko.observable)); self.attributes(attributes.map(ko.observable));
addLastEmptyRow(); addLastEmptyRow();
attributeNames = await server.get('attributes/names');
$(".attribute-name:last").focus();
}; };
function isValid() { function isValid() {
@ -54,11 +59,7 @@ const attributesDialog = (function() {
const attrs = self.attributes(); const attrs = self.attributes();
const last = attrs[attrs.length - 1](); const last = attrs[attrs.length - 1]();
// console.log("last", attrs.map(attr => attr()));
if (last.name.trim() !== "" || last.value !== "") { if (last.name.trim() !== "" || last.value !== "") {
console.log("Adding new row");
self.attributes.push(ko.observable({ self.attributes.push(ko.observable({
attributeId: '', attributeId: '',
name: '', name: '',
@ -68,8 +69,6 @@ const attributesDialog = (function() {
} }
this.attributeChanged = function (row) { this.attributeChanged = function (row) {
console.log(row);
addLastEmptyRow(); addLastEmptyRow();
for (const attr of self.attributes()) { for (const attr of self.attributes()) {
@ -124,6 +123,22 @@ const attributesDialog = (function() {
ko.applyBindings(attributesModel, document.getElementById('attributes-dialog')); ko.applyBindings(attributesModel, document.getElementById('attributes-dialog'));
$(document).on('focus', '.attribute-name:not(.ui-autocomplete-input)', function (e) {
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in init.js
source: attributeNames.map(attr => {
return {
label: attr,
value: attr
}
}),
minLength: 0
});
$(this).autocomplete("search", $(this).val());
});
return { return {
showDialog showDialog
}; };

View File

@ -105,7 +105,7 @@ $(window).on('beforeunload', () => {
// Overrides the default autocomplete filter function to search for matched on atleast 1 word in each of the input term's words // Overrides the default autocomplete filter function to search for matched on atleast 1 word in each of the input term's words
$.ui.autocomplete.filter = (array, terms) => { $.ui.autocomplete.filter = (array, terms) => {
if (!terms) { if (!terms) {
return []; return array;
} }
const startDate = new Date(); const startDate = new Date();

View File

@ -7,14 +7,15 @@ const auth = require('../../services/auth');
const sync_table = require('../../services/sync_table'); const sync_table = require('../../services/sync_table');
const utils = require('../../services/utils'); const utils = require('../../services/utils');
const wrap = require('express-promise-wrap').wrap; const wrap = require('express-promise-wrap').wrap;
const attributes = require('../../services/attributes');
router.get('/: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 noteId = ? ORDER BY dateCreated", [noteId])); res.send(await sql.getRows("SELECT * FROM attributes WHERE noteId = ? ORDER BY dateCreated", [noteId]));
})); }));
router.put('/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => {
const noteId = req.params.noteId; const noteId = req.params.noteId;
const attributes = req.body; const attributes = req.body;
const now = utils.nowDate(); const now = utils.nowDate();
@ -45,4 +46,20 @@ router.put('/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next)
res.send(await sql.getRows("SELECT * FROM attributes WHERE noteId = ? ORDER BY dateCreated", [noteId])); res.send(await sql.getRows("SELECT * FROM attributes WHERE noteId = ? ORDER BY dateCreated", [noteId]));
})); }));
router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) => {
const noteId = req.params.noteId;
const names = await sql.getColumn("SELECT DISTINCT name FROM attributes");
for (const attr of attributes.BUILTIN_ATTRIBUTES) {
if (!names.includes(attr)) {
names.push(attr);
}
}
names.sort();
res.send(names);
}));
module.exports = router; module.exports = router;

View File

@ -40,7 +40,7 @@ function register(app) {
app.use('/api/notes', notesApiRoute); app.use('/api/notes', notesApiRoute);
app.use('/api/tree', treeChangesApiRoute); app.use('/api/tree', treeChangesApiRoute);
app.use('/api/notes', cloningApiRoute); app.use('/api/notes', cloningApiRoute);
app.use('/api/notes', attributesRoute); app.use('/api', attributesRoute);
app.use('/api/notes-history', noteHistoryApiRoute); app.use('/api/notes-history', noteHistoryApiRoute);
app.use('/api/recent-changes', recentChangesApiRoute); app.use('/api/recent-changes', recentChangesApiRoute);
app.use('/api/settings', settingsApiRoute); app.use('/api/settings', settingsApiRoute);

View File

@ -5,6 +5,8 @@ const utils = require('./utils');
const sync_table = require('./sync_table'); const sync_table = require('./sync_table');
const Repository = require('./repository'); const Repository = require('./repository');
const BUILTIN_ATTRIBUTES = [ 'run_on_startup', 'disable_versioning' ];
async function getNoteAttributeMap(noteId) { async function getNoteAttributeMap(noteId) {
return await sql.getMap(`SELECT name, value FROM attributes WHERE noteId = ?`, [noteId]); return await sql.getMap(`SELECT name, value FROM attributes WHERE noteId = ?`, [noteId]);
} }
@ -64,5 +66,6 @@ module.exports = {
getNotesWithAttribute, getNotesWithAttribute,
getNoteWithAttribute, getNoteWithAttribute,
getNoteIdsWithAttribute, getNoteIdsWithAttribute,
createAttribute createAttribute,
BUILTIN_ATTRIBUTES
}; };

View File

@ -400,7 +400,7 @@
<tr> <tr>
<td data-bind="text: attributeId"></td> <td data-bind="text: attributeId"></td>
<td> <td>
<input type="text" data-bind="value: name, event: { change: $parent.attributeChanged }"/> <input type="text" class="attribute-name" data-bind="value: name, event: { change: $parent.attributeChanged }"/>
<div style="color: red" data-bind="if: $parent.isNotUnique($index())">Attribute name must be unique per note.</div> <div style="color: red" data-bind="if: $parent.isNotUnique($index())">Attribute name must be unique per note.</div>
<div style="color: red" data-bind="if: $parent.isEmptyName($index())">Attribute name can't be empty.</div> <div style="color: red" data-bind="if: $parent.isEmptyName($index())">Attribute name can't be empty.</div>