How to create dynamic tree structure in svelte? - html

I need a treeview in svelte that has checkboxes with each node.
I am unable to get functionality that if we check a parent node then all of its (only its) children whether they are file of folders, all of them (child) get checked automatically and any of them is unchecked later then the parent should also get unchecked. How can I achieve this?
Below is the code that I followed from here. I tried few things but no luck.
App.svelte-
<script>
import Folder from './Folder.svelte';
let root = [
{
name: 'Important work stuff',
files: [
{ name: 'quarterly-results.xlsx' }
]
},
{
name: 'Animal GIFs',
files: [
{
name: 'Dogs',
files: [
{ name: 'treadmill.gif' },
{ name: 'rope-jumping.gif' }
]
},
{
name: 'Goats',
files: [
{ name: 'parkour.gif' },
{ name: 'rampage.gif' }
]
},
{ name: 'cat-roomba.gif' },
{ name: 'duck-shuffle.gif' },
{ name: 'monkey-on-a-pig.gif' }
]
},
{ name: 'TODO.md' }
];
</script>
<Folder name="Home" files={root} expanded/>
File.svelte-
<script>
export let name;
$: type = name.slice(name.lastIndexOf('.') + 1);
</script>
<span style="background-image: url(tutorial/icons/{type}.svg)">{name}</span>
<style>
span {
padding: 0 0 0 1.5em;
background: 0 0.1em no-repeat;
background-size: 1em 1em;
}
</style>
Folder.svelte-
<script>
import File from './File.svelte';
export let expanded = false;
export let name;
export let files;
let checkedState = true;
function toggle() {
expanded = !expanded;
}
function onCheckboxChanged(e){
console.log("out: "+document.getElementById("cb1").checked)
}
</script>
<input type="checkbox" on:change={onCheckboxChanged} id="cb1" bind:checked={checkedState}><span class:expanded on:click={toggle}>{name}</span>
{#if expanded}
<ul>
{#each files as file}
<li>
{#if file.files}
<svelte:self {...file}/>
{:else}
<input type="checkbox" on:change={onCheckboxChanged} id="cb1" bind:checked={checkedState}><File {...file}/>
{/if}
</li>
{/each}
</ul>
{/if}
<style>
span {
padding: 0 0 0 1.5em;
background: url(tutorial/icons/folder.svg) 0 0.1em no-repeat;
background-size: 1em 1em;
font-weight: bold;
cursor: pointer;
}
.expanded {
background-image: url(tutorial/icons/folder-open.svg);
}
ul {
padding: 0.2em 0 0 0.5em;
margin: 0 0 0 0.5em;
list-style: none;
border-left: 1px solid #eee;
}
li {
padding: 0.2em 0;
}
</style>

Remove binding in checkbox next to File component. This will prevent from case when you click File's checkbox and whole folder get checked. Instead you need just to pass state to child File components when Folder gets checked. So change code like this:
<!-- <input type="checkbox" bind:checked={checkedState}> -->
<input type="checkbox" checked={checkedState}>
Then you need to pass Folder's checked state to all child Folder components (so when you check folder, folders inside would be checked too). You can do it by making checkedState a prop, not just regular variable, then just pass this prop when calling svelte:self component
<script>
// let checkedState = true;
export let checkedState = true;
</script>
<!-- <svelte:self {...file}/> -->
<svelte:self {...file} {checkedState} />
After that only one problem case left. When all child components are (un)checked parent should be (un)checked too. I can't really see possibility to implement this thing with current structure. I would recommend you to move checkbox inside File component and then create field checked or selected in file object (in App.svelte) so you could easily manage all states in any child component. It also would be easier to read this values when extracting data from root Folder component (e.g. if you will need to send this data to server). After adding prop you of course should work with object properties, not component's state. Good luck with it!
P.S: Don't use static ids in your components and even more so in each loops. This will make your output html invalid.

I came across with this question, when I tried to build the same TreeView component you're trying to build.
And I'm implemented the dynamic feature you want with compnent event.
Here is the demo, which also use svelte:self and inspired by this REPL
The main points in this approach are:
The child component do not have the responsibility to compute the tree's state, it only have to represent it.
Let the parent component do the entire tree state computation, when the children node's state have changed.

Related

Stencil scoped styles nested

I'm not sure if this is possible but I don't know how to affect the scoped styles of a component inside another component.
We have the following components:
#Component({
tag: "gov-button",
styleUrl: "gov-button.scss",
shadow: false,
scoped: true
})
export class GovButton {
render() {
return (
<button class="element">
<slot name="left-icon"></slot>
<slot />
<slot name="right-icon"> </slot>
</button>
)
}
}
button.element {
slot::slotted(gov-icon) {
font-size: 3rem;
}
}
#Component({
tag: "gov-icon",
styleUrl: "gov-icon.scss",
shadow: false,
scoped: true
})
export class GovIcon {
render() {
return (
<span aria-hidden="true" class={this.name}></span>
)
}
}
span {
font-size: 1rem;
}
S následujícím použití
<gov-button variant="primary" size="small">
<gov-icon slot="left-icon" name="lightbulb"></gov-icon>
Small Primary
<gov-icon slot="right-icon" name="question"></gov-icon>
</gov-button>
I would like to affect the appearance of the gov-icon component in the gov-button.scss stylesheet, which is inserted into the gov-button component via a slot.
Unfortunately, with no selector I am not able to affect its appearance and I am not sure if it is even possible.
Thank you for help
I think this is because technically there are no slots in the Light DOM and Stencil only "emulates" them when scoped is on. Scoped CSS in Stencil means adding CSS classes based on component name (sc-gov-button and sc-gov-icon in your case) to your markup and modifying CSS selectors accordingly so they are "scoped" to these classes. This is why this doesn't work:
button.element {
slot::slotted(gov-icon) {
font-size: 3rem;
}
}
As a workaround, you can use the scoped icon selector in your button CSS instead:
.sc-gov-icon {
font-size: 3rem;
}
Of course it has its drawbacks such as that you need to update them when you change component names.
Take a look how they use scoped in Ionic - they use it only for a few components where they don't want Shadow DOM for performance reasons.

How do I add an css background image in a Vue file?

I want to replace the navigation drawer with an image I have (./src/assets/image.jpg)
<v-navigation-drawer
mini-variant
dark
app
permanent
class="withBackground"
>
...
<style scoped>
.withBackground {
background: rgba(-1, 0, 0, 0);
background-image: // what here?;
}
</style>
I have no idea what to set to background-image. Should I load the image via import and attach it to a data field and then place it there?
You should use the src attribute.
You could do:
<v-navigation-drawer
mini-variant
dark
app
permanent
src="./src/assets/image.jpg"
>
Or if the image source is a variable something like this:
<template>
<v-navigation-drawer
mini-variant
dark
app
permanent
:src="imageSrc"
/>
</template>
<script>
export default {
data() {
return {
imageSrc: null
};
},
mounted() {
this.imageSrc = require('./src/assets/image.jpg')
},
};
</script>
For more info:
https://next.vuetifyjs.com/en/components/navigation-drawers/#navigation-drawer

How can I move a value of variable html to a file/child component vue?

My code is works. The code like this :
<template>
...
</template>
<script>
export default {
methods: {
setFooter () {
if (!this.footer) {
const div = document.createElement('div')
let html
html = "\
<span>\
<div style='float:left; height: 14px; width: 14px; border-radius: 12px;'>\
</div>\
</span>\
<span style='float: left;font-size:12px'>Available</span>\
<span>\
<div style='float:left;height: 14px; width: 14px; border-radius: 12px;'>\
</div>\
</span>\
<span style='float:left; font-size: 12px'>Not available</span>"
div.innerHTML = html
document.querySelector('.v-date-picker-table').append(div)
this.footer = true
}
},
}
}
</script>
setFooter method called if an asynchronous/Axios done
My problem is that my code looks sloppy and unreadable. Because I store the HTML tag inside the HTML variable. So I want to move it into a file/component Vue. Maybe I should call a child component of a method
How can I do it?
The existing implementation looks fine. The primary thing you are trying to achieve is to move the unreadable HTML code to a separate component and mount dynamically to the current component when the date picker is opened.
It won't work in that way.The Child component cannot be mounted into a parent datepicker component. Because,
document.querySelector('.v-date-picker-table').append(div)
The old above logic appends HTML string to a div element, datepicker component is dynamically created on the fly, you won't have control to append another Vue component
Instead, you can store HTML string to a separate js file to make your code readable
// create a js file names utility.js(any file name to your convenient)
'use strict'
let getFooterDiv = function() {
var div = document.createElement('div');
var html = "<span><div style='float:left;height: 20px; width: 20px; border-radius: 10px; background-color: green;'></div></span><span style='margin-left: 10px; float: left;'>Available</span><span><div style='float:left;height: 20px; width: 20px; border-radius: 10px; background-color: black; margin-left:10px;'></div></span><span style='margin-left: 10px; float:left;'>Unavailable</span>";
div.innerHTML = html;
return div;
};
module.exports = {
footerDiv: getFooterDiv,
}
in the above component you can import this js function and use it
import { footerDiv } from utility;
.....
setFooter() {
if (!this.footer) {
var footer = footerDiv();
document.querySelector('.v-date-picker-table').append(footer);
this.footer = true;
}
},
Now the code looks readable, and html generation is moved to a separate function instead of separate component
How I've read OP's question...
When some async call completed, we need to put some HTML into some external DOM element which is not under control of the current VueJS app.
I would suggest using Portal for VueJS 3 or some 3rd Party lib providing similar functionality.
Using Portal you can inject active VueJS component into foreign DOM element. Also, you will be able to import a shared VueX store or some other state storage and react to the completion of async call in parallel with the main orchestrator component of the VueJS app.
I hope it'll do the job...
<!-- footer.vue -->
<template>
<div>
footer code
</div>
</template>
<script>
export default {
props:["myvalue"]
}
</script>
<!-- main.vue -->
<template>
<div>
<footer :myvalue="myvalue"></footer>
</div>
</template>
<script>
import footer from "footer.vue"
export default {
components:{footer},
data(){
return {
myvalue:"data"
}
}
}

Ckeditor allowedContent for html tag

I am using Ckeditor as rich editor for text input in the Chrome browser. I also have added some html id tag for easy parsing by bs4 after the system getting the data.
The following is my setting in the html:
CKEDITOR.replace( 'editor', {
toolbar : 'Basic',
uiColor : '#9AB8F3',
height : '70%',
startupShowBorders: false,
})
And in the config.js:
config.toolbarGroups = [
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
{ name: 'links' },
{ name: 'insert' },
{ name: 'forms' },
{ name: 'tools' },
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
{ name: 'others' },
'/',
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
{ name: 'styles' },
{ name: 'colors' },
{ name: 'about' }
];
// Remove some buttons provided by the standard plugins, which are
// not needed in the Standard(s) toolbar.
config.removeButtons = 'Underline,Subscript,Superscript';
// Set the most common block elements.
config.format_tags = 'p;h1;h2;h3;pre';
// Simplify the dialog windows.
config.removeDialogTabs = 'image:advanced;link:advanced';
config.allowedContent = True;
};
Although I have already followed the instruction to allow all html tag content to be preserved with config.allowedContent = *; in the config.jd. However, it seems not working as I got the following results when getting data (by CKEDITOR.instances.editor.getData()):
<span style='font-size:11.0pt;'> content </span>
instead of this that I want:
<span id="news_content" style='font-size:11.0pt;'> content </span>
In other words, it still strips out all the html tag I added.
When I checked the source code, I found that the same textarea content was produced twice with the one with the tag being put in hidden format, i.e.,
<textarea name="editor" id="editor" rows="100" cols="40" style="visibility: hidden; display: none;">
And the editor produces another version in the real textarea that allows me to edit. However, this is useless because all the html tags are stripped there.
So, my question is, how to preserve the html tag in the real textarea so that I can parse the html with id tags after editing and submission. Could anyone advise on this? Thanks a lot.
I may not be able to answer my own question, but I like to share my solution with those encountering similar situation.
In short, finally I give up using ckeditor or any plug-in editor as many of them will strip off the html tag, which is essential to me in the subsequent process.
Thanks to html5, my solution is using editable div. The setting is very simple as below:
css:
#my-content {
box-shadow: 0 0 2px #CCC;
min-height: 150px;
overflow: auto;
padding: 1em;
margin: 5px;
resize: vertical;
outline: none;
}
html:
<div id="my-content" class="editable" style="width:900px; height:400px; text-overflow: ellipsis; overflow-y: scroll;"></div>
js script:
$('.editable').each(function(){
this.contentEditable = true;
});
So far, I am happy with it, it shows what exactly the html code showing and preserve all the tags I added. The only downside is it does not provide any toolbar for format editing. My solution is to make one for it, and via the following link you can get a very good tutorial as to making a toolbar with a ready-to-use demo as illustration.
https://code.tutsplus.com/tutorials/create-a-wysiwyg-editor-with-the-contenteditable-attribute--cms-25657
Hope this helps.

Struggling to understand the subtlety of relectToAttribute

I am struggling to understand the subtlety of reflectToAttribute on a Polymer elements property.
I have an pair of elements for transmitting values around the dom tree like iron-meta which I have called akc-meta and akc-meta-query. In my test fixture I am doing this
<test-fixture id="basic-test">
<template>
<template is="dom-bind" id=app>
<akc-meta key="[[key1]]" value="{{value1}}" id="meta1"></akc-meta>
<akc-meta-query key="[[key2]]" value="{{value2}}" id="meta2"></akc-meta-query>
<akc-meta-query key="[[key3]]" value="{{value3}}" id="meta3"></akc-meta-query>
<akc-meta key="[[key4]]" value="{{value4}}" id="meta4"></akc-meta>
</template>
</template>
</test-fixture>
and in my test suite I can set values like this
app.key1 = 'keya';
app.key2 = 'keya';
app.key3 = 'keya';
app.value1 = 'This is a multiple query test';
expect(app.value2).to.equal('This is a multiple query test');
expect(app.value3).to.equal('This is a multiple query test');
app.value1 = 'New Value';
expect(app.value2).to.equal('New Value');
expect(app.value3).to.equal('New Value');
where these elements transmit values under the hood between the elements when the keys are the same.
Neither of the elements use reflectToAttribute on any of the properties, although the value property of akc-meta-query does use notify:true
So what does reflectToAttribute actually do and why do you need it?
I have created a small example in this jsbin.
<style>
x-test {
display: block;
}
x-test[prop1="initialvalue1"] {
border: 5px solid yellow;
}
x-test[prop2="initialvalue2"] {
background: green;
}
x-test[prop1="newvalue1"] {
border: 5px solid black;
}
x-test[prop2="newvalue2"] {
background: red;
}
</style>
<dom-module id="x-test">
<template>
<div>{{prop1}}</div>
<div>{{prop2}}</div>
<button on-click="update1">Update Prop1</button>
<button on-click="update2">Update Prop2</button>
</template>
</dom-module>
<script>
HTMLImports.whenReady(function() {
Polymer({
is: 'x-test',
properties:{
prop1: {
type:String
},
prop2: {
type:String,
reflectToAttribute: true
},
},
update1: function(){
this.prop1 = "newvalue1";
},
update2: function(){
this.prop2 = "newvalue2";
}
});
});
</script>
<x-test prop1="initialvalue1" prop2="initialvalue2"></x-test>
The element here has two properties. prop1 is not reflected to the attribute, but prop2 is. Both are set to an initial value. If you open the developer tools, you will see something like this:
There is an update button to change either prop1 or prop2. If you click each button and update both properties, you will get this picture
As you can see, prop1 still has its old state. The state change has not been reflected back to the attribute. In contrast, prop2 has been reflected and has changed.
Now if you access either property through JavaScript, you will get the updated value. However, when you access the HTML attribute, in case of prop1 you will not.
A use case for this could be when you have CSS rules with attribute selectors as in my example. The selector x-test[prop1="newvalue1"] does not apply when prop1 is updated. On the other hand x-test[prop2="newvalue2"] does apply after the update of prop2, because it is configured to reflect to attribute.