12479: Avoid saving tags with name and/or value being empty string.
[arvados.git] / apps / workbench / app / assets / javascripts / components / edit_tags.js
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 window.SelectOrAutocomplete = {
6     view: function(vnode) {
7         return m("input", {
8             style: {
9                 width: '100%'
10             },
11             type: 'text',
12             value: vnode.attrs.value
13         }, vnode.attrs.value)
14     },
15     oncreate: function(vnode) {
16         $(vnode.dom).selectize({
17             labelField: 'value',
18             valueField: 'value',
19             searchField: 'value',
20             sortField: 'value',
21             maxItems: 1,
22             placeholder: vnode.attrs.placeholder,
23             create: vnode.attrs.create ? function(input) {
24                 return {value: input}
25             } : false,
26             items: [vnode.attrs.value()],
27             options: vnode.attrs.options.map(function(option) {
28                 return {value: option}
29             }),
30             onChange: function(val) {
31                 if (val != '') {
32                     vnode.attrs.value(val)
33                 }
34             }
35         })
36     }
37 }
38
39 window.TagEditorRow = {
40     view: function(vnode) {
41         // Name options list
42         var nameOpts = Object.keys(vnode.attrs.vocabulary().types)
43         if (vnode.attrs.name() != '' && !(vnode.attrs.name() in vnode.attrs.vocabulary().types)) {
44             nameOpts.push(vnode.attrs.name())
45         }
46         // Value options list
47         var valueOpts = []
48         if (vnode.attrs.name() in vnode.attrs.vocabulary().types &&
49             'options' in vnode.attrs.vocabulary().types[vnode.attrs.name()]) {
50                 valueOpts = vnode.attrs.vocabulary().types[vnode.attrs.name()].options
51         }
52         if (vnode.attrs.value() != '') {
53             valueOpts.push(vnode.attrs.value())
54         }
55         return m("tr", [
56             // Erase tag
57             m("td",
58             vnode.attrs.editMode &&
59                 m('div.text-center', m('a.btn.btn-default.btn-sm', {
60                     style: {
61                         align: 'center'
62                     },
63                     onclick: function(e) { vnode.attrs.removeTag() }
64                 }, m('i.fa.fa-fw.fa-trash-o'))),
65             ),
66             // Tag name
67             m("td",
68             vnode.attrs.editMode ?
69             m("div", {key: 'name-'+vnode.attrs.name()},[m(SelectOrAutocomplete, {
70                 options: nameOpts,
71                 value: vnode.attrs.name,
72                 create: !vnode.attrs.vocabulary().strict,
73                 placeholder: 'new tag',
74             })])
75             : vnode.attrs.name),
76             // Tag value
77             m("td",
78             vnode.attrs.editMode ?
79             m("div", {key: 'value-'+vnode.attrs.name()}, [m(SelectOrAutocomplete, {
80                 options: valueOpts,
81                 value: vnode.attrs.value,
82                 placeholder: 'new value',
83                 create: (vnode.attrs.name() in vnode.attrs.vocabulary().types)
84                     ? (vnode.attrs.vocabulary().types[vnode.attrs.name()].type == 'text') || 
85                         vnode.attrs.vocabulary().types[vnode.attrs.name()].overridable || false
86                     : true, // If tag not in vocabulary, we should accept any value
87                 })
88             ])
89             : vnode.attrs.value)
90         ])
91     }
92 }
93
94 window.TagEditorTable = {
95     view: function(vnode) {
96         return m("table.table.table-condensed", {
97             border: "1"
98         }, [
99             m("colgroup", [
100                 m("col", {width:"5%"}),
101                 m("col", {width:"25%"}),
102                 m("col", {width:"70%"}),
103             ]),
104             m("thead", [
105                 m("tr", [
106                     m("th"),
107                     m("th", "Key"),
108                     m("th", "Value"),
109                 ])
110             ]),
111             m("tbody", [
112                 vnode.attrs.tags.length > 0
113                 ? vnode.attrs.tags.map(function(tag, idx) {
114                     return m(TagEditorRow, {
115                         key: idx,
116                         removeTag: function() {
117                             vnode.attrs.tags.splice(idx, 1)
118                             vnode.attrs.dirty(true)
119                         },
120                         editMode: vnode.attrs.editMode,
121                         name: tag.name,
122                         value: tag.value,
123                         vocabulary: vnode.attrs.vocabulary
124                     })
125                 })
126                 : m("tr", m("td[colspan=3]", m("center","(no tags)")))
127             ]),
128         ])
129     }
130 }
131
132 window.TagEditorApp = {
133     appendTag: function(vnode, name, value) {
134         var tag = {name: m.stream(name), value: m.stream(value)}
135         tag.name.map(vnode.state.dirty)
136         tag.value.map(vnode.state.dirty)
137         tag.name.map(m.redraw)
138         vnode.state.tags.push(tag)
139     },
140     oninit: function(vnode) {
141         vnode.state.sessionDB = new SessionDB()
142         // Get vocabulary
143         vnode.state.vocabulary = m.stream({"strict":false, "types":{}})
144         m.request('/vocabulary.json').then(vnode.state.vocabulary)
145         vnode.state.editMode = vnode.attrs.targetEditable
146         vnode.state.tags = []
147         vnode.state.dirty = m.stream(false)
148         vnode.state.dirty.map(m.redraw)
149         vnode.state.objPath = '/arvados/v1/'+vnode.attrs.targetController+'/'+vnode.attrs.targetUuid
150         // Get tags
151         vnode.state.sessionDB.request(
152             vnode.state.sessionDB.loadLocal(),
153             '/arvados/v1/'+vnode.attrs.targetController,
154             {
155                 data: {
156                     filters: JSON.stringify([['uuid', '=', vnode.attrs.targetUuid]]),
157                     select: JSON.stringify(['properties'])
158                 },
159             }).then(function(obj) {
160                 if (obj.items.length == 1) {
161                     o = obj.items[0]
162                     Object.keys(o.properties).forEach(function(k) {
163                         vnode.state.appendTag(vnode, k, o.properties[k])
164                     })
165                     // Data synced with server, so dirty state should be false
166                     vnode.state.dirty(false)
167                 }
168             }
169         )
170     },
171     view: function(vnode) {
172         return [
173             vnode.state.editMode &&
174             m("div.pull-left", [
175                 m("a.btn.btn-primary.btn-sm"+(!(vnode.state.dirty() === false) ? '' : '.disabled'), {
176                     style: {
177                         margin: '10px 0px'
178                     },
179                     onclick: function(e) {
180                         var tags = {}
181                         vnode.state.tags.forEach(function(t) {
182                             if (t.name() != '' && t.value() != '') {
183                                 tags[t.name()] = t.value()
184                             }
185                         })
186                         vnode.state.sessionDB.request(
187                             vnode.state.sessionDB.loadLocal(),
188                             vnode.state.objPath, {
189                                 method: "PUT",
190                                 data: {properties: JSON.stringify(tags)}
191                             }
192                         ).then(function(v) {
193                             vnode.state.dirty(false)
194                         })
195                     }
196                 }, !(vnode.state.dirty() === false) ? ' Save changes ' : ' Saved ')
197             ]),
198             // Tags table
199             m(TagEditorTable, {
200                 editMode: vnode.state.editMode,
201                 tags: vnode.state.tags,
202                 vocabulary: vnode.state.vocabulary,
203                 dirty: vnode.state.dirty
204             }),
205             vnode.state.editMode &&
206             m("div.pull-left", [
207                 // Add tag button
208                 m("a.btn.btn-primary.btn-sm", {
209                     onclick: function(e) {
210                         vnode.state.appendTag(vnode, '', '')
211                     }
212                 }, [
213                     m("i.glyphicon.glyphicon-plus"),
214                     " Add new tag "
215                 ])
216             ])
217         ]
218     },
219 }