Skip to content
This repository was archived by the owner on Jan 1, 2025. It is now read-only.

[DRAFT] basic checkbox setup #31

Merged
merged 7 commits into from
Aug 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@
:search-text="searchText"
@onNodeExpanded="onNodeExpanded"
@onCheckboxToggle="onCheckboxToggle"
@onDataUpdated="onDataUpdated"
@onToggleParentCheckbox="onToggleParentCheckbox"
/>

<pre style="position:absolute;right:0;top:0;height:96vh;overflow:auto;"><code id="codeBlock"></code></pre>
</template>

<script>
import { ref } from 'vue'
import Tree from './lib/index'
import prettier from 'https://unpkg.com/prettier@2.3.2/esm/standalone.mjs'
import parserBabel from 'https://unpkg.com/prettier@2.3.2/esm/parser-babel.mjs'

export default {
components: {
Expand Down Expand Up @@ -68,20 +74,35 @@ export default {
const searchText = ref('')

const onNodeExpanded = (node, state) => {
console.log('state: ', state)
console.log('node: ', node)
// console.log('state: ', state)
// console.log('node: ', node)
}

const onCheckboxToggle = (node, state) => {
console.log('checkbox state: ', state)
console.log('checkbox node: ', node)
// console.log('checkbox state: ', state)
// console.log('checkbox node: ', node)
}

const onToggleParentCheckbox = (node, state) => {
// console.log('parent checkbox state: ', state)
// console.log('parent checkbox node: ', node)
}

const onDataUpdated = updatedData => {
document.querySelector('#codeBlock').innerText =
prettier.format(JSON.stringify(updatedData), {
parser: 'babel',
plugins: [parserBabel],
})
}

return {
data,
searchText,
onNodeExpanded,
onCheckboxToggle,
onToggleParentCheckbox,
onDataUpdated,
}
},
}
Expand Down
76 changes: 61 additions & 15 deletions src/lib/components/Tree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
<div>
<ul :style="{'gap': gap + 'px'}">
<tree-row
v-for="node in refNodes"
v-for="node in reactiveNodes"
:ref="'tree-row-' + node.id"
:key="node.id"
:node="node"
:indent-size="indentSize"
:gap="gap"
:expand-row-by-default="refExpandRowByDefault"
:expand-row-by-default="reactiveExpandRowByDefault"
@emitNodeExpanded="onNodeExpanded"
@emitOnUpdated="onDataUpdated"
@emitCheckboxToggle="onCheckboxToggle"
>
<template #iconActive>
Expand All @@ -24,9 +25,11 @@
</template>

<script>
import { ref, watch } from 'vue'
import { ref, watch, onMounted, nextTick } from 'vue'
import TreeRow from './TreeRow.vue'
import initData from '../composables/initData'
import useSearch from '../composables/useSearch'
import updateData from '../composables/updateData'

export default {
name: 'Tree',
Expand Down Expand Up @@ -68,38 +71,81 @@ export default {
default: true,
},
},
emits: ['onNodeExpanded', 'onCheckboxToggle'],
emits: ['onNodeExpanded', 'onCheckboxToggle', 'onDataUpdated'],
setup(props, { emit }) {
const { searchTree } = useSearch()
// TODO: the names below should be changed
const refNodes = ref(props.nodes)
const refExpandRowByDefault = ref(props.expandRowByDefault)
const reactiveNodes = ref(props.nodes)
const reactiveExpandRowByDefault = ref(props.expandRowByDefault)

onMounted(() => {
reactiveNodes.value = initData(reactiveNodes.value)
})


const nodeArray = (selector, parent=document) => {
return [].slice.call(parent.querySelectorAll(selector))
}

// checkboxes of interest

watch(() => props.searchText, () => {
if (props.searchText !== '') {
refNodes.value = searchTree(props.nodes, props.searchText, props.props)
reactiveNodes.value = searchTree(props.nodes, props.searchText, props.props)
if (props.expandAllRowsOnSearch) {
refExpandRowByDefault.value = true
reactiveExpandRowByDefault.value = true
}
} else {
refNodes.value = props.nodes
refExpandRowByDefault.value = false
reactiveNodes.value = props.nodes
reactiveExpandRowByDefault.value = false
}
})

const onNodeExpanded = (node, state) => {
emit('onNodeExpanded', node, state)
}

const onCheckboxToggle = (node, state)=>{
emit('onCheckboxToggle', node, state)
const onCheckboxToggle = (context, event) => {
reactiveNodes.value = updateData(reactiveNodes.value, context)

let check = event.target
const allThings = nodeArray('input')

if (allThings.indexOf(check) === -1) return

const children = nodeArray('input', check.parentNode.parentNode)
children.forEach(child => child.checked = check.checked)

while (check) {
const parent = (check.parentNode.parentNode.closest(['ul']).parentNode).querySelector('input')

if (!parent.parentNode.parentNode.querySelector(['ul'])) {
check = false
return
}
const siblings = nodeArray('input', parent.parentNode.parentNode.querySelector(['ul']))

const checkStatus = siblings.map(check => check.checked)
const every = checkStatus.every(Boolean)
const some = checkStatus.some(Boolean)

parent.checked = every
parent.indeterminate = !every && every !== some

check = check != parent ? parent : 0
}
emit('onCheckboxToggle', context)
}

const onDataUpdated = () => {
emit('onDataUpdated', props.nodes)
}

return {
onNodeExpanded,
refNodes,
refExpandRowByDefault,
reactiveNodes,
reactiveExpandRowByDefault,
onCheckboxToggle,
onDataUpdated,
}
},
}
Expand Down
59 changes: 39 additions & 20 deletions src/lib/components/TreeRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
>
<div
class="tree-row-item"
@click.stop="toggleExpanded(node)"
@click.stop="toggleExpanded(reactiveNode)"
>
<template v-if="node.nodes">
<template v-if="!expanded">
<template v-if="reactiveNode.nodes">
<template v-if="!reactiveNode.expanded">
<slot name="iconActive">
<arrow-right />
</slot>
Expand All @@ -22,18 +22,19 @@
</template>
<input
type="checkbox"
@input="toggleCheckbox(node)"
:checked="reactiveNode.checked"
@click.stop="toggleCheckbox(reactiveNode, $event)"
>
<span class="tree-row-txt">
{{ node.label }}
{{ reactiveNode.label }}
</span>
</div>
<ul
v-if="expanded"
v-if="reactiveNode.expanded"
:style="{'gap': gap + 'px'}"
>
<tree-row
v-for="child in node.nodes"
v-for="child in reactiveNode.nodes"
:ref="'tree-row-' + child.id"
:key="child.id"
:node="child"
Expand All @@ -42,6 +43,7 @@
:expand-row-by-default="expandRowByDefault"
:indent-size="indentSize"
@emitNodeExpanded="emitNodeExpanded"
@emitOnUpdated="emitOnUpdated"
@emitCheckboxToggle="emitCheckboxToggle"
>
<template #iconActive>
Expand All @@ -60,7 +62,7 @@
</template>

<script>
import { ref, nextTick, watch } from 'vue'
import { ref, nextTick, watch, onUpdated } from 'vue'
import ArrowRight from './Icons/ArrowRight.vue'
import ArrowDown from './Icons/ArrowDown.vue'

Expand Down Expand Up @@ -91,45 +93,62 @@ export default {
default: false,
},
},
emits: ['emitNodeExpanded', 'emitCheckboxToggle'],
emits: ['emitNodeExpanded', 'emitCheckboxToggle', 'emitOnUpdated'],
setup(props, { emit }) {
const expanded = ref(false)
const checked = ref(false)
const reactiveNode = ref(props.node)
const toggleExpanded = node => {
expanded.value = !expanded.value
reactiveNode.value.expanded = !reactiveNode.value.expanded
nextTick(() => {
emit('emitNodeExpanded', node, expanded.value)
emit('emitNodeExpanded', node, reactiveNode.value.expanded)
})
}

watch(() => props.expandRowByDefault, newVal => {
expanded.value = newVal
reactiveNode.value.expanded = newVal
}, {
immediate: true,
})

watch(() => props.node, newVal => {
reactiveNode.value.checked = newVal.checked
}, {
deep: true,
})

// redirect the event toward the Tree component
const emitNodeExpanded = (node, state) => {
emit('emitNodeExpanded', node, state)
}

const toggleCheckbox = node =>{
checked.value = !checked.value
const emitOnUpdated = (node, state) => {
emit('emitOnUpdated', node, state)
}

onUpdated(() => {
emitOnUpdated()
})

const toggleCheckbox = (node, event) => {
reactiveNode.value.checked = !reactiveNode.value.checked
nextTick(()=>{
emit('emitCheckboxToggle', node, checked.value)
emit('emitCheckboxToggle', {
id: node.id,
checked: node.checked,
}, event)
})
}

const emitCheckboxToggle = (node, state) => {
emit('emitCheckboxToggle', node, state)
const emitCheckboxToggle = (context, event) => {
emit('emitCheckboxToggle', context, event)
}

return {
expanded,
toggleExpanded,
emitNodeExpanded,
emitOnUpdated,
toggleCheckbox,
emitCheckboxToggle,
reactiveNode,
}
},
}
Expand Down
17 changes: 17 additions & 0 deletions src/lib/composables/initData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default function initData(data) {
const updateNodes = nodes => {
nodes.forEach(node => {
node.checked = false
node.expanded = false
node.indeterminate = false

if (Array.isArray(node.nodes)) {
updateNodes(node.nodes)
}
})

return nodes
}

return updateNodes(data)
}
33 changes: 33 additions & 0 deletions src/lib/composables/updateData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export default function updateData(data, context) {
const { id, checked } = context

const updateNodes = nodes => {
nodes.forEach(node => {
if (node.id === id) {
node.checked = checked

const handleRecursive = _nodes => {
_nodes.forEach(_node => {
_node.checked = node.checked ? true : false

if (Array.isArray(_node.nodes)) {
handleRecursive(_node.nodes)
}
})
}

if (node.nodes) {
handleRecursive(node.nodes)
}
}

if (Array.isArray(node.nodes)) {
updateNodes(node.nodes)
}
})

return nodes
}

return updateNodes(data)
}