I have a table which its rows creates by collection, like this plnkr -
example
and this code
#Component({
selector: 'my-app',
template: `
<table>
<tr *ngFor="let element of elements">
<td>{{element.name}}</td>
</tr>
</table>
`,
})
export class App {
elements:any;
constructor() {
this.elements = [
{name:"name1"},
{name:"name2"},
{name:"name3"},
{name:"name4"}
];
}
}
I want to add animation each time the order of the rows changes that show the row moves to the new place.
I tried to add index number as animation state but I don't know how to make the animation between them, how can I do it?
Related
I have 2 files one called clients.js with data and one called clients.vue and I would like to import the data and list it out in a table in the clients.vue file; but I can not access it when importing.
It's working fine if I move the array in to the clients.vue-file (so without import).
My code looks (simplified) like this:
Clients.vue
<template>
<div>
<table>
<tbody>
<tr v-for="client in clients">
<td>{{ client.ClientName }}</td>
<td>{{ client.ClientId }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import clients from "./data/clients.js";
export default {};
</script>
And my clients.js file:
export default {
data () {
return {
clients: [
{
ClientName: 'testname1',
ClientId: 1
},
{
ClientName: 'testname2',
ClientId: 2
}
]
}
}
}
I know probably have "activate" the imported array somehow, but i do not know how.
You would have to expose a property defined in your component's data object.
You have imported the clients but not injected it into your data of Clients.vue.
Change the Clients.vue like so.
<script>
import clients from "./data/clients.js";
export default {
...clients
};
This will inject the clients into Client.vue. Then you can use that in your template of Client.vue.
Alternative:
However this is not a good pattern in terms of readability.
It would be better to have a property in the component itself and then set the property in mounted hook.
You can do so in the following way:
<template>
<div>
<table>
<tbody>
<!-- use key when iterating -->
<tr v-for="client in clients" :key="client.ClientId">
<td>{{ client.ClientName }}</td>
<td>{{ client.ClientId }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import clients from "./data/clients.js";
export default {
clients: [] // initial value
},
mounted () { // setting clients in mounted
this.clients = { ...clients } // use Vue.set if this is a deep nested object
},
</script>
<style lang="scss" scoped></style>
Mixin way
If you intend to use clients.js as an mixin.
Then change your script to this:
<script>
import clients from "./data/clients.js";
export default {
mixins: [clients],
}
</script>
I am pretty new to VueJS 2, so wanted to see if I am working in the correct way. I have a system where someone uploads a file that contains data, which will then be used to create charts. So I display the uploaded files to them
<tr v-for="file in files.data" :key="file.id">
//file information
<td>
<router-link :to="{ name: file.chart, params: { fileName: file.name }}"
tag="a" exact> View Results
</router-link>
</td>
</tr>
So you can see I have a link in the table, that directs them to the chart page for the file they uploaded. It includes the params for the file name to be loaded.
On the chart page, I get the params within the created method. I then pass these to the component for the chart to be displayed
<template>
<div>
//some information
<div class="row">
<div class="col-12" id="parentDiv">
<barchart :file-name = this.fileName></barchart>
</div>
</div>
</div>
</template>
<script>
import Barchart from '../charts/Barchart';
export default {
components: {
'barchart': Barchart
},
data() {
return {
fileName: ''
}
},
created() {
this.fileName = this.$route.params.fileName;
}
}
</script>
Finally, I have the Barchart component. This is what creates the chart based on the file uploaded data.
<script>
import * as d3 from 'd3';
export default {
props: {
fileName: {
type: String,
required: true
}
},
methods: {
createBarChart() {
//d3 to create chart using the file that was uploaded
}
},
created() {
let vm = this;
d3.json('storage/' + this.fileName)
.then(function (data) {
vm.createBarChart(data);
}).catch(function (error) {
// handle error
});
}
};
</script>
To me, there seems to be a lot of passing of data from one component to the next. I pass it from the files display component (which displays all uploaded files), then to the page for the chart, which then passes it to the chart component.
The other issue is, if I am on the charts page, and I refresh the page, then the chart no longer has the filename prop and therefore the chart does not render. How would I handle this
situation?
Any advice appreciated
The reason that you are losing the chart on refresh is due to the use of the created method.
In your chart component remove the entire created method and reference the route param directly in your barchart reference, like so:
<template>
<div>
//some information
<div class="row">
<div class="col-12" id="parentDiv">
<barchart :file-name="$route.params.fileName"></barchart>
</div>
</div>
</div>
</template>
<script>
import Barchart from '../charts/Barchart';
export default {
components: {
'barchart': Barchart
},
data() {
return {
}
}
}
</script>
You may want to look into vuex to manage the data passing from parent to some deeply nested child.
Before you decide you want to persist the file in the nested component, you may want to consider if this is good UX (does it make sense that when the user refreshes the page, the old file they had uploaded is still cached?) You can look into using localStorage to store things locally so that upon refresh, the data is still there without needing the user to re-enter it.
I have multiple drop divs and so every time i drop the image of a component into those div i import the component corresponding to this image and i need to put it into the dom in the target div
drops[i].addEventListener('drop', function () {
if (this.draggedElement === null) {
return false
}
// get the component conf of this image
let conf = BlocksStore.conf[this.draggedElement.dataset.category].blocks[this.draggedElement.dataset.type]
// get the component itself (./blocks/SimpleTitle.vue)
let component = require('./blocks/' + conf.component)
drops[i].classList.remove('drag-enter')
// here is where i don't know what to do ...
drops[i].innerHTML = component
this.draggedElement = null
}.bind(this))
Here the code of ./blocks/SimpleTitle.vue
<template>
<tr>
<td align="center">
Lorem Ipsum
</td>
</tr>
</template>
<script>
export default {
name: 'simple-title'
}
</script>
<style>
</style>
I already tried to append the tag instead of the component but the dom don't comprehend it as a component
My example linked below shows a very large list of a grid of divs (1,000 x 20)
and when clicking on one div, it will highlight only that one element. However,
it seems there is significant overhead by VueJS in the rerendering which introduces a lag when clicking.
<div class="row" v-for="row in rows">
<div v-for="column in row.columns" :class="{red: isHighlighted(row,column)}" #click.prevent="setHighlighted({row: row.id, column: column.id})">
<div>Value: {{column['value']}}</div>
</div>
</div>
Code Pen Example
Sure, you can speed it up by not doing something that requires an evaluation on every update. In your case, the class setting has to call a function for every box every time row or column changes.
I added the style as a member of the column, so that on highlighting, it can be found directly, rather than requiring each cell to notice the change to highlighted. However, this still didn't remove the lag.
After working on this for a while, I surmised that the :class setting was being re-evaluated on every update, even if it was not a function call. My earlier solution handled class-setting explicitly, and that avoided the :class issue. This solution uses a component for each cell, which avoids re-calculation because components only re-evaluate when their props change.
const store = new Vuex.Store({
state: {
rows: [],
rowCount: 2000,
highlighted: {
row: null,
column: null
}
},
getters: {
rows(state) {
return state.rows;
},
rowCount(state) {
return state.rowCount;
},
highlighted(state) {
return state.highlighted;
}
},
mutations: {
setRows(state, rows) {
state.rows = rows;
},
setHighlighted(state, highlighted) {
state.highlighted = highlighted;
}
}
});
new Vue({
el: "#app",
store,
data() {
return {
highlightedEntry: null,
highlightedEl: null
};
},
created() {
this.setRows(
Array.from(Array(this.rowCount).keys()).map(i => {
return {
id: i,
columns: Array.from(Array(parseInt(20)).keys()).map(j => {
return {
id: j,
value: Math.random().toPrecision(4),
isHighlighted: false
};
})
};
})
);
},
computed: {
...Vuex.mapGetters(["rows", "rowCount", "highlighted"])
},
components: {
aCell: {
props: ['rowId', 'column'],
template: '<div :class="{red: column.isHighlighted}" #click="highlight">Value: {{column.value}}</div>',
computed: {
style() {
return this.column.style;
}
},
methods: {
highlight() {
this.$emit('highlight', this.rowId, this.column);
}
}
}
},
methods: {
...Vuex.mapMutations(["setRows", "setHighlighted"]),
highlight(rowId, column) {
if (this.highlightedEntry) {
this.highlightedEntry.isHighlighted = false;
}
this.highlightedEntry = column;
column.isHighlighted = true;
this.setHighlighted({
row: rowId,
column: column.id
});
}
}
});
.row {
display: flex;
justify-content: space-between;
}
.row>* {
border: 1px solid black;
}
.red {
background-color: red;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<div id="app">
<div>
Cells: {{rowCount * 20}}
</div>
<div class="row" v-for="row in rows" :key="row.id">
<div v-for="column in row.columns">
<a-cell :row-id="row.id" :column="column" #highlight="highlight"></a-cell>
</div>
</div>
</div>
If each of your items has a unique property like an id, pass that to :key="item.id":
<div v-for="column in row.columns" :key="column.id" ...
When Vue is updating a list of elements rendered with v-for, by default it uses an “in-place patch” strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will patch each element in-place and make sure it reflects what should be rendered at that particular index. This is similar to the behavior of track-by="$index" in Vue 1.x.
This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state (e.g. form input values).
To give Vue a hint so that it can track each node’s identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item. An ideal value for key would be the unique id of each item. This special attribute is a rough equivalent to track-by in 1.x, but it works like an attribute, so you need to use v-bind to bind it to dynamic values (using shorthand here):
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
It is recommended to provide a key with v-for whenever possible, unless the iterated DOM content is simple, or you are intentionally relying on the default behavior for performance gains.
Since it’s a generic mechanism for Vue to identify nodes, the key also has other uses that are not specifically tied to v-for, as we will see later in the guide.
https://v2.vuejs.org/v2/guide/list.html#key
I just don't understand, in list.vue I only trigger an list action that asynchronously alters state.list in updated hook. items is a computed property that only relies on state.list. Why would this lead to infinite updated events?
I've found that if I move the code in updated hook to watch option like this:
watch: {
'$route': function () {
this.$store.dispatch('list')
},
},
infinite problem would disappear. But this would trigger updated hook twice every time $route change is watched, which I also don't know Why.
Simple demo is here. Related code
// main.js
var Vue = require('vue')
var app = require('./App.vue')
new Vue(app).$mount('#app')
// App.vue
<template>
<div id="app">
<h1>list bug test</h1>
<router-view></router-view>
</div>
</template>
<script>
import store from './store.js'
import router from './router.js'
export default {
store,
router,
}
</script>
// list.vue
<template>
<table>
<tbody>
<tr>
<th>title</th>
<th>actions</th>
</tr>
<tr v-for="(item, index) in items">
<td> {{item.title}} </td>
<td> submit </td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
updated: function () {
console.log('items.vue updated')
this.$store.dispatch('list')
},
mounted: function () {
console.log('items.vue mounted')
this.$store.dispatch('list')
},
computed: {
items: function () {
return this.$store.state.list.map(e => ( {title: e.ksmc } ))
},
},
}
</script>
// router.js
var router = new VueRouter({
routes:[
{
path:'/',
name: 'list',
component: listView,
},
],
})
// store.js
var store = new Vuex.Store({
state: {
error: undefined,
list: JSON.parse(localStorage.getItem('list')) || [],
},
mutations: {
list: function(state, list) {
state.list = list
},
error: function(state, error) {
state.error = error
},
},
actions: {
list (ctx, kwargs) {
setTimeout(() => {
ctx.commit('list', [{ksmc:'this is a title'}])
}, 1000)
},
},
})
The updated hook is called after the component's DOM has been updated due to a change in the component's data model (to cause the component to be re-rendered). Therefore you shouldn't change the component's state (async or not) inside this hook otherwise the state change will cause the component to re-render which will fire the updated hook which will change the state... and so on.
The docs explain it well:
The component’s DOM will have been updated when this hook is called, so you can perform DOM-dependent operations here. However, in most cases you should avoid changing state inside the hook. To react to state changes, it’s usually better to use a computed property or watcher instead.