-
-
Notifications
You must be signed in to change notification settings - Fork 2k
/
showTransformModal.js
296 lines (262 loc) · 11.3 KB
/
showTransformModal.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import picoModal from 'picomodal'
import Selectr from './assets/selectr/selectr'
import { translate } from './i18n'
import { stringifyPartial } from './jsonUtils'
import { getChildPaths, debounce } from './util'
import { MAX_PREVIEW_CHARACTERS } from './constants'
const DEFAULT_DESCRIPTION =
'Enter a <a href="http://jmespath.org" target="_blank">JMESPath</a> query to filter, sort, or transform the JSON data.<br/>' +
'To learn JMESPath, go to <a href="http://jmespath.org/tutorial.html" target="_blank">the interactive tutorial</a>.'
/**
* Show advanced filter and transform modal using JMESPath
* @param {Object} params
* @property {HTMLElement} container The container where to center
* the modal and create an overlay
* @property {JSON} json The json data to be transformed
* @property {string} [queryDescription] Optional custom description explaining
* the transform functionality
* @property {function} createQuery Function called to create a query
* from the wizard form
* @property {function} executeQuery Execute a query for the preview pane
* @property {function} onTransform Callback invoked with the created
* query as callback
*/
export function showTransformModal (
{
container,
json,
queryDescription = DEFAULT_DESCRIPTION,
createQuery,
executeQuery,
onTransform
}
) {
const value = json
const content = '<div class="pico-modal-contents">' +
'<div class="pico-modal-header">' + translate('transform') + '</div>' +
'<p>' + queryDescription + '</p>' +
'<div class="jsoneditor-jmespath-label">' + translate('transformWizardLabel') + ' </div>' +
'<div id="wizard" class="jsoneditor-jmespath-block jsoneditor-jmespath-wizard">' +
' <table class="jsoneditor-jmespath-wizard-table">' +
' <tbody>' +
' <tr>' +
' <th>' + translate('transformWizardFilter') + '</th>' +
' <td class="jsoneditor-jmespath-filter">' +
' <div class="jsoneditor-inline jsoneditor-jmespath-filter-field" >' +
' <select id="filterField">' +
' </select>' +
' </div>' +
' <div class="jsoneditor-inline jsoneditor-jmespath-filter-relation" >' +
' <select id="filterRelation">' +
' <option value="==">==</option>' +
' <option value="!=">!=</option>' +
' <option value="<"><</option>' +
' <option value="<="><=</option>' +
' <option value=">">></option>' +
' <option value=">=">>=</option>' +
' </select>' +
' </div>' +
' <div class="jsoneditor-inline jsoneditor-jmespath-filter-value" >' +
' <input type="text" class="value" placeholder="value..." id="filterValue" />' +
' </div>' +
' </td>' +
' </tr>' +
' <tr>' +
' <th>' + translate('transformWizardSortBy') + '</th>' +
' <td class="jsoneditor-jmespath-filter">' +
' <div class="jsoneditor-inline jsoneditor-jmespath-sort-field">' +
' <select id="sortField">' +
' </select>' +
' </div>' +
' <div class="jsoneditor-inline jsoneditor-jmespath-sort-order" >' +
' <select id="sortOrder">' +
' <option value="asc">Ascending</option>' +
' <option value="desc">Descending</option>' +
' </select>' +
' </div>' +
' </td>' +
' </tr>' +
' <tr id="selectFieldsPart">' +
' <th>' + translate('transformWizardSelectFields') + '</th>' +
' <td class="jsoneditor-jmespath-filter">' +
' <select class="jsoneditor-jmespath-select-fields" id="selectFields" multiple></select>' +
' </td>' +
' </tr>' +
' </tbody>' +
' </table>' +
'</div>' +
'<div class="jsoneditor-jmespath-label">' + translate('transformQueryLabel') + ' </div>' +
'<div class="jsoneditor-jmespath-block">' +
' <textarea id="query" ' +
' rows="4" ' +
' autocomplete="off" ' +
' autocorrect="off" ' +
' autocapitalize="off" ' +
' spellcheck="false"' +
' title="' + translate('transformQueryTitle') + '">[*]</textarea>' +
'</div>' +
'<div class="jsoneditor-jmespath-label">' + translate('transformPreviewLabel') + ' </div>' +
'<div class="jsoneditor-jmespath-block">' +
' <textarea id="preview" ' +
' class="jsoneditor-transform-preview"' +
' readonly> </textarea>' +
'</div>' +
'<div class="jsoneditor-jmespath-block jsoneditor-modal-actions">' +
' <input type="submit" id="ok" value="' + translate('ok') + '" autofocus />' +
'</div>' +
'</div>'
picoModal({
parent: container,
content,
overlayClass: 'jsoneditor-modal-overlay',
overlayStyles: {
backgroundColor: 'rgb(1,1,1)',
opacity: 0.3
},
modalClass: 'jsoneditor-modal jsoneditor-modal-transform',
focus: false
})
.afterCreate(modal => {
const elem = modal.modalElem()
const wizard = elem.querySelector('#wizard')
const ok = elem.querySelector('#ok')
const filterField = elem.querySelector('#filterField')
const filterRelation = elem.querySelector('#filterRelation')
const filterValue = elem.querySelector('#filterValue')
const sortField = elem.querySelector('#sortField')
const sortOrder = elem.querySelector('#sortOrder')
const selectFields = elem.querySelector('#selectFields')
const query = elem.querySelector('#query')
const preview = elem.querySelector('#preview')
if (!Array.isArray(value)) {
wizard.style.fontStyle = 'italic'
wizard.textContent = '(wizard not available for objects, only for arrays)'
}
const sortablePaths = getChildPaths(json)
sortablePaths.forEach(path => {
const formattedPath = preprocessPath(path)
const filterOption = document.createElement('option')
filterOption.text = formattedPath
filterOption.value = formattedPath
filterField.appendChild(filterOption)
const sortOption = document.createElement('option')
sortOption.text = formattedPath
sortOption.value = formattedPath
sortField.appendChild(sortOption)
})
const selectablePaths = getChildPaths(json, true).filter(path => path !== '')
if (selectablePaths.length > 0) {
selectablePaths.forEach(path => {
const formattedPath = preprocessPath(path)
const option = document.createElement('option')
option.text = formattedPath
option.value = formattedPath
selectFields.appendChild(option)
})
} else {
const selectFieldsPart = elem.querySelector('#selectFieldsPart')
if (selectFieldsPart) {
selectFieldsPart.style.display = 'none'
}
}
const selectrFilterField = new Selectr(filterField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' })
const selectrFilterRelation = new Selectr(filterRelation, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'compare...' })
const selectrSortField = new Selectr(sortField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' })
const selectrSortOrder = new Selectr(sortOrder, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'order...' })
const selectrSelectFields = new Selectr(selectFields, { multiple: true, clearable: true, defaultSelected: false, placeholder: 'select fields...' })
selectrFilterField.on('selectr.change', generateQueryFromWizard)
selectrFilterRelation.on('selectr.change', generateQueryFromWizard)
filterValue.oninput = generateQueryFromWizard
selectrSortField.on('selectr.change', generateQueryFromWizard)
selectrSortOrder.on('selectr.change', generateQueryFromWizard)
selectrSelectFields.on('selectr.change', generateQueryFromWizard)
elem.querySelector('.pico-modal-contents').onclick = event => {
// prevent the first clear button (in any select box) from getting
// focus when clicking anywhere in the modal. Only allow clicking links.
if (event.target.nodeName !== 'A') {
event.preventDefault()
}
}
function preprocessPath (path) {
return (path === '')
? '@'
: (path[0] === '.')
? path.slice(1)
: path
}
function updatePreview () {
try {
const transformed = executeQuery(value, query.value)
preview.className = 'jsoneditor-transform-preview'
preview.value = stringifyPartial(transformed, 2, MAX_PREVIEW_CHARACTERS)
ok.disabled = false
} catch (err) {
preview.className = 'jsoneditor-transform-preview jsoneditor-error'
preview.value = err.toString()
ok.disabled = true
}
}
const debouncedUpdatePreview = debounce(updatePreview, 300)
function tryCreateQuery (json, queryOptions) {
try {
query.value = createQuery(json, queryOptions)
ok.disabled = false
debouncedUpdatePreview()
} catch (err) {
const message = 'Error: an error happened when executing "createQuery": ' + (err.message || err.toString())
query.value = ''
ok.disabled = true
preview.className = 'jsoneditor-transform-preview jsoneditor-error'
preview.value = message
}
}
function generateQueryFromWizard () {
const queryOptions = {}
if (filterField.value && filterRelation.value && filterValue.value) {
queryOptions.filter = {
field: filterField.value,
relation: filterRelation.value,
value: filterValue.value
}
}
if (sortField.value && sortOrder.value) {
queryOptions.sort = {
field: sortField.value,
direction: sortOrder.value
}
}
if (selectFields.value) {
const fields = []
for (let i = 0; i < selectFields.options.length; i++) {
if (selectFields.options[i].selected) {
const selectedField = selectFields.options[i].value
fields.push(selectedField)
}
}
queryOptions.projection = {
fields
}
}
tryCreateQuery(json, queryOptions)
}
query.oninput = debouncedUpdatePreview
ok.onclick = event => {
event.preventDefault()
event.stopPropagation()
modal.close()
onTransform(query.value)
}
// initialize with empty query
tryCreateQuery(json, {})
setTimeout(() => {
query.select()
query.focus()
query.selectionStart = 3
query.selectionEnd = 3
})
})
.afterClose(modal => {
modal.destroy()
})
.show()
}