@jinntec/fore 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fore-dev.js +2 -2
- package/dist/fore-dev.js.map +1 -1
- package/dist/fore.js +2 -2
- package/dist/fore.js.map +1 -1
- package/index.js +3 -0
- package/package.json +1 -1
- package/src/actions/abstract-action.js +100 -66
- package/src/actions/fx-confirm.js +3 -3
- package/src/actions/fx-construct-done.js +28 -0
- package/src/actions/fx-insert.js +53 -51
- package/src/actions/fx-setattribute.js +68 -0
- package/src/fore.js +3 -1
- package/src/fx-bind.js +1 -0
- package/src/fx-fore.js +48 -1
- package/src/fx-model.js +6 -3
- package/src/fx-submission.js +24 -4
- package/src/fx-var.js +7 -2
- package/src/getInScopeContext.js +11 -1
- package/src/json-util.js +27 -0
- package/src/relevance.js +18 -0
- package/src/ui/abstract-control.js +2 -0
- package/src/ui/fx-droptarget.js +9 -0
- package/src/ui/fx-repeat-attributes.js +4 -2
- package/src/ui/fx-repeat.js +366 -356
- package/src/ui/fx-repeatitem.js +78 -86
- package/src/ui/fx-trigger.js +3 -2
- package/src/withDraggability.js +218 -0
- package/src/xpath-evaluation.js +140 -1
package/src/ui/fx-repeatitem.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import {Fore} from '../fore.js';
|
|
2
|
+
import {foreElementMixin} from '../ForeElementMixin.js';
|
|
3
|
+
import {FxFore} from "../fx-fore.js";
|
|
4
|
+
import {withDraggability} from "../withDraggability.js";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* `fx-repeat`
|
|
@@ -8,105 +10,95 @@ import { foreElementMixin } from '../ForeElementMixin.js';
|
|
|
8
10
|
* @customElement
|
|
9
11
|
* @demo demo/index.html
|
|
10
12
|
*/
|
|
11
|
-
export class FxRepeatitem extends foreElementMixin(HTMLElement) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
constructor() {
|
|
21
|
-
super();
|
|
22
|
-
this.inited = false;
|
|
23
|
-
|
|
24
|
-
this.addEventListener('click', this._dispatchIndexChange);
|
|
25
|
-
// this.addEventListener('focusin', this._handleFocus);
|
|
26
|
-
this.addEventListener('focusin', this._dispatchIndexChange);
|
|
27
|
-
|
|
28
|
-
this.attachShadow({ mode: 'open', delegatesFocus: true });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
_handleFocus() {
|
|
32
|
-
this.parentNode.setIndex(this.index);
|
|
33
|
-
// TODO: do this somewhere else, somewhere more central
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* todo: resolve - this is problematic as it triggers a lot of unneeded refreshes but it needed
|
|
37
|
-
* when you want to support activating the right repeatitem when the user tabs through controls.
|
|
38
|
-
*/
|
|
39
|
-
// this.closest('fx-fore').refresh();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
_dispatchIndexChange() {
|
|
43
|
-
// console.log('_dispatchIndexChange on index ', this.index);
|
|
44
|
-
if (this.parentNode) {
|
|
45
|
-
this.parentNode.dispatchEvent(
|
|
46
|
-
new CustomEvent('item-changed', { composed: false, bubbles: true, detail: { item: this , index:this.index } }),
|
|
47
|
-
);
|
|
13
|
+
export class FxRepeatitem extends withDraggability(foreElementMixin(HTMLElement), true) {
|
|
14
|
+
static get properties() {
|
|
15
|
+
return {
|
|
16
|
+
inited: {
|
|
17
|
+
type: Boolean,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
48
20
|
}
|
|
49
|
-
}
|
|
50
21
|
|
|
51
|
-
|
|
52
|
-
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this.inited = false;
|
|
53
25
|
|
|
54
|
-
|
|
26
|
+
this.addEventListener('click', this._dispatchIndexChange);
|
|
27
|
+
// this.addEventListener('focusin', this._handleFocus);
|
|
28
|
+
this.addEventListener('focusin', this._dispatchIndexChange);
|
|
29
|
+
|
|
30
|
+
this.attachShadow({mode: 'open', delegatesFocus: true});
|
|
31
|
+
|
|
32
|
+
this.dropTarget = null;
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
connectedCallback() {
|
|
37
|
+
super.connectedCallback();
|
|
38
|
+
this.display = this.style.display;
|
|
39
|
+
|
|
40
|
+
const html = `
|
|
55
41
|
<slot></slot>
|
|
56
42
|
`;
|
|
57
43
|
|
|
58
|
-
|
|
44
|
+
this.shadowRoot.innerHTML = `
|
|
59
45
|
${html}
|
|
60
46
|
`;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// console.log('repeatitem init model ', this.nodeset);
|
|
74
|
-
// this._initializeChildren(this);
|
|
75
|
-
this.inited = true;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/*
|
|
79
|
-
getModelItem() {
|
|
80
|
-
super.getModelItem();
|
|
81
|
-
// console.log('modelItem in repeatitem ', this.getModelItem()[this.index]);
|
|
82
|
-
return this.getModelItem()[this.index];
|
|
83
|
-
}
|
|
84
|
-
*/
|
|
85
|
-
|
|
86
|
-
refresh(force) {
|
|
87
|
-
this.modelItem = this.getModelItem();
|
|
88
|
-
// ### register ourselves as boundControl
|
|
89
|
-
if (!this.modelItem.boundControls.includes(this)) {
|
|
90
|
-
this.modelItem.boundControls.push(this);
|
|
47
|
+
this.getOwnerForm().registerLazyElement(this);
|
|
48
|
+
|
|
49
|
+
this.ref = `${this.parentNode.ref}`;
|
|
50
|
+
|
|
51
|
+
this.tabindex = 0;
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
disconnectedCallback() {
|
|
56
|
+
super.disconnectedCallback();
|
|
57
|
+
this.removeEventListener('click', this._dispatchIndexChange);
|
|
58
|
+
this.removeEventListener('focusin', this._handleFocus);
|
|
91
59
|
}
|
|
92
60
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
61
|
+
|
|
62
|
+
init() {
|
|
63
|
+
// console.log('repeatitem init model ', this.nodeset);
|
|
64
|
+
// this._initializeChildren(this);
|
|
65
|
+
this.inited = true;
|
|
97
66
|
}
|
|
98
67
|
|
|
99
68
|
/*
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
69
|
+
getModelItem() {
|
|
70
|
+
super.getModelItem();
|
|
71
|
+
// console.log('modelItem in repeatitem ', this.getModelItem()[this.index]);
|
|
72
|
+
return this.getModelItem()[this.index];
|
|
73
|
+
}
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
_dispatchIndexChange() {
|
|
77
|
+
this.dispatchEvent(
|
|
78
|
+
new CustomEvent('item-changed', {composed: false, bubbles: true, detail: {item: this, index: this.index}}),
|
|
79
|
+
);
|
|
103
80
|
}
|
|
104
|
-
*/
|
|
105
81
|
|
|
106
|
-
|
|
107
|
-
|
|
82
|
+
|
|
83
|
+
refresh(force) {
|
|
84
|
+
this.modelItem = this.getModelItem();
|
|
85
|
+
// ### register ourselves as boundControl
|
|
86
|
+
if (!this.modelItem.boundControls.includes(this)) {
|
|
87
|
+
this.modelItem.boundControls.push(this);
|
|
88
|
+
|
|
89
|
+
if (this.modelItem && !this.modelItem.relevant) {
|
|
90
|
+
this.removeAttribute('relevant');
|
|
91
|
+
this.setAttribute('nonrelevant', '');
|
|
92
|
+
} else {
|
|
93
|
+
this.removeAttribute('nonrelevant');
|
|
94
|
+
this.setAttribute('relevant', '');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Always recurse for these refreshes, especially when forced
|
|
98
|
+
Fore.refreshChildren(this, force);
|
|
99
|
+
}
|
|
108
100
|
}
|
|
109
101
|
|
|
110
102
|
if (!customElements.get('fx-repeatitem')) {
|
|
111
|
-
|
|
112
|
-
}
|
|
103
|
+
window.customElements.define('fx-repeatitem', FxRepeatitem);
|
|
104
|
+
}
|
package/src/ui/fx-trigger.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import XfAbstractControl from './abstract-control.js';
|
|
2
2
|
import { leadingDebounce } from '../events.js';
|
|
3
|
-
import {resolveId} from "../xpath-evaluation";
|
|
4
3
|
|
|
5
4
|
export class FxTrigger extends XfAbstractControl {
|
|
6
5
|
connectedCallback() {
|
|
@@ -24,7 +23,9 @@ export class FxTrigger extends XfAbstractControl {
|
|
|
24
23
|
const slot = this.shadowRoot.querySelector('slot');
|
|
25
24
|
slot.addEventListener('slotchange', () => {
|
|
26
25
|
const elements = slot.assignedElements({ flatten: true });
|
|
27
|
-
elements[0].
|
|
26
|
+
if(!elements[0].getAttribute('tabindex')){
|
|
27
|
+
elements[0].setAttribute('tabindex', '0');
|
|
28
|
+
}
|
|
28
29
|
if(elements[0].nodeName !== 'BUTTON'){
|
|
29
30
|
elements[0].setAttribute('role', 'button');
|
|
30
31
|
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import getInScopeContext from './getInScopeContext.js';
|
|
2
|
+
|
|
3
|
+
export const withDraggability = (superclass, isAlsoDraggable) =>
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adds draggability to generic components.
|
|
7
|
+
* Add the `dnd` attribute to make it draggable
|
|
8
|
+
*/
|
|
9
|
+
class DraggableComponent extends superclass {
|
|
10
|
+
static get properties() {
|
|
11
|
+
return {
|
|
12
|
+
...superclass.properties,
|
|
13
|
+
dnd: {
|
|
14
|
+
type: Boolean
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
connectedCallback() {
|
|
25
|
+
this.drop = (event) => this._drop(event);
|
|
26
|
+
this.addEventListener('drop', this.drop);
|
|
27
|
+
this.dragOver = (event) => this._dragOver(event);
|
|
28
|
+
this.addEventListener('dragover', this.dragOver);
|
|
29
|
+
this.dragLeave = (event) => this._dragLeave(event);
|
|
30
|
+
this.addEventListener('dragleave', this.dragLeave);
|
|
31
|
+
this.dragEnd = (event) => this._dragEnd(event);
|
|
32
|
+
this.addEventListener('dragend', this._dragEnd);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
disconnectedCallback() {
|
|
36
|
+
this.removeEventListener('drop', this.drop);
|
|
37
|
+
this.removeEventListener('dragover', this.dragOver);
|
|
38
|
+
this.removeEventListener('dragleave', this.dragLeave);
|
|
39
|
+
this.removeEventListener('dragend', this.dragEnd);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_dragOver(event) {
|
|
43
|
+
// console.log('dragover ',this);
|
|
44
|
+
// console.log('event target ',event.target);
|
|
45
|
+
if(event.target.classList.contains('no-drop')) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
event.stopPropagation();
|
|
49
|
+
const repeatItem = event.target.closest('fx-repeatitem');
|
|
50
|
+
if (!this.getOwnerForm().draggedItem) {
|
|
51
|
+
// Not dragging
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Only allow drag and drop in similar repeats
|
|
55
|
+
if (this === this.getOwnerForm().draggedItem) {
|
|
56
|
+
// Ignore: drop on itself
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const {draggedItem} = this.getOwnerForm();
|
|
60
|
+
|
|
61
|
+
if (this.accepts(draggedItem)) {
|
|
62
|
+
this.classList.remove('no-drop');
|
|
63
|
+
} else {
|
|
64
|
+
this.classList.add('no-drop');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const thisClosestRepeat = this.hasAttribute('id') ? this : this.closest('[id]');
|
|
68
|
+
const draggingClosestRepeat = draggedItem.hasAttribute('id') ? draggedItem : draggedItem.closest('[id]');
|
|
69
|
+
if (thisClosestRepeat?.id === draggingClosestRepeat?.id) {
|
|
70
|
+
if (repeatItem !== this.getOwnerForm().draggedItem) {
|
|
71
|
+
this.classList.add('drag-over');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_dragLeave(event){
|
|
79
|
+
this.classList.remove('drag-over');
|
|
80
|
+
this.classList.remove('no-drop');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_dragEnd (event) {
|
|
84
|
+
const item = this.getOwnerForm().draggedItem
|
|
85
|
+
if (item.getAttribute('drop-action') === 'copy') {
|
|
86
|
+
item.remove();
|
|
87
|
+
}
|
|
88
|
+
this.classList.remove('drag-over');
|
|
89
|
+
// event.stopPropagation();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_getDataNode () {
|
|
93
|
+
const dataNode = this.getOwnerForm().draggedItem?.getModelItem()?.node;
|
|
94
|
+
if (!dataNode) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const {draggedItem} = this.getOwnerForm();
|
|
99
|
+
const thisClosestRepeat = this.hasAttribute('id') ? this : this.closest('[id]');
|
|
100
|
+
const draggingClosestRepeat = draggedItem.hasAttribute('id') ? draggedItem : draggedItem.closest('[id]');
|
|
101
|
+
if (thisClosestRepeat?.id !== draggingClosestRepeat?.id) {
|
|
102
|
+
// Moving between different repeats: this can make the items 'lost': placed into a
|
|
103
|
+
// different set
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return dataNode;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
accepts (draggedItem) {
|
|
111
|
+
if (!this.hasAttribute('accept')) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const accept = this.getAttribute('accept');
|
|
115
|
+
const isAccepted = draggedItem.matches(accept);
|
|
116
|
+
// console.log('accepted', isAccepted);
|
|
117
|
+
return isAccepted;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_drop(event){
|
|
121
|
+
this.classList.remove('drag-over');
|
|
122
|
+
event.stopPropagation();
|
|
123
|
+
if (this.localName === 'fx-droptarget') {
|
|
124
|
+
if(this.children.length !== 0){
|
|
125
|
+
console.log("we have to do something");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let {draggedItem} = this.getOwnerForm();
|
|
129
|
+
|
|
130
|
+
if (draggedItem.getAttribute('drop-action') === 'copy') {
|
|
131
|
+
draggedItem = draggedItem.cloneNode(true);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!this.accepts(draggedItem)) {
|
|
135
|
+
this.classList.remove('no-drop');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if(draggedItem === this){
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if(draggedItem.localName === 'fx-droptarget'){
|
|
142
|
+
// todo : this looks still a bit weak
|
|
143
|
+
if(this.hasAttribute('drop-position')){
|
|
144
|
+
this.replaceChildren(draggedItem);
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if(this.parentNode.lastElementChild === this){
|
|
149
|
+
this.parentNode.append(draggedItem);
|
|
150
|
+
event.stopImmediatePropagation();
|
|
151
|
+
// return;
|
|
152
|
+
}else if (draggedItem === this.previousElementSibling) {
|
|
153
|
+
// insertBefore of draggedItem before us would be a no-op: it is already before us.
|
|
154
|
+
// Instead: insert _after_ us, so we can still do something!
|
|
155
|
+
this.parentNode.insertBefore(draggedItem, this.nextElementSibling);
|
|
156
|
+
} else {
|
|
157
|
+
this.parentNode.insertBefore(draggedItem, this);
|
|
158
|
+
}
|
|
159
|
+
}else{
|
|
160
|
+
this.appendChild(draggedItem);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/*
|
|
164
|
+
if(this.hasAttribute('drop-position')){
|
|
165
|
+
if(this.getAttribute('drop-position') === 'before'){
|
|
166
|
+
this.parentNode.insertBefore(draggedItem,this);
|
|
167
|
+
} else {
|
|
168
|
+
this.parentNode.append(draggedItem);
|
|
169
|
+
}
|
|
170
|
+
}else{
|
|
171
|
+
this.replaceChildren(draggedItem);
|
|
172
|
+
}
|
|
173
|
+
*/
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
this.getOwnerForm().getModel().updateModel();
|
|
176
|
+
this.getOwnerForm().refresh(true);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const dataNode = this._getDataNode();
|
|
180
|
+
if (!dataNode) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (this.localName === 'fx-repeat') {
|
|
185
|
+
// We are sure we'll handle this event!
|
|
186
|
+
event.preventDefault();
|
|
187
|
+
// Dropping on repeat itself always means to *append* the dropped item
|
|
188
|
+
|
|
189
|
+
let contextNode = this.nodeset;
|
|
190
|
+
if (Array.isArray(contextNode) && !contextNode.length) {
|
|
191
|
+
// Guess: just append it to the context node. Hope that the `ref` is actually a
|
|
192
|
+
// child axis, like `ref="./item"`. A ref like `./items/item` breaks.
|
|
193
|
+
const context = getInScopeContext(this.getAttributeNode('ref') || this, this.ref);
|
|
194
|
+
context.append(dataNode);
|
|
195
|
+
} else {
|
|
196
|
+
// Guess: just insert it after it to the context node. Hope that the `ref` is
|
|
197
|
+
// actually listing siblings, like `ref="./item"` or
|
|
198
|
+
// `ref="./items/item"`. `ref="descendant::item[@category='a']"` breaks
|
|
199
|
+
contextNode = contextNode[contextNode.length - 1];
|
|
200
|
+
contextNode.after(dataNode);
|
|
201
|
+
}
|
|
202
|
+
} else if (this.localName === 'fx-repeatitem') {
|
|
203
|
+
const repeatItemNode = this.getModelItem().node;
|
|
204
|
+
|
|
205
|
+
if (repeatItemNode.previousSibling === dataNode) {
|
|
206
|
+
// moving before will make it do nothing, move after
|
|
207
|
+
repeatItemNode.after(dataNode);
|
|
208
|
+
} else {
|
|
209
|
+
repeatItemNode.before(dataNode);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Note: full refresh needed since multiple model items may be affected.
|
|
214
|
+
// TODO: Leverage the changedPaths trick
|
|
215
|
+
this.getOwnerForm().getModel().updateModel();
|
|
216
|
+
this.getOwnerForm().refresh(true);
|
|
217
|
+
}
|
|
218
|
+
};
|
package/src/xpath-evaluation.js
CHANGED
|
@@ -339,6 +339,8 @@ function functionNameResolver({prefix, localName}, _arity) {
|
|
|
339
339
|
case 'fore-attr':
|
|
340
340
|
case 'index':
|
|
341
341
|
case 'instance':
|
|
342
|
+
case 'json2xml':
|
|
343
|
+
case 'xml2Json':
|
|
342
344
|
case 'log':
|
|
343
345
|
case 'parse':
|
|
344
346
|
case 'local-date':
|
|
@@ -349,6 +351,7 @@ function functionNameResolver({prefix, localName}, _arity) {
|
|
|
349
351
|
case 'uri-host':
|
|
350
352
|
case 'uri-param':
|
|
351
353
|
case 'uri-path':
|
|
354
|
+
case 'uri-relpath':
|
|
352
355
|
case 'uri-port':
|
|
353
356
|
case 'uri-query':
|
|
354
357
|
case 'uri-scheme':
|
|
@@ -381,7 +384,9 @@ function functionNameResolver({prefix, localName}, _arity) {
|
|
|
381
384
|
function getVariablesInScope(formElement) {
|
|
382
385
|
let closestActualFormElement = formElement;
|
|
383
386
|
while (closestActualFormElement && !('inScopeVariables' in closestActualFormElement)) {
|
|
384
|
-
closestActualFormElement = closestActualFormElement.
|
|
387
|
+
closestActualFormElement = closestActualFormElement.nodeType === Node.ATTRIBUTE_NODE ?
|
|
388
|
+
closestActualFormElement.ownerElement :
|
|
389
|
+
closestActualFormElement.parentNode;
|
|
385
390
|
}
|
|
386
391
|
|
|
387
392
|
if (!closestActualFormElement) {
|
|
@@ -1036,6 +1041,131 @@ registerCustomXPathFunction(
|
|
|
1036
1041
|
'item()?',
|
|
1037
1042
|
instance,
|
|
1038
1043
|
);
|
|
1044
|
+
const getAttributes = (value) => {
|
|
1045
|
+
if (Array.isArray(value)) {
|
|
1046
|
+
return ` type="array"`;
|
|
1047
|
+
} else if (typeof value === 'number') {
|
|
1048
|
+
return ` type="number"`;
|
|
1049
|
+
} else if (typeof value === 'boolean') {
|
|
1050
|
+
return ` type="boolean"`;
|
|
1051
|
+
}
|
|
1052
|
+
return '';
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
const jsonToXml = (dynamicContext, json) => {
|
|
1056
|
+
const escapeXml = (str) => {
|
|
1057
|
+
return str.replace(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]/g, (char) => {
|
|
1058
|
+
return `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`;
|
|
1059
|
+
});
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
const convert = (obj, parent) => {
|
|
1063
|
+
const type = typeof obj;
|
|
1064
|
+
if (type === 'number') {
|
|
1065
|
+
parent.setAttribute('type', 'number');
|
|
1066
|
+
parent.textContent = obj.toString();
|
|
1067
|
+
} else if (type === 'boolean') {
|
|
1068
|
+
parent.setAttribute('type', 'boolean');
|
|
1069
|
+
parent.textContent = obj.toString();
|
|
1070
|
+
} else if (obj === null) {
|
|
1071
|
+
const node = document.createElement('_');
|
|
1072
|
+
node.setAttribute('type', 'null');
|
|
1073
|
+
parent.appendChild(node);
|
|
1074
|
+
} else if (type === 'string') {
|
|
1075
|
+
parent.textContent = escapeXml(obj);
|
|
1076
|
+
} else if (Array.isArray(obj)) {
|
|
1077
|
+
parent.setAttribute('type', 'array');
|
|
1078
|
+
obj.forEach((item) => {
|
|
1079
|
+
const node = document.createElement('_');
|
|
1080
|
+
convert(item, node);
|
|
1081
|
+
node.textContent = item;
|
|
1082
|
+
parent.appendChild(node);
|
|
1083
|
+
});
|
|
1084
|
+
} else if (type === 'object') {
|
|
1085
|
+
parent.setAttribute('type', 'object');
|
|
1086
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
1087
|
+
if(value){
|
|
1088
|
+
const childNode = document.createElement(key.replace(/[^a-zA-Z0-9_]/g, '_'));
|
|
1089
|
+
convert(value, childNode);
|
|
1090
|
+
parent.appendChild(childNode);
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
const root = document.createElement('json');
|
|
1097
|
+
if(Array.isArray(json)){
|
|
1098
|
+
root.setAttribute('type', 'array');
|
|
1099
|
+
}else{
|
|
1100
|
+
root.setAttribute('type', 'object');
|
|
1101
|
+
}
|
|
1102
|
+
convert(json, root);
|
|
1103
|
+
// return root.outerHTML;
|
|
1104
|
+
console.log('xml',root)
|
|
1105
|
+
return root;
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
registerCustomXPathFunction(
|
|
1109
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'json2xml'},
|
|
1110
|
+
['item()?'],
|
|
1111
|
+
'item()?',
|
|
1112
|
+
jsonToXml
|
|
1113
|
+
);
|
|
1114
|
+
const xmlToJson = (dynamicContext, xml) => {
|
|
1115
|
+
const isElementNode = (node) => {
|
|
1116
|
+
return node.nodeType === Node.ELEMENT_NODE;
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
const isTextNode = (node) => {
|
|
1120
|
+
return node.nodeType === Node.TEXT_NODE;
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1123
|
+
const parseNode = (node) => {
|
|
1124
|
+
if (isElementNode(node)) {
|
|
1125
|
+
const obj = {};
|
|
1126
|
+
if (node.hasAttributes()) {
|
|
1127
|
+
obj['type'] = node.getAttribute('type');
|
|
1128
|
+
}
|
|
1129
|
+
if (node.childNodes.length === 1 && isTextNode(node.firstChild)) {
|
|
1130
|
+
return node.textContent;
|
|
1131
|
+
}
|
|
1132
|
+
for (const child of node.childNodes) {
|
|
1133
|
+
const childName = child.nodeName;
|
|
1134
|
+
const childValue = parseNode(child);
|
|
1135
|
+
if (obj[childName]) {
|
|
1136
|
+
if (!Array.isArray(obj[childName])) {
|
|
1137
|
+
obj[childName] = [obj[childName]];
|
|
1138
|
+
}
|
|
1139
|
+
obj[childName].push(childValue);
|
|
1140
|
+
} else {
|
|
1141
|
+
obj[childName] = childValue;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return obj;
|
|
1145
|
+
} else if (isTextNode(node)) {
|
|
1146
|
+
return node.textContent;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
const parser = new DOMParser();
|
|
1152
|
+
const xmlDoc = parser.parseFromString(xml, 'application/xml');
|
|
1153
|
+
const root = xmlDoc.documentElement;
|
|
1154
|
+
return parseNode(root);
|
|
1155
|
+
};
|
|
1156
|
+
registerCustomXPathFunction(
|
|
1157
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'xmltoJson'},
|
|
1158
|
+
['item()?'],
|
|
1159
|
+
'item()?',
|
|
1160
|
+
xmlToJson
|
|
1161
|
+
);
|
|
1162
|
+
|
|
1163
|
+
/*
|
|
1164
|
+
// Example usage:
|
|
1165
|
+
const xml = '<json type="object"><given>Mark</given><family>Smith</family></json>';
|
|
1166
|
+
console.log(xmlToJson(xml));
|
|
1167
|
+
*/
|
|
1168
|
+
|
|
1039
1169
|
|
|
1040
1170
|
registerCustomXPathFunction(
|
|
1041
1171
|
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'depends'},
|
|
@@ -1167,6 +1297,15 @@ registerCustomXPathFunction(
|
|
|
1167
1297
|
'xs:string?',
|
|
1168
1298
|
(dynamicContext, arg) => window.location.search,
|
|
1169
1299
|
);
|
|
1300
|
+
registerCustomXPathFunction(
|
|
1301
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-relpath'},
|
|
1302
|
+
[],
|
|
1303
|
+
'xs:string?',
|
|
1304
|
+
(dynamicContext, arg) => {
|
|
1305
|
+
const path = new URL(window.location.href).pathname;
|
|
1306
|
+
return path.substring(0,path.lastIndexOf('/') + 1);
|
|
1307
|
+
},
|
|
1308
|
+
);
|
|
1170
1309
|
registerCustomXPathFunction(
|
|
1171
1310
|
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'uri-path'},
|
|
1172
1311
|
[],
|