From a3b31fab54ac397ebeded252536b0d1b951b86bc Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 4 Feb 2018 19:27:27 -0500 Subject: [PATCH] autocomplete for attribute names, issue #31 --- src/public/javascripts/dialogs/attributes.js | 27 +++++++++++++++----- src/public/javascripts/init.js | 2 +- src/routes/api/attributes.js | 21 +++++++++++++-- src/routes/routes.js | 2 +- src/services/attributes.js | 5 +++- src/views/index.ejs | 2 +- 6 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/public/javascripts/dialogs/attributes.js b/src/public/javascripts/dialogs/attributes.js index 76023b970..d09ff00d4 100644 --- a/src/public/javascripts/dialogs/attributes.js +++ b/src/public/javascripts/dialogs/attributes.js @@ -3,6 +3,7 @@ const attributesDialog = (function() { const dialogEl = $("#attributes-dialog"); const attributesModel = new AttributesModel(); + let attributeNames = []; function AttributesModel() { const self = this; @@ -17,6 +18,10 @@ const attributesDialog = (function() { self.attributes(attributes.map(ko.observable)); addLastEmptyRow(); + + attributeNames = await server.get('attributes/names'); + + $(".attribute-name:last").focus(); }; function isValid() { @@ -54,11 +59,7 @@ const attributesDialog = (function() { const attrs = self.attributes(); const last = attrs[attrs.length - 1](); -// console.log("last", attrs.map(attr => attr())); - if (last.name.trim() !== "" || last.value !== "") { - console.log("Adding new row"); - self.attributes.push(ko.observable({ attributeId: '', name: '', @@ -68,8 +69,6 @@ const attributesDialog = (function() { } this.attributeChanged = function (row) { - console.log(row); - addLastEmptyRow(); for (const attr of self.attributes()) { @@ -124,6 +123,22 @@ const attributesDialog = (function() { 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 { showDialog }; diff --git a/src/public/javascripts/init.js b/src/public/javascripts/init.js index 138d23f62..5eb906804 100644 --- a/src/public/javascripts/init.js +++ b/src/public/javascripts/init.js @@ -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 $.ui.autocomplete.filter = (array, terms) => { if (!terms) { - return []; + return array; } const startDate = new Date(); diff --git a/src/routes/api/attributes.js b/src/routes/api/attributes.js index f7ecdfb06..80ea5683a 100644 --- a/src/routes/api/attributes.js +++ b/src/routes/api/attributes.js @@ -7,14 +7,15 @@ const auth = require('../../services/auth'); const sync_table = require('../../services/sync_table'); const utils = require('../../services/utils'); 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; 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 attributes = req.body; 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])); })); +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; \ No newline at end of file diff --git a/src/routes/routes.js b/src/routes/routes.js index 326d32158..4c3458659 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -40,7 +40,7 @@ function register(app) { app.use('/api/notes', notesApiRoute); app.use('/api/tree', treeChangesApiRoute); app.use('/api/notes', cloningApiRoute); - app.use('/api/notes', attributesRoute); + app.use('/api', attributesRoute); app.use('/api/notes-history', noteHistoryApiRoute); app.use('/api/recent-changes', recentChangesApiRoute); app.use('/api/settings', settingsApiRoute); diff --git a/src/services/attributes.js b/src/services/attributes.js index e365ecd04..3b680b831 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -5,6 +5,8 @@ const utils = require('./utils'); const sync_table = require('./sync_table'); const Repository = require('./repository'); +const BUILTIN_ATTRIBUTES = [ 'run_on_startup', 'disable_versioning' ]; + async function getNoteAttributeMap(noteId) { return await sql.getMap(`SELECT name, value FROM attributes WHERE noteId = ?`, [noteId]); } @@ -64,5 +66,6 @@ module.exports = { getNotesWithAttribute, getNoteWithAttribute, getNoteIdsWithAttribute, - createAttribute + createAttribute, + BUILTIN_ATTRIBUTES }; \ No newline at end of file diff --git a/src/views/index.ejs b/src/views/index.ejs index aaf57880f..89666db05 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -400,7 +400,7 @@ - +
Attribute name must be unique per note.
Attribute name can't be empty.