Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
Monster
Manage
Activity
Members
Plan
Jira
Code
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
OSS
Libraries
Javascript
Monster
Commits
fb796880
Verified
Commit
fb796880
authored
10 months ago
by
Volker Schukai
Browse files
Options
Downloads
Patches
Plain Diff
feat: new dataset feature refreshOnMutation #215
parent
df47406b
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
source/components/datatable/dataset.mjs
+278
-202
278 additions, 202 deletions
source/components/datatable/dataset.mjs
with
278 additions
and
202 deletions
source/components/datatable/dataset.mjs
+
278
−
202
View file @
fb796880
...
...
@@ -12,33 +12,33 @@
* SPDX-License-Identifier: AGPL-3.0
*/
import
{
instanceSymbol
,
internalSymbol
}
from
"
../../constants.mjs
"
;
import
{
Pathfinder
}
from
"
../../data/pathfinder.mjs
"
;
import
{
getLinkedObjects
,
hasObjectLink
}
from
"
../../dom/attributes.mjs
"
;
import
{
customElementUpdaterLinkSymbol
}
from
"
../../dom/constants.mjs
"
;
import
{
instanceSymbol
,
internalSymbol
}
from
"
../../constants.mjs
"
;
import
{
Pathfinder
}
from
"
../../data/pathfinder.mjs
"
;
import
{
getLinkedObjects
,
hasObjectLink
}
from
"
../../dom/attributes.mjs
"
;
import
{
customElementUpdaterLinkSymbol
}
from
"
../../dom/constants.mjs
"
;
import
{
assembleMethodSymbol
,
CustomElement
,
attributeObserverSymbol
,
registerCustomElement
,
assembleMethodSymbol
,
CustomElement
,
attributeObserverSymbol
,
registerCustomElement
,
}
from
"
../../dom/customelement.mjs
"
;
import
{
findElementWithSelectorUpwards
}
from
"
../../dom/util.mjs
"
;
import
{
isString
}
from
"
../../types/is.mjs
"
;
import
{
Observer
}
from
"
../../types/observer.mjs
"
;
import
{
clone
}
from
"
../../util/clone.mjs
"
;
import
{
findElementWithSelectorUpwards
}
from
"
../../dom/util.mjs
"
;
import
{
isString
}
from
"
../../types/is.mjs
"
;
import
{
Observer
}
from
"
../../types/observer.mjs
"
;
import
{
clone
}
from
"
../../util/clone.mjs
"
;
import
{
ATTRIBUTE_DATASOURCE_SELECTOR
,
ATTRIBUTE_DATATABLE_INDEX
,
ATTRIBUTE_DATASOURCE_SELECTOR
,
ATTRIBUTE_DATATABLE_INDEX
,
}
from
"
./constants.mjs
"
;
import
{
Datasource
}
from
"
./datasource.mjs
"
;
import
{
DatasetStyleSheet
}
from
"
./stylesheet/dataset.mjs
"
;
import
{
Datasource
}
from
"
./datasource.mjs
"
;
import
{
DatasetStyleSheet
}
from
"
./stylesheet/dataset.mjs
"
;
import
{
handleDataSourceChanges
,
datasourceLinkedElementSymbol
,
handleDataSourceChanges
,
datasourceLinkedElementSymbol
,
}
from
"
./util.mjs
"
;
import
{
FormStyleSheet
}
from
"
../stylesheet/form.mjs
"
;
import
{
FormStyleSheet
}
from
"
../stylesheet/form.mjs
"
;
export
{
DataSet
};
export
{
DataSet
};
/**
* The data set component is used to show the data of a data source.
...
...
@@ -79,184 +79,219 @@ export { DataSet };
* @summary A data set
*/
class
DataSet
extends
CustomElement
{
/**
* This method is called by the `instanceof` operator.
* @returns {symbol}
*/
static
get
[
instanceSymbol
]()
{
return
Symbol
.
for
(
"
@schukai/monster/components/dataset@@instance
"
);
}
/**
* This method determines which attributes are to be monitored by `attributeChangedCallback()`.
*
* @return {string[]}
* @since 1.15.0
*/
static
get
observedAttributes
()
{
const
attributes
=
super
.
observedAttributes
;
attributes
.
push
(
ATTRIBUTE_DATATABLE_INDEX
);
return
attributes
;
}
/**
* To set the options via the html tag the attribute `data-monster-options` must be used.
* @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
*
* The individual configuration values can be found in the table.
*
* @property {Object} templates Template definitions
* @property {string} templates.main Main template
* @property {object} datasource The datasource
* @property {string} datasource.selector The selector of the datasource
* @property {object} mapping The mapping
* @property {string} mapping.data The data
* @property {number} mapping.index The index
* @property {Array} data The data
*/
get
defaults
()
{
const
obj
=
Object
.
assign
({},
super
.
defaults
,
{
templates
:
{
main
:
getTemplate
(),
},
datasource
:
{
selector
:
null
,
},
mapping
:
{
data
:
"
dataset
"
,
index
:
0
,
},
data
:
{},
});
updateOptionsFromArguments
.
call
(
this
,
obj
);
return
obj
;
}
/**
*
* @return {string}
*/
static
getTag
()
{
return
"
monster-dataset
"
;
}
write
()
{
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
!
this
[
datasourceLinkedElementSymbol
])
{
reject
(
new
Error
(
"
No datasource
"
));
return
;
}
const
internalUpdateCloneData
=
this
.
getInternalUpdateCloneData
();
if
(
!
internalUpdateCloneData
)
{
reject
(
new
Error
(
"
No update data
"
));
return
;
}
const
internalData
=
internalUpdateCloneData
?.[
"
data
"
];
if
(
internalData
===
undefined
||
internalData
===
null
||
internalData
===
""
)
{
reject
(
new
Error
(
"
No data
"
));
return
;
}
setTimeout
(()
=>
{
const
path
=
this
.
getOption
(
"
mapping.data
"
);
const
index
=
this
.
getOption
(
"
mapping.index
"
);
let
pathWithIndex
;
if
(
isString
(
path
)
&&
path
!==
""
)
{
pathWithIndex
=
path
+
"
.
"
+
index
;
}
else
{
pathWithIndex
=
index
;
}
const
data
=
this
[
datasourceLinkedElementSymbol
].
data
;
const
unref
=
JSON
.
stringify
(
data
);
const
ref
=
JSON
.
parse
(
unref
);
new
Pathfinder
(
ref
).
setVia
(
pathWithIndex
,
internalData
);
this
[
datasourceLinkedElementSymbol
].
data
=
ref
;
resolve
();
},
0
);
});
}
/**
* This method is responsible for assembling the component.
*
* It calls the parent's assemble method first, then initializes control references and event handlers.
* If the `datasource.selector` option is provided and is a string, it searches for the corresponding
* element in the DOM using that selector.
*
* If the selector matches exactly one element, it checks if the element is an instance of the `Datasource` class.
*
* If it is, the component's `datasourceLinkedElementSymbol` property is set to the element, and the component
* attaches an observer to the datasource's changes.
*
* The observer is a function that calls the `handleDataSourceChanges` method in the context of the component.
* Additionally, the component attaches an observer to itself, which also calls the `handleDataSourceChanges`
* method in the component's context.
*/
[
assembleMethodSymbol
]()
{
super
[
assembleMethodSymbol
]();
// initControlReferences.call(self);
initEventHandler
.
call
(
this
);
const
selector
=
this
.
getOption
(
"
datasource.selector
"
);
if
(
isString
(
selector
))
{
const
element
=
findElementWithSelectorUpwards
(
this
,
selector
);
if
(
element
===
null
)
{
throw
new
Error
(
"
the selector must match exactly one element
"
);
}
if
(
!
(
element
instanceof
Datasource
))
{
throw
new
TypeError
(
"
the element must be a datasource
"
);
}
this
[
datasourceLinkedElementSymbol
]
=
element
;
element
.
datasource
.
attachObserver
(
new
Observer
(
handleDataSourceChanges
.
bind
(
this
)),
);
}
this
.
attachObserver
(
new
Observer
(()
=>
{
handleDataSourceChanges
.
call
(
this
);
}),
);
}
/**
* @return [CSSStyleSheet]
*/
static
getCSSStyleSheet
()
{
return
[
FormStyleSheet
,
DatasetStyleSheet
];
}
/**
* This method is called by the `instanceof` operator.
* @returns {symbol}
*/
static
get
[
instanceSymbol
]()
{
return
Symbol
.
for
(
"
@schukai/monster/components/dataset@@instance
"
);
}
/**
* This method determines which attributes are to be monitored by `attributeChangedCallback()`.
*
* @return {string[]}
* @since 1.15.0
*/
static
get
observedAttributes
()
{
const
attributes
=
super
.
observedAttributes
;
attributes
.
push
(
ATTRIBUTE_DATATABLE_INDEX
);
return
attributes
;
}
/**
* To set the options via the html tag the attribute `data-monster-options` must be used.
* @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
*
* The individual configuration values can be found in the table.
*
* @property {Object} templates Template definitions
* @property {string} templates.main Main template
* @property {object} datasource The datasource
* @property {string} datasource.selector The selector of the datasource
* @property {object} mapping The mapping
* @property {string} mapping.data The data
* @property {number} mapping.index The index
* @property {Array} data The data
*/
get
defaults
()
{
const
obj
=
Object
.
assign
({},
super
.
defaults
,
{
templates
:
{
main
:
getTemplate
(),
},
datasource
:
{
selector
:
null
,
},
mapping
:
{
data
:
"
dataset
"
,
index
:
0
,
},
features
:
{
/**
* @since 3.70.0
* @type {boolean}
*/
refreshOnMutation
:
true
,
},
/**
* @since 3.70.0
* @type {boolean}
*/
refreshOnMutation
:
{
selector
:
"
input, select, textarea
"
},
data
:
{},
});
updateOptionsFromArguments
.
call
(
this
,
obj
);
return
obj
;
}
/**
*
* @return {string}
*/
static
getTag
()
{
return
"
monster-dataset
"
;
}
/**
* This method is called when the component is created.
* @since 3.70.0
* @returns {DataSet}
*/
refresh
()
{
// makes sure that handleDataSourceChanges is called
this
.
setOption
(
"
data
"
,
{});
return
this
;
}
/**
*
* @returns {Promise<unknown>}
*/
write
()
{
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
!
this
[
datasourceLinkedElementSymbol
])
{
reject
(
new
Error
(
"
No datasource
"
));
return
;
}
const
internalUpdateCloneData
=
this
.
getInternalUpdateCloneData
();
if
(
!
internalUpdateCloneData
)
{
reject
(
new
Error
(
"
No update data
"
));
return
;
}
const
internalData
=
internalUpdateCloneData
?.[
"
data
"
];
if
(
internalData
===
undefined
||
internalData
===
null
||
internalData
===
""
)
{
reject
(
new
Error
(
"
No data
"
));
return
;
}
setTimeout
(()
=>
{
const
path
=
this
.
getOption
(
"
mapping.data
"
);
const
index
=
this
.
getOption
(
"
mapping.index
"
);
let
pathWithIndex
;
if
(
isString
(
path
)
&&
path
!==
""
)
{
pathWithIndex
=
path
+
"
.
"
+
index
;
}
else
{
pathWithIndex
=
String
(
index
);
}
const
data
=
this
[
datasourceLinkedElementSymbol
].
data
;
const
unref
=
JSON
.
stringify
(
data
);
const
ref
=
JSON
.
parse
(
unref
);
new
Pathfinder
(
ref
).
setVia
(
pathWithIndex
,
internalData
);
this
[
datasourceLinkedElementSymbol
].
data
=
ref
;
resolve
();
},
0
);
});
}
/**
* This method is responsible for assembling the component.
*
* It calls the parent's assemble method first, then initializes control references and event handlers.
* If the `datasource.selector` option is provided and is a string, it searches for the corresponding
* element in the DOM using that selector.
*
* If the selector matches exactly one element, it checks if the element is an instance of the `Datasource` class.
*
* If it is, the component's `datasourceLinkedElementSymbol` property is set to the element, and the component
* attaches an observer to the datasource's changes.
*
* The observer is a function that calls the `handleDataSourceChanges` method in the context of the component.
* Additionally, the component attaches an observer to itself, which also calls the `handleDataSourceChanges`
* method in the component's context.
*/
[
assembleMethodSymbol
]()
{
super
[
assembleMethodSymbol
]();
initEventHandler
.
call
(
this
);
const
selector
=
this
.
getOption
(
"
datasource.selector
"
);
if
(
isString
(
selector
))
{
const
element
=
findElementWithSelectorUpwards
(
this
,
selector
);
if
(
element
===
null
)
{
throw
new
Error
(
"
the selector must match exactly one element
"
);
}
if
(
!
(
element
instanceof
Datasource
))
{
throw
new
TypeError
(
"
the element must be a datasource
"
);
}
this
[
datasourceLinkedElementSymbol
]
=
element
;
element
.
datasource
.
attachObserver
(
new
Observer
(
handleDataSourceChanges
.
bind
(
this
)),
);
}
this
.
attachObserver
(
new
Observer
(()
=>
{
handleDataSourceChanges
.
call
(
this
);
}),
);
if
(
this
.
getOption
(
"
features.refreshOnMutation
"
)
&&
this
.
getOption
(
"
refreshOnMutation.selector
"
))
{
initMutationObserver
.
call
(
this
);
}
}
/**
* @return [CSSStyleSheet]
*/
static
getCSSStyleSheet
()
{
return
[
FormStyleSheet
,
DatasetStyleSheet
];
}
}
/**
* @private
*/
function
initEventHandler
()
{
this
[
attributeObserverSymbol
][
ATTRIBUTE_DATATABLE_INDEX
]
=
()
=>
{
const
index
=
this
.
getAttribute
(
ATTRIBUTE_DATATABLE_INDEX
);
if
(
index
)
{
this
.
setOption
(
"
mapping.index
"
,
parseInt
(
index
,
10
));
}
};
this
[
attributeObserverSymbol
][
ATTRIBUTE_DATATABLE_INDEX
]
=
()
=>
{
const
index
=
this
.
getAttribute
(
ATTRIBUTE_DATATABLE_INDEX
);
if
(
index
)
{
this
.
setOption
(
"
mapping.index
"
,
parseInt
(
index
,
10
));
}
};
}
/**
...
...
@@ -264,26 +299,67 @@ function initEventHandler() {
* @param {Object} options
*/
function
updateOptionsFromArguments
(
options
)
{
const
index
=
this
.
getAttribute
(
ATTRIBUTE_DATATABLE_INDEX
);
const
index
=
this
.
getAttribute
(
ATTRIBUTE_DATATABLE_INDEX
);
if
(
index
!==
null
&&
index
!==
undefined
)
{
options
.
mapping
.
index
=
parseInt
(
index
,
10
);
}
if
(
index
!==
null
&&
index
!==
undefined
)
{
options
.
mapping
.
index
=
parseInt
(
index
,
10
);
}
const
selector
=
this
.
getAttribute
(
ATTRIBUTE_DATASOURCE_SELECTOR
);
const
selector
=
this
.
getAttribute
(
ATTRIBUTE_DATASOURCE_SELECTOR
);
if
(
selector
)
{
options
.
datasource
.
selector
=
selector
;
}
if
(
selector
)
{
options
.
datasource
.
selector
=
selector
;
}
}
/**
* @private
*/
function
initMutationObserver
()
{
const
config
=
{
attributes
:
false
,
childList
:
true
,
subtree
:
true
};
const
callback
=
(
mutationList
,
observer
)
=>
{
if
(
mutationList
.
length
===
0
)
{
return
;
}
let
doneFlag
=
false
;
for
(
const
mutation
of
mutationList
)
{
if
(
mutation
.
type
===
"
childList
"
)
{
for
(
const
node
of
mutation
.
addedNodes
)
{
if
(
node
instanceof
HTMLElement
&&
node
.
matches
(
this
.
getOption
(
"
refreshOnMutation.selector
"
)))
{
doneFlag
=
true
;
break
;
}
}
if
(
doneFlag
)
{
break
;
}
}
}
if
(
doneFlag
)
{
this
.
refresh
();
}
};
const
observer
=
new
MutationObserver
(
callback
);
observer
.
observe
(
this
,
config
);
}
/**
* @private
* @return {string}
*/
function
getTemplate
()
{
// language=HTML
return
`
// language=HTML
return
`
<div data-monster-role="control" part="control">
<slot></slot>
</div>
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment