I'm trying to keep a component alive when moving the bound item object to a different data array. Because it gets moved, the default keep-alive tag doesn't work.
I need this to improve loading time when dynamic components in my app use external libraries.
Simplified example: (https://jsfiddle.net/eywraw8t/24419/)
HTML:
<div id="app">
<div v-for="list in lists">
<h1>{{ list.title }}</h1>
<ul>
<draggable v-model="list.items" :options="{group: 'list-items'}">
<list-item
v-for="item in list.items"
:key="item.key"
:content="item.content">
</list-item>
</draggable>
</ul>
</div>
</div>
JS:
Vue.component('list-item', {
props: {
content: {
required: true
}
},
mounted () {
document.body.insertAdjacentHTML('beforeend', 'Mounted! ');
},
template: '<li>{{ content }}</li>'
})
new Vue({
el: "#app",
data: {
lists: [
{
title: 'List 1',
items: [
{ key: 'item1', content: 'Item 1' },
{ key: 'item2', content: 'Item 2' },
{ key: 'item3', content: 'Item 3' }
]
},
{
title: 'List 2',
items: [
{ key: 'item4', content: 'Item 4' },
{ key: 'item5', content: 'Item 5' },
{ key: 'item6', content: 'Item 6' }
]
}
]
}
})
If the problem is just one of caching expensive html build, you can do it by removing the list-item component from the template and building them ahead of time in app.mounted().
How well this works in your real-world scenario depends on the nature of item.content and it's lifecycle.
console.clear()
const ListItem = Vue.component('list-item', {
props: {
content: {
required: true
}
},
mounted () {
document.body.insertAdjacentHTML('beforeend', 'Mounted! ');
},
template: '<li>{{ content }}</li>'
})
new Vue({
el: "#app",
methods: {
getHtml(content) {
const li = new ListItem({propsData: {content}});
li.$mount()
return li.$el.outerHTML
}
},
mounted () {
this.lists.forEach(list => {
list.items.forEach(item => {
const cacheHtml = this.getHtml(item.content)
Vue.set( item, 'cacheHtml', cacheHtml )
})
})
},
data: {
lists: [
{
title: 'List 1',
items: [
{ key: 'item1', content: 'Item 1' },
{ key: 'item2', content: 'Item 2' },
{ key: 'item3', content: 'Item 3' }
]
},
{
title: 'List 2',
items: [
{ key: 'item4', content: 'Item 4' },
{ key: 'item5', content: 'Item 5' },
{ key: 'item6', content: 'Item 6' }
]
}
]
}
})
ul {
margin-bottom: 20px;
}
li:hover {
color: blue;
cursor: move;
}
h1 {
font-size: 20px;
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.6.0/Sortable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/15.0.0/vuedraggable.min.js"></script>
<div id="app">
<div v-for="list in lists">
<h1>{{ list.title }}</h1>
<ul>
<draggable v-model="list.items" :options="{group: 'list-items'}">
<div v-for="item in list.items" :key="item.key">
<li v-html="item.cacheHtml"></li>
</div>
</draggable>
</ul>
</div>
</div>
Reactive item.content
To keep reactivity when item.content changes, you will need a little more code.
add a copy of the item.content to the cache
add a method to fetch the cached html with refresh if content has changed.
(You may be able to do this a little more elegantly with a parameterized computed property).
To simulate an item.content change, I've added a setTimeout to mounted().
console.clear()
const ListItem = Vue.component('list-item', {
props: {
content: {
required: true
}
},
mounted () {
document.body.insertAdjacentHTML('beforeend', 'Mounted! ');
},
template: '<li>{{ content }}</li>'
})
new Vue({
el: "#app",
methods: {
getHtml(content) {
const li = new ListItem({
propsData: { content }
});
li.$mount()
return li.$el.outerHTML
},
cacheHtml(item) {
if (item.cache && item.cache.content === item.content) {
return item.cache.html
} else {
const html = this.getHtml(item.content)
const cache = {content: item.content, html}
Vue.set(item, 'cache', cache)
}
}
},
mounted () {
this.lists.forEach(list => {
list.items.forEach(item => {
this.cacheHtml(item)
})
})
setTimeout(() =>
Vue.set( this.lists[0].items[0], 'content', 'changed' )
,2000)
},
data: {
lists: [
{
title: 'List 1',
items: [
{ key: 'item1', content: 'Item 1' },
{ key: 'item2', content: 'Item 2' },
{ key: 'item3', content: 'Item 3' }
]
},
{
title: 'List 2',
items: [
{ key: 'item4', content: 'Item 4' },
{ key: 'item5', content: 'Item 5' },
{ key: 'item6', content: 'Item 6' }
]
}
]
}
})
ul {
margin-bottom: 20px;
}
li:hover {
color: blue;
cursor: move;
}
h1 {
font-size: 20px;
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.6.0/Sortable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/15.0.0/vuedraggable.min.js"></script>
<div id="app">
<div v-for="list in lists">
<h1>{{ list.title }}</h1>
<ul>
<draggable v-model="list.items" :options="{group: 'list-items'}">
<div v-for="item in list.items" :key="item.key">
<li v-html="cacheHtml(item)"></li>
</div>
</draggable>
</ul>
</div>
</div>
i looked into your problem and i think i might found a solution, i cannot do it in js fiddle but i'll try and explain it:
in your js fiddle the mounted is hooked in your list-item component, so indeed every time that state changed (when dragging), the event is triggered.
i create a setup with a main templated component (componentX), with a mounted function, and then created a seperated list-item component
in my sample you will see the mounted twice at the start, that is normal since we have 2 lists! but then when you start to drag and drop you will not get additional mounted events
you can download the solution in a zip from:
http://www.bc3.eu/download/test-vue.zip
it is a vue cli project, so you can just npm run dev to start a local server
Related
I am using the Nuxt Composition API and trying to render the elements in getList to my page. When I load the page I get an error saying that "_vm.getList is not a function".
<template>
<div class="drop-zone">
<div v-for="item in getList(1)" :key="item.id" class="drag-el">
{{item.title}}
</div>
</div>
</template>
<script>
export default{
setup(){
const items = ref([
{id:0, title: 'Item A', list:1},
{id:1, title: 'Item B', list:1},
{id:2, title: 'Item C', list:2},
]),
const getList = (list) => {
return items.value.filter((item) => item.list == list);
},
return {
getList,
}
},
}
</script>
You need to import ref (not sure how), then this one should work
<template>
<div class="drop-zone">
<div v-for="item in getList(1)" :key="item.id" class="drag-el">
{{ item.title }}
</div>
</div>
</template>
<script>
export default {
setup() {
const items = ref([
{ id: 0, title: 'Item A', list: 1 },
{ id: 1, title: 'Item B', list: 1 },
{ id: 2, title: 'Item C', list: 2 },
])
const getList = (list) => {
return items.value.filter((item) => item.list === list)
}
return {
getList,
}
},
}
</script>
At least, ESlint is not complaining with this. Sorry if I can't help more, didn't worked with Vue3 so far (yet).
I am trying to build a chart using organisation module of highcharts in angular.
Currently, I am creating it by using static data but it should be created based on api result.
Also i want whenever any node is clicked than an active class should be applied to this and its child. I have used click event for this and class is being applied but css is not reflecting.
below is my line of code can you please suggest
comp.ts
export class OrganisationComponent implements OnInit {
public options: any = {
chart: {
height: 600,
inverted: true
},
title: {
text: 'Org Chart'
},
series: [{
type: 'organization',
name: 'Highsoft',
keys: ['from', 'to'],
cursor: 'pointer',
events: {
click: function (event) {
event.point.linksFrom.forEach(element => {
if(element.toNode){
if(element.toNode.linksFrom){
element.toNode.linksFrom.forEach(innerElement => {
innerElement.toNode.dataLabel.addClass('active');
})
}
}
element.toNode.dataLabel.addClass('active');
});
}
},
data: [
['PMO', 'TM'],
['TM', 'D1'],
['TM', 'D2'],
['TM', 'D3'],
['D1', 'Intern'],
],
nodes: [
{
id: 'PMO',
icon: 'account_circle',
name: 'Project Manager'
},
{
id: 'TM',
icon: 'account_circle',
name: 'Team Lead'
},
{
id: 'D1',
icon: 'account_circle',
name: 'Developer 1'
},
{
id: 'D2',
icon: 'account_circle',
name: 'Developer 2'
},
{
id: 'D3',
icon: 'account_circle',
name: 'Developer 3'
},
{
id: 'Intern',
icon: 'account_circle',
name: 'Intern'
},
],
dataLabels: {
nodeFormat : `{point.name}`
}
}],
};
constructor() {}
ngOnInit() {
Highcharts.chart('container', this.options); // organization
}
}
.html
.css
.active {
font-size: 22px;
color: red;
}
stackblitz link
https://stackblitz.com/edit/angular-bar-highcharts-bnom1n
I am stuck how can i make it work with api data
how to apply active class on all child nodes and clicked node
Try this
Add styles in style.css as per other answer for css , for n level highlight try this recursive approach.
// update your click event as per below
events: {
click: function (event) {
event.point.linksFrom.forEach(element => {
highlightPoints(element);
element.toNode.dataLabel.addClass('active');
});
function highlightPoints(element){
if(element.toNode){
if(element.toNode.linksFrom){
element.toNode.linksFrom.forEach(innerElement => {
highlightPoints(innerElement);
innerElement.toNode.dataLabel.addClass('active');
})
}
}
}
}
To active child nodes by clicking on parent, do the following:
add .active style in style.css
.active {
font-size: 30px;
background-color: red;
}
Here is the updated stackblitz demo
Hope this helps.
Thanks
I have this fiddle:
https://jsfiddle.net/pnqzspoe/12014/
I want to modify it a bit and want to display each node as a text area containing the corresponding text. Further, I want to give an option to 'reply' to it. This would mean insertion of a new text area into which we can enter text.
Here is the code:
<script type="text/x-template" id="item-template">
<li>
<div
:class="{bold: isFolder}"
#click="toggle"
#dblclick="changeType">
{{ model.name }}
<span v-if="isFolder">[{{ open ? '-' : '+' }}]</span>
</div>
<ul v-show="open" v-if="isFolder">
<item
class="item"
v-for="(model, index) in model.children"
:key="index"
:model="model">
</item>
<li class="add" #click="addChild">+</li>
</ul>
</li>
</script>
<p>(You can double click on an item to turn it into a folder.)</p>
var data = {
name: 'My Tree',
children: [
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
},
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
]
}
]
}
// define the item component
Vue.component('item', {
template: '#item-template',
props: {
model: Object
},
data: function () {
return {
open: false
}
},
computed: {
isFolder: function () {
return this.model.children &&
this.model.children.length
}
},
methods: {
toggle: function () {
if (this.isFolder) {
this.open = !this.open
}
},
changeType: function () {
if (!this.isFolder) {
Vue.set(this.model, 'children', [])
this.addChild()
this.open = true
}
},
addChild: function () {
this.model.children.push({
name: 'new stuff'
})
}
}
})
// boot up the demo
var demo = new Vue({
el: '#demo',
data: {
treeData: data
}
})
What would be the template for this use-case?
If I don't understand your question wrongly...
Replace
{{model.name}}
with
<textarea v-model="model.name"></textarea>
should work?
Hello I have this code in my symfony 3 project :
TWIG TEMPLATE:
<div id="fileManagerContainer" class="AppContent">
{% verbatim %}
<!-- item template -->
<script type="text/x-template" id="item-template">
<li>
<div
:class="{bold: isFolder}"
#click="toggle"
#dblclick="changeType">
{{model.name}}
<span v-if="isFolder">{{open ? '-' : '+'}}</span>
</div>
<ul v-show="open" v-if="isFolder">
<item
class="item"
v-for="model in model.children"
:model="model">
</item>
<li class="add" #click="addChild">+</li>
</ul>
</li>
</script>
{% endverbatim %}
<p>(You can double click on an item to turn it into a folder.)</p>
<!-- the demo root element -->
<ul id="demo">
<item
class="item"
:model="treeData">
</item>
</ul>
</div>
VUE FILE :
// demo data
var data = {
name: 'My Tree',
children: [
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
},
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
]
}
]
}
// define the item component
Vue.component('item', {
template: '#item-template',
props: {
model: Object
},
data: function () {
return {
open: false
}
},
computed: {
isFolder: function () {
return this.model.children &&
this.model.children.length
}
},
methods: {
toggle: function () {
if (this.isFolder) {
this.open = !this.open
}
},
changeType: function () {
if (!this.isFolder) {
Vue.set(this.model, 'children', [])
this.addChild()
this.open = true
}
},
addChild: function () {
this.model.children.push({
name: 'new stuff'
})
}
}
})
// boot up the demo
var demo = new Vue({
delimiters: ['{{', '}}'],
el: '#demo',
data: {
treeData: data
}
})
ant it works on jsfiddle, but doesnt do a thing in real project. All scripts are loaded perfectly, Vue.js works but just this piece of code does not. Any ideas ?
Learning JS and trying to figure out the tree view from Vue.js.
The example is on the Vue site here: Tree view on Vue site
What I have done is created a html doc that has the HTML code as per JSFiddle:
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Menlo, Consolas, monospace;
color: #444;
}
.item {
cursor: pointer;
}
.bold {
font-weight: bold;
}
ul {
padding-left: 1em;
line-height: 1.5em;
list-style-type: dot;
}
</style>
</head>
<body>
<script type="text/x-template" id="template">
<div v-class="bold: isFolder"
v-on="click: toggle, dblclick: changeType">
{{model.name}}
<span v-if="isFolder">[{{open ? '-' : '+'}}]</span>
</div>
<ul v-show="open" v-if="isFolder">
<li class="item"
v-repeat="model: model.children"
v-component="item">
</li>
<li v-on="click: addChild">+</li>
</ul>
</script>
<script src="JS/app.js"></script>
<script src="JS/vue.min.js"></script>
<p>(You can double click on an item to turn it into a folder.)</p>
<!-- the demo root element -->
<ul id="demo">
<li class="item"
v-component="item"
v-with="model: treeData">
</li>
</ul>
</body>
</html>
Then I added the Javascript to a separate app.js file and put it in a folder in same dir as html file called JS.
I have also put vue.min.js in that folder but the code doesn't work at all.
It appears that the script is just not running as the CSS and everything else displays OK.
I'm probably making a fairly elementary mistake here in terms of pointing towards correct js files or leaving something out but the syntax hasn't been changed from the working online demo so I doubt it's that.
JS:
// demo data
var data = {
name: 'My Tree',
children: [
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
},
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
]
}
]
}
// define the item component
Vue.component('item', {
template: '#template',
data: function () {
return {
open: false
}
},
computed: {
isFolder: function () {
return this.model.children &&
this.model.children.length
}
},
methods: {
toggle: function () {
if (this.isFolder) {
this.open = !this.open
}
},
changeType: function () {
if (!this.isFolder) {
this.model.$add('children', [])
this.addChild()
this.open = true
}
},
addChild: function () {
this.model.children.push({
name: 'new stuff'
})
}
}
})
// boot up the demo
var demo = new Vue({
el: '#demo',
data: {
treeData: data
}
})
If anyone has any ideas of what I am doing incorrectly please let me know.
Issue present on all browsers(Safari, Firefox, Chrome) -> I am fairly certain this is a high level issue as the JSFiddle page and example page linked above both display properly and I literally just copy+pasted the code into the html and js files in addition to downloading and referencing vue.min.js
All help and suggestions welcome!
M
Edit:
After Orland's answer below I included all the code in one file as below:
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Menlo, Consolas, monospace;
color: #444;
}
.item {
cursor: pointer;
}
.bold {
font-weight: bold;
}
ul {
padding-left: 1em;
line-height: 1.5em;
list-style-type: dot;
}
</style>
</head>
<body>
<script src="https://cdn.rawgit.com/yyx990803/vue/master/dist/vue.min.js"></script>
<!-- item template -->
<script type="text/x-template" id="item-template">
<div v-class="bold: isFolder"
v-on="click: toggle, dblclick: changeType">
{{model.name}}
<span v-if="isFolder">[{{open ? '-' : '+'}}]</span>
</div>
<ul v-show="open" v-if="isFolder">
<li class="item"
v-repeat="model: model.children"
v-component="item">
</li>
<li v-on="click: addChild">+</li>
</ul>
</script>
<p>(You can double click on an item to turn it into a folder.)</p>
<!-- the demo root element -->
<ul id="demo">
<item model="{{ treeData }}"></item>
</ul>
<script>
// demo data
var data = {
name: 'My Tree',
children: [
{ name: 'wat' },
{
name: 'child folder',
children: [
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
},
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
]
}
]
}
// define the item component
Vue.component('item', {
template: '#item-template',
props: ['model'],
data: function () {
return {
open: false,
model: {}
}
},
computed: {
isFolder: function () {
return this.model.children &&
this.model.children.length
}
},
methods: {
toggle: function () {
if (this.isFolder) {
this.open = !this.open
}
},
changeType: function () {
if (!this.isFolder) {
this.model.$add('children', [])
this.addChild()
this.open = true
}
},
addChild: function () {
this.model.children.push({
name: 'new stuff'
})
}
}
})
// boot up the demo
var demo = new Vue({
el: '#demo',
data: {
treeData: data
}
})
</script>
</body>
</html>
This works perfectly but I am developing an App that will be run mostly offline so I tried changing the vue.min.js source to the local vue.min.js I have and it stops working!! The change I make is:
From <script src="https://cdn.rawgit.com/yyx990803/vue/master/dist/vue.min.js"></script>
to <script src="JS/vue.min.js"></script>
Cannot understand this but assume it is something I am doing in locating the vue.min.js!!!???
It seems that even the original snippet on Vue JS site is not working. I updated the snippet to make it work.
// demo data
var data = {
name: 'My Tree',
children: [
{ name: 'wat' },
{
name: 'child folder',
children: [
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
},
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
]
}
]
}
// define the item component
Vue.component('item', {
template: '#item-template',
props: ['model'],
data: function () {
return {
open: false,
model: {}
}
},
computed: {
isFolder: function () {
return this.model.children &&
this.model.children.length
}
},
methods: {
toggle: function () {
if (this.isFolder) {
this.open = !this.open
}
},
changeType: function () {
if (!this.isFolder) {
this.model.$add('children', [])
this.addChild()
this.open = true
}
},
addChild: function () {
this.model.children.push({
name: 'new stuff'
})
}
}
})
// boot up the demo
var demo = new Vue({
el: '#demo',
data: {
treeData: data
}
})
body {
font-family: Menlo, Consolas, monospace;
color: #444;
}
.item {
cursor: pointer;
}
.bold {
font-weight: bold;
}
ul {
padding-left: 1em;
line-height: 1.5em;
list-style-type: dot;
}
<script src="https://cdn.rawgit.com/yyx990803/vue/master/dist/vue.min.js"></script>
<!-- item template -->
<script type="text/x-template" id="item-template">
<div v-class="bold: isFolder"
v-on="click: toggle, dblclick: changeType">
{{model.name}}
<span v-if="isFolder">[{{open ? '-' : '+'}}]</span>
</div>
<ul v-show="open" v-if="isFolder">
<li class="item"
v-repeat="model: model.children"
v-component="item">
</li>
<li v-on="click: addChild">+</li>
</ul>
</script>
<p>(You can double click on an item to turn it into a folder.)</p>
<!-- the demo root element -->
<ul id="demo">
<item model="{{ treeData }}"></item>
</ul>