X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/4fed183482e37ad80e97e841d2f0b825ef0d2570..7ed41dc0191d366c914850a350c3b30f769365af:/apps/workbench/app/assets/javascripts/components/edit_tags.js diff --git a/apps/workbench/app/assets/javascripts/components/edit_tags.js b/apps/workbench/app/assets/javascripts/components/edit_tags.js index 939692acfe..fa3f13e4ac 100644 --- a/apps/workbench/app/assets/javascripts/components/edit_tags.js +++ b/apps/workbench/app/assets/javascripts/components/edit_tags.js @@ -2,7 +2,41 @@ // // SPDX-License-Identifier: AGPL-3.0 +window.SimpleInput = { + view: function(vnode) { + return m("input.form-control", { + style: { + width: '100%', + }, + type: 'text', + placeholder: vnode.attrs.placeholder, + value: vnode.attrs.value, + onchange: function() { + console.log(this.value) + if (this.value != '') { + vnode.attrs.value(this.value) + } + }, + }, vnode.attrs.value) + }, + oncreate: function(vnode) { + if (vnode.attrs.setFocus) { + vnode.dom.focus() + } + } +} + window.SelectOrAutocomplete = { + onFocus: function(vnode) { + // Allow the user to edit an already entered value by removing it + // and filling the input field with the same text + activeSelect = vnode.state.selectized[0].selectize + value = activeSelect.getValue() + if (value.length > 0) { + activeSelect.clear(silent = true) + activeSelect.setTextboxValue(value) + } + }, view: function(vnode) { return m("input", { style: { @@ -13,12 +47,17 @@ window.SelectOrAutocomplete = { }, vnode.attrs.value) }, oncreate: function(vnode) { - vnode.state.selector = $(vnode.dom).selectize({ + vnode.state.selectized = $(vnode.dom).selectize({ labelField: 'value', valueField: 'value', searchField: 'value', sortField: 'value', + persist: false, + hideSelected: true, + openOnFocus: false, + createOnBlur: true, maxItems: 1, + placeholder: vnode.attrs.placeholder, create: vnode.attrs.create ? function(input) { return {value: input} } : false, @@ -27,69 +66,98 @@ window.SelectOrAutocomplete = { return {value: option} }), onChange: function(val) { - vnode.attrs.value(val) - m.redraw() + if (val != '') { + vnode.attrs.value(val) + } + }, + onFocus: function() { + vnode.state.onFocus(vnode) } - }).data('selectize') + }) + if (vnode.attrs.setFocus) { + vnode.state.selectized[0].selectize.focus() + } } } -// When in edit mode, present a tag name selector and tag value -// selector/editor depending of the tag type. window.TagEditorRow = { view: function(vnode) { - // Value options list - valueOpts = [] - if (vnode.attrs.name() in vnode.attrs.vocabulary().types && - 'options' in vnode.attrs.vocabulary().types[vnode.attrs.name()]) { - valueOpts = vnode.attrs.vocabulary().types[vnode.attrs.name()].options + var nameOpts = Object.keys(vnode.attrs.vocabulary().tags) + var valueOpts = [] + var inputComponent = SelectOrAutocomplete + if (nameOpts.length === 0) { + // If there's not vocabulary defined, switch to a simple input field + inputComponent = SimpleInput + } else { + // Name options list + if (vnode.attrs.name() != '' && !(vnode.attrs.name() in vnode.attrs.vocabulary().tags)) { + nameOpts.push(vnode.attrs.name()) + } + // Value options list + if (vnode.attrs.name() in vnode.attrs.vocabulary().tags && + 'values' in vnode.attrs.vocabulary().tags[vnode.attrs.name()]) { + valueOpts = vnode.attrs.vocabulary().tags[vnode.attrs.name()].values + } + if (vnode.attrs.value() != '') { + valueOpts.push(vnode.attrs.value()) + } } - valueOpts.push(vnode.attrs.value()) - return m("tr", [ // Erase tag - m("td", - vnode.attrs.editMode && + m("td", [ + vnode.attrs.editMode && m('div.text-center', m('a.btn.btn-default.btn-sm', { style: { align: 'center' }, onclick: function(e) { vnode.attrs.removeTag() } - }, m('i.fa.fa-fw.fa-trash-o'))), - ), + }, m('i.fa.fa-fw.fa-trash-o'))) + ]), // Tag name - m("td", - vnode.attrs.editMode ? - m("div", {key: 'name-'+vnode.attrs.name()},[m(SelectOrAutocomplete, { - options: (vnode.attrs.name() in vnode.attrs.vocabulary().types) - ? Object.keys(vnode.attrs.vocabulary().types) - : Object.keys(vnode.attrs.vocabulary().types).concat(vnode.attrs.name()), - value: vnode.attrs.name, - create: vnode.attrs.vocabulary().strict - })]) - : vnode.attrs.name), + m("td", [ + vnode.attrs.editMode ? + m("div", {key: 'name-'+vnode.attrs.name()},[ + m(inputComponent, { + options: nameOpts, + value: vnode.attrs.name, + // Allow any tag name unless "strict" is set to true. + create: !vnode.attrs.vocabulary().strict, + placeholder: 'new tag', + // Focus on tag name field when adding a new tag that's + // not the first one. + setFocus: vnode.attrs.name() === '' + }) + ]) + : vnode.attrs.name + ]), // Tag value - m("td", - vnode.attrs.editMode ? - m("div", {key: 'value-'+vnode.attrs.name()}, [m(SelectOrAutocomplete, { - options: valueOpts, - value: vnode.attrs.value, - create: (vnode.attrs.name() in vnode.attrs.vocabulary().types) - ? (vnode.attrs.vocabulary().types[vnode.attrs.name()].type == 'text') || - vnode.attrs.vocabulary().types[vnode.attrs.name()].overridable || false - : true, // If tag not in vocabulary, we should accept any value - }) + m("td", [ + vnode.attrs.editMode ? + m("div", {key: 'value-'+vnode.attrs.name()}, [ + m(inputComponent, { + options: valueOpts, + value: vnode.attrs.value, + placeholder: 'new value', + // Allow any value on tags not listed on the vocabulary. + // Allow any value on tags without values, or the ones + // that aren't explicitly declared to be strict. + create: !(vnode.attrs.name() in vnode.attrs.vocabulary().tags) + || !vnode.attrs.vocabulary().tags[vnode.attrs.name()].values + || vnode.attrs.vocabulary().tags[vnode.attrs.name()].values.length === 0 + || !vnode.attrs.vocabulary().tags[vnode.attrs.name()].strict, + // Focus on tag value field when new tag name is set + setFocus: vnode.attrs.name() !== '' && vnode.attrs.value() === '' + }) + ]) + : vnode.attrs.value ]) - : vnode.attrs.value) ]) } } window.TagEditorTable = { view: function(vnode) { - return m("table.table.table-condensed", { - border: "1" - }, [ + return m("table.table.table-condensed.table-justforlayout", [ m("colgroup", [ m("col", {width:"5%"}), m("col", {width:"25%"}), @@ -103,101 +171,116 @@ window.TagEditorTable = { ]) ]), m("tbody", [ - vnode.attrs.tags.map(function(tag, idx) { + vnode.attrs.tags.length > 0 + ? vnode.attrs.tags.map(function(tag, idx) { return m(TagEditorRow, { key: idx, - removeTag: function() { vnode.attrs.tags.splice(idx, 1) }, + removeTag: function() { + vnode.attrs.tags.splice(idx, 1) + vnode.attrs.dirty(true) + }, editMode: vnode.attrs.editMode, name: tag.name, value: tag.value, vocabulary: vnode.attrs.vocabulary }) }) + : m("tr", m("td[colspan=3]", m("center","loading tags..."))) ]), ]) } } window.TagEditorApp = { + appendTag: function(vnode, name, value) { + var tag = {name: m.stream(name), value: m.stream(value)} + vnode.state.tags.push(tag) + // Set dirty flag when any of name/value changes to non empty string + tag.name.map(function(v) { + if (v !== '') { + vnode.state.dirty(true) + } + }) + tag.value.map(function(v) { + if (v !== '') { + vnode.state.dirty(true) + } + }) + tag.name.map(m.redraw) + }, oninit: function(vnode) { - vnode.state.saveLabel = m.stream(' Save ') vnode.state.sessionDB = new SessionDB() // Get vocabulary - vnode.state.vocabulary = m.stream({"strict":false, "types":{}}) + vnode.state.vocabulary = m.stream({"strict":false, "tags":{}}) m.request('/vocabulary.json').then(vnode.state.vocabulary) vnode.state.editMode = vnode.attrs.targetEditable - // Get tags vnode.state.tags = [] + vnode.state.dirty = m.stream(false) + vnode.state.dirty.map(m.redraw) vnode.state.objPath = '/arvados/v1/'+vnode.attrs.targetController+'/'+vnode.attrs.targetUuid + // Get tags vnode.state.sessionDB.request( - vnode.state.sessionDB.loadLocal(), vnode.state.objPath, { + vnode.state.sessionDB.loadLocal(), + '/arvados/v1/'+vnode.attrs.targetController, + { data: { - select: JSON.stringify(['properties']) // FIXME: not working + filters: JSON.stringify([['uuid', '=', vnode.attrs.targetUuid]]), + select: JSON.stringify(['properties']) }, }).then(function(obj) { - console.log(obj) - Object.keys(obj.properties).forEach(function(k) { - vnode.state.tags.push({ - name: m.stream(k), - value: m.stream(obj.properties[k]) + if (obj.items.length == 1) { + o = obj.items[0] + Object.keys(o.properties).forEach(function(k) { + vnode.state.appendTag(vnode, k, o.properties[k]) }) - }) - vnode.state.dirty = m.stream(null) - vnode.state.tags.map(function(tag) { - tag.name.map(m.redraw) - tag.name.map(vnode.state.dirty) - tag.value.map(vnode.state.dirty) - }) + // Data synced with server, so dirty state should be false + vnode.state.dirty(false) + // Add new tag row when the last one is completed + vnode.state.dirty.map(function() { + if (!vnode.state.editMode) { return } + lastTag = vnode.state.tags.slice(-1).pop() + if (lastTag === undefined || (lastTag.name() !== '' && lastTag.value() !== '')) { + vnode.state.appendTag(vnode, '', '') + } + }) + } } ) }, view: function(vnode) { return [ + vnode.state.editMode && + m("div.pull-left", [ + m("a.btn.btn-primary.btn-sm"+(vnode.state.dirty() ? '' : '.disabled'), { + style: { + margin: '10px 0px' + }, + onclick: function(e) { + var tags = {} + vnode.state.tags.forEach(function(t) { + if (t.name() != '' && t.value() != '') { + tags[t.name()] = t.value() + } + }) + vnode.state.sessionDB.request( + vnode.state.sessionDB.loadLocal(), + vnode.state.objPath, { + method: "PUT", + data: {properties: JSON.stringify(tags)} + } + ).then(function(v) { + vnode.state.dirty(false) + }) + } + }, vnode.state.dirty() ? ' Save changes ' : ' Saved ') + ]), // Tags table m(TagEditorTable, { editMode: vnode.state.editMode, tags: vnode.state.tags, - vocabulary: vnode.state.vocabulary - }), - vnode.state.editMode && - m("div", [ - m("div.pull-left", [ - // Add tag button - m("a.btn.btn-primary.btn-sm", { - onclick: function(e) { - vnode.state.tags.push({ - name: m.stream('new tag'), - value: m.stream('new tag value') - }) - } - }, [ - m("i.glyphicon.glyphicon-plus"), - " Add new tag " - ]) - ]), - m("div.pull-right", [ - // Save button - m("a.btn.btn-primary.btn-sm", { - onclick: function(e) { - vnode.state.saveLabel('Saving...') - var tags = {} - vnode.state.tags.forEach(function(t) { - tags[t.name()] = t.value() - }) - vnode.state.sessionDB.request( - vnode.state.sessionDB.loadLocal(), - vnode.state.objPath, { - method: "PUT", - data: {properties: JSON.stringify(tags)} - } - ).then(function(v) { - vnode.state.saveLabel(' Save ') - console.log('ok!') - }) - } - }, vnode.state.saveLabel) - ]) - ]) + vocabulary: vnode.state.vocabulary, + dirty: vnode.state.dirty + }) ] }, } \ No newline at end of file