-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wip base editor and composable functionality with test case
- Loading branch information
Showing
6 changed files
with
355 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
<!-- | ||
This BaseEditor component is designed as a building block for creating custom | ||
input components. It provides a consistent API and structure for both the parent | ||
component that uses it and for developers who extend it. By extending this base | ||
component, developers can focus on the unique aspects of their custom components | ||
(such as a combination of specific input types or complex logic) while inheriting | ||
standardized behavior for common tasks like data synchronization, validation, and | ||
error management. | ||
|
||
Follow these steps to create a custom input component that extends the BaseEditor | ||
component. This assumes you are familiar with Vue.js and Vuetify, as well as the | ||
Composition API: | ||
|
||
- create a new .vue file in your components directory, for example: MyCustomEditor.vue | ||
- in the template section, use the BaseEditor component as the primary wrapper | ||
- pass the required props to BaseEditor that control its behavior: v-model, | ||
label, rules, value-parser, and value-combiner. These props allow the parent | ||
component to interact with your custom component. | ||
- define all of your custom components within the names "inputs" slot: | ||
`<template #inputs="{ subValues }"></template>` | ||
- subcomponent values should be v-modeled using unique keys on the named slot's | ||
`subValues` prop; for example v-model a v-text-field to `subValues.customTextField` | ||
- implement the valueParser and valueCombiner functions that transform the parent | ||
v-model value "internalValue" into subcomponent values and vice versa. | ||
- TODO: include description of rules and validation | ||
- TODO: include description of matching (although this is not influenced by the BaseEditor) | ||
|
||
--> | ||
|
||
<template> | ||
<!-- v-input is used as the wrapper component for all editor/input components --> | ||
<v-input | ||
v-model="internalValue" | ||
:error="computedError" | ||
:error-messages="computedErrorMessages" | ||
class="base-editor" | ||
> | ||
<!-- The default slot template provides scoped props for custom behaviour --> | ||
<template #default="{ isFocused, isDirty }"> | ||
<v-row no-gutters> | ||
<!-- Custom inputs are passed in via the named slot "inputs". | ||
This is the meat for extending the BaseEditor component --> | ||
<!-- Automatically binds the input handler to the input event --> | ||
<slot name="inputs" :subValues="subValues" :bind-input="bindInput"></slot> | ||
</v-row> | ||
</template> | ||
</v-input> | ||
</template> | ||
|
||
<script setup> | ||
import { useBaseInput } from '@/composables/baseinput'; | ||
import { computed } from 'vue'; | ||
|
||
/** | ||
* Props for the BaseEditor component. | ||
* @typedef {Object} BaseEditorProps | ||
* @property {string|number|Object} modelValue - The value bound to v-model. | ||
* @property {Array} [rules] - An array of validation rules for the input. | ||
* @property {Function} valueParser - A function to parse the modelValue into subcomponent values. | ||
* @property {Function} valueCombiner - A function to combine subcomponent values into the modelValue. | ||
*/ | ||
|
||
/** | ||
* Emits for the BaseEditor component. | ||
* @typedef {Object} BaseEditorEmits | ||
* @property {Function} update:modelValue - Emits when the value changes. | ||
*/ | ||
|
||
// Define props and emits | ||
const props = defineProps({ | ||
modelValue: [String, Number, Object], | ||
rules: Array, | ||
valueParser: Function, | ||
valueCombiner: Function | ||
}); | ||
const emit = defineEmits(['update:modelValue']); | ||
|
||
// Use the composable to manage the custom input logic | ||
const { subValues, internalValue, updateValue } = useBaseInput( | ||
props, | ||
emit, | ||
props.valueParser, | ||
props.valueCombiner | ||
); | ||
|
||
/** | ||
* Computes whether the input has validation errors based on the provided rules. | ||
* @returns {boolean} True if there's any validation error, otherwise false. | ||
*/ | ||
const computedError = computed(() => { | ||
return props.rules?.some(rule => rule(internalValue.value) !== true); | ||
}); | ||
|
||
/** | ||
* Computes the list of error messages based on the validation rules. | ||
* @returns {Array} An array of error messages. | ||
*/ | ||
const computedErrorMessages = computed(() => { | ||
return props.rules?.map(rule => rule(internalValue.value)).filter(msg => msg !== true) || []; | ||
}); | ||
|
||
/** | ||
* Automatically bind input events to the updateValue function for custom inputs. | ||
* @param {Object} element - The Vue component instance of the input element. | ||
*/ | ||
function bindInput(element) { | ||
if (element && element.props && !element.props.onInput) { | ||
element.props.onInput = updateValue; | ||
} | ||
} | ||
</script> | ||
|
||
|
||
<style scoped> | ||
.base-editor { | ||
/* Custom styling for the base component */ | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<template> | ||
<BaseEditor | ||
v-model="internalValue" | ||
:value-parser="parseValue" | ||
:value-combiner="combineValues" | ||
> | ||
<template #inputs="{ subValues }"> | ||
<v-col cols="6"> | ||
<v-text-field | ||
v-model="subValues.part1" | ||
label="Part 1" | ||
></v-text-field> | ||
</v-col> | ||
<v-col cols="6"> | ||
<v-select | ||
v-model="subValues.part2" | ||
:items="selectItems" | ||
label="Part 2" | ||
></v-select> | ||
</v-col> | ||
</template> | ||
</BaseEditor> | ||
BaseEditor v-model value: {{ internalValue }} | ||
</template> | ||
|
||
<script setup> | ||
import BaseEditor from '@/components/BaseEditor.vue'; | ||
// import { useBaseInput } from '@/composables/baseinput'; | ||
import { ref, watch, onBeforeMount} from 'vue' | ||
const props = defineProps({ | ||
modelValue: String, | ||
property_shape: Object, | ||
node_uid: String, | ||
triple_uid: String | ||
}); | ||
const emit = defineEmits(['update:modelValue']); | ||
// Internal reactive state | ||
const internalValue = ref(props.modelValue); | ||
// Watch for changes in modelValue prop | ||
watch(() => props.modelValue, (newValue) => { | ||
internalValue.value = newValue; | ||
}); | ||
// Emit updates to parent | ||
watch(internalValue, (newValue) => { | ||
emit('update:modelValue', newValue); | ||
}); | ||
// const { internalValue } = useBaseInput( | ||
// props, | ||
// emit, | ||
// parseValue, | ||
// combineValues | ||
// ); | ||
const selectItems = ["kaas", "koek", "moer"] | ||
onBeforeMount(() => { | ||
console.log("...BeforeMount CustomSimple....") | ||
console.log(props.modelValue) | ||
}) | ||
// Parse the combined value into subcomponent values | ||
function parseValue(value) { | ||
if (value) { | ||
const [part1, part2] = value.split('-'); | ||
return { part1: part1 || '', part2: part2 || '' }; | ||
} else { | ||
return { part1: '', part2: '' }; | ||
} | ||
} | ||
// Combine subcomponent values into a single value | ||
function combineValues(values) { | ||
return `${values.part1}-${values.part2}`; | ||
} | ||
</script> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
<template> | ||
|
||
<v-sheet class="pa-4" border rounded elevation="2"> | ||
|
||
<!-- <component | ||
:is="CustomSimple" | ||
v-model="formData[node_uid][0][triple_uid][0]" | ||
:property_shape="shape_obj" | ||
:node_uid="node_uid" | ||
:triple_uid="triple_uid" | ||
> | ||
</component> --> | ||
|
||
<v-btn @click="logStuff">Log stuff</v-btn> | ||
|
||
<v-switch v-model="kaas"></v-switch> | ||
|
||
<span v-if="kaas"> | ||
<CustomSimple | ||
v-model="formData[node_uid][0][triple_uid][0]" | ||
:property_shape="shape_obj" | ||
:node_uid="node_uid" | ||
:triple_uid="triple_uid" | ||
> | ||
</CustomSimple> | ||
</span> | ||
|
||
<v-divider></v-divider> | ||
|
||
{{ formData }} | ||
|
||
<v-divider></v-divider> | ||
|
||
{{ formData[node_uid] }} | ||
|
||
<v-divider></v-divider> | ||
|
||
{{ formData[node_uid].at(-1) }} | ||
|
||
<v-divider></v-divider> | ||
|
||
bla: {{ formData[node_uid].at(-1)[triple_uid] }} | ||
|
||
<v-divider></v-divider> | ||
|
||
blabla: {{ formData[node_uid].at(-1)[triple_uid].at(-1) }} | ||
|
||
<v-divider></v-divider> | ||
|
||
|
||
</v-sheet> | ||
|
||
</template> | ||
|
||
<script setup> | ||
import CustomSimple from '@/components/CustomSimple.vue'; | ||
import { ref, inject, onBeforeMount} from 'vue' | ||
import { useShapeData } from '@/composables/shapedata'; | ||
import { useFormData } from '@/composables/formdata'; | ||
const simpledata = ref(null) | ||
const kaas = ref(false) | ||
const { | ||
formData, | ||
add_empty_node, | ||
add_empty_triple, | ||
} = useFormData() | ||
const shape_file_url = new URL("@/assets/shapesgraph.ttl", import.meta.url).href | ||
const { | ||
nodeShapes | ||
} = useShapeData(shape_file_url) | ||
const node_uid = 'https://concepts.datalad.org/s/prov/unreleased/Activity' | ||
const triple_uid = 'https://concepts.datalad.org/s/thing/unreleased/description' | ||
const shape_obj = nodeShapes.value[node_uid] | ||
onBeforeMount(() => { | ||
console.log("...BeforeMounet TestComp....") | ||
add_empty_node(node_uid) | ||
add_empty_triple(node_uid, triple_uid) | ||
}) | ||
function logStuff() { | ||
console.log(formData) | ||
console.log(formData[node_uid]) | ||
console.log(formData[node_uid].at(-1)[triple_uid].at(-1)) | ||
} | ||
</script> |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// composables/baseinput.js | ||
import { ref, watch, computed } from 'vue'; | ||
|
||
/** | ||
* Composable for managing the state and behavior of the BaseEditor component. | ||
* | ||
* @param {Object} props - The props passed to the custom input component. | ||
* @param {Function} emit - The emit function to trigger events. | ||
* @param {Function} valueParser - A function to parse the modelValue into subcomponent values. | ||
* @param {Function} valueCombiner - A function to combine subcomponent values into the modelValue. | ||
* @returns {Object} - Returns an object containing reactive references and methods for managing the input state. | ||
*/ | ||
|
||
export function useBaseInput(props, emit, valueParser, valueCombiner) { | ||
/** | ||
* Reactive object to hold the individual values of subcomponents. | ||
* @type {Object} | ||
*/ | ||
const subValues = ref(valueParser(props.modelValue) || {}); | ||
|
||
/** | ||
* Computed property to manage the internal value of the custom input component. | ||
* - The getter combines the subcomponent values into the modelValue. | ||
* - The setter parses the modelValue back into subcomponent values. | ||
*/ | ||
const internalValue = computed({ | ||
get() { | ||
return valueCombiner(subValues.value); | ||
}, | ||
set(value) { | ||
subValues.value = valueParser(value); | ||
} | ||
}); | ||
|
||
/** | ||
* Watcher for the parent component's modelValue. | ||
* Updates internal values when modelValue changes. | ||
*/ | ||
watch( | ||
() => props.modelValue, | ||
(newValue) => { | ||
if (newValue !== internalValue.value) { | ||
internalValue.value = newValue; | ||
} | ||
} | ||
); | ||
|
||
/** | ||
* Method to emit an update to the parent component when the subcomponent values change. | ||
*/ | ||
function updateValue() { | ||
emit('update:modelValue', internalValue.value); | ||
} | ||
|
||
return { | ||
subValues, | ||
internalValue, | ||
updateValue | ||
}; | ||
} |