I use Ractive to display data from a constantly changing array (received from PouchDB) with the magic: trueparameter. That worked fine until in Version 0.9.x this parameter was no longer available.
So it is advised to use ractive.update()every time my array changes. The problem I'm facing seems quite simple, but I'm fairly inexperienced:
For tracking the changes I do roughly the following (taken from a PouchDB script):
fetchInitialDocs().then(renderDocsSomehow).then(reactToChanges).catch(console.log.bind(console));
function renderDocsSomehow() {
let myRactive = new Ractive({
el: '#myDiv',
template: 'my Template',
//magic: true,
data:{
doc1: docs
},
components: {myComponents},
computed: {computedValues}
});
}
function reactToChanges() {
//here my changes appear
onUpdatedOrInserted(change.doc);
myRactive.update() // <- I cannot use update here because "myRactive" is not known at this point.
}
What I tried also was setting an observer in the renderDocsSomehow() function
ractive.observe('doc1', function(newValue, oldValue, keypath) {
ractive.update()
});
but this did not do the trick.
So how can I inform Ractive that my data has changed and it needs to update itself?
If reactToChanges is just a function that sets up "listeners" for myRactive and is never called elsewhere, you can probably put the code for it in an oninit. This way, you have access to the instance.
function renderDocsSomehow() {
let myRactive = new Ractive({
el: '#myDiv',
template: 'my Template',
//magic: true,
data:{
doc1: docs
},
components: {myComponents},
computed: {computedValues},
oninit(){
//here my changes appear
onUpdatedOrInserted(change.doc);
this.update()
}
});
}
I found some sort of answer and thought I'd share it, even if it is not working 100% as desired.
If I return ractive from my first function It's usable in the second one:
fetchInitialDocs().then(renderDocsSomehow).then(reactToChanges).catch(console.log.bind(console));
function renderDocsSomehow() {
let myRactive = new Ractive({
el: '#myDiv',
template: 'my Template',
data:{
doc1: docs
},
components: {myComponents},
computed: {computedValues}
});
return myRactive;
}
function reactToChanges() {
//here my changes appear
onUpdatedOrInserted(change.doc);
renderDocsSomehow().update()
}
Sadly update()changes the whole ractive part of my page to its default state (every accordion that was folded out before is reverted and every chart.js is redrawn), which of course is not desirable in a highly dynamic application. Therefore I will nor mark it as answer by now.
Related
I currently have an array of object that I am rendering to a table. I am trying to follow the examples provided by Vuejs to use a "single source of truth" shared between multiple vues on the same page.
Overall, I am trying to make it where when vue1.refresh() is triggered, all the vues update their data when the "single source of truth" is updated. However, self.surveys = surveys; only updates the data on vue1.
Note: I am following the guide from https://v2.vuejs.org/v2/guide/state-management.html
// The single source of truth
var cache = {
data: [{...}] // Array of objects
}
var vue1 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
refresh: function(){
var self = this;
// After getting data back from an ajax call
.done(function(surveys) {
self.surveys = surveys;
});
},
}
});
var vue2 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
// Methods
}
});
There are two principles of Vue that will help you here:
In Vue, every data item is a source of truth.
Only the owner of a data item should modify it.
In your example, you have three sources of truth: the one you want to be the single source, and two others that are initialized from it. Also, the one you want to be the source of truth isn't a data item, it is outside Vue.
So to start, you should have a single Vue that represents your entire application and defines any data that represents application-level state:
new Vue({
el: '#app',
data: {
cache: {
data: [...]
}
}
});
The two Vue objects that you created should be children of the application Vue, which is to say, components.
The parent tells the children what the truth is via props. The child can suggest changes to the truth by emitting events to the parent, but the child does not directly modify the truth. That keeps all management of the truth in one place.
You would need to mutate the array, not replace it.
Array.prototype.splice can do this for you, if you don't want to use something like Vuex, as suggested by Vanojx1.
Splice expects specific elements, not a complete array for insertions. Because you have an array you want to use and you need to clear the old one, the syntax is a little odd... You pass this, the start, the count to remove (the entire length), and then the elements to add (concatenated on from your new array).
Array.prototype.splice.apply([self.surveys, 0, self.surveys.length].concat(surveys));
Problem is, you are replacing shared Cache object previously assigned to surveys variable, with new, not shared object. And solution? Do not try to mutate cache object. Just use Vuex. Vuex is simple, real "Vue way" solution.
// The single source of truth
var cache = {
data: [{...}] // Array of objects
}
var vue1 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
refresh: function(){
var self = this;
// After getting data back from an ajax call
.done(function(surveys) {
self.surveys = surveys; // Problem is right here
});
},
}
});
var vue2 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
// Methods
}
});
Try this example, which works like you code - not correct way:
var cache = {
key1: 'Value1'
}
var vue1 = new Vue({
el: '#app1',
data: {
surveys: cache
},
methods: {
replace () {
this.surveys = {key1: 'Replaced'}
}
}
})
var vue2 = new Vue({
el: '#app2',
data: {
surveys: cache
},
methods: {
replace () {
this.surveys = {key1: 'Replaced'}
}
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app1">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
<div id="app2">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
Then try this example, with Vuex, where you can freely replace "cache object" and replacint will affect other instance:
const store = new Vuex.Store({
state: {
cache: {
key1: 'Value1'
}
},
mutations: {
replace (state) {
state.cache = {key1: 'Replaced'}
}
}
})
var vue1 = new Vue({
el: '#app1',
store,
computed: {
surveys () {
return this.$store.state.cache
}
},
methods: Vuex.mapMutations([
'replace'
])
})
var vue2 = new Vue({
el: '#app2',
store,
computed: {
surveys () {
return this.$store.state.cache
}
},
methods: Vuex.mapMutations([
'replace'
])
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex#2.4.0/dist/vuex.min.js"></script>
<div id="app1">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
<div id="app2">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
As said in the comment before, you can use vuex to accomplish what you need, everytime you need to pass data between diferent components you can do that with a eventBus or passing props up and down between the components.
When you have a aplication that needs to pass a lot of data and receive it you can use vuex, first you need to install it and then you can do it this way:
you should cut the methods out and place the mounted(), it fires when the component loads, i think it was you need
var vue1 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
}.
mounted() {
var self = this;
// After getting data back from an ajax call
.done(function(surveys) {
self.surveys = surveys;
});
}
});
when you get the response pass it to vuex store, you can do it with a mutation like this:
this.$store.mutation('handlerFunction', self.surveys)
in the vuex you need to have the handlerfunction inside the mutation
mutations: {
// appends a section to the tree
handlerFunction: (state, dataReceived) => {
//then you can do
state.surveys = dataReceived
},
then in your other component you can receive it via a getter, the logic is the same watch vuex for more deaills, you have the main logic of connection here.
Hope it helps!
I have the following:
Vue.component('times-updated', {
template: '<span>Times Updated: {{ timesUpdated }}</span>',
data: function() {
return {
timesUpdated: this.$parent.myData.timesUpdated
}
}
});
var vm = new Vue({
el: '#test',
data: function() {
return {
myData: {}
}
}
})
setInterval(function(){
$.ajax({
url: `${window.location.href}/json`, // This just returns an array : array.timesUpdated: 2 etc
}).done(function (data) {
vm.myData = data; // changes this data
});
}, 1000)
and am using the following html:
<div class="test">
<times-updated></times-updated>
</div>
I poll a REST API that returns an array which includes a timesUpdated property:
{
timesUpdated: 5
}
My intention is that every second I use jQuery's $.ajax method to call the API, update the myData data object on vm, which would then update the times-updated component.
The code works on initial page load, the times-updated component can retrieve the value on its parent's myData property, but whilst I have confirms that vm.myData does reflect the new value from the API, the component doesn't update its display to show the new count.
What am i doing wrong?
The data function is only called once during the life cycle of the component; when it is initially created. So essentially your component is just displaying the value as it existed when the component was created.
Additionally, it's generally bad practice to reach out of a component to get a data value. Vue is props down, events up. You should convert your component to use a property.
Vue.component('times-updated', {
props:["times"],
template: '<span>Times Updated: {{ times }}</span>',
})
The fact that you are using a function to define the Vue in this particular case doesn't really matter, it's just not a typical practice. Components require a function because they need an isolated scope.
Here is an example.
That callback is required only in components
// vue instance
new Vue({
data: {
status: true
}
};
// vue components (callback)
Vue.component('custom-component', {
data: function() {
return {
status: false
}
}
});
Why the following codes are not equivalent?
With computed:
computed: Ember.computed('selected', function() {
console.log('computed');
return this.get('selected');
}),
observer1: Ember.observer('computed', function() {
console.log('observer1');
}),
observer2: Ember.observer('selected', function() {
console.log('observer2');
}),
With alias:
computed: Ember.computed.alias('selected'),
observer1: Ember.observer('computed', function() {
console.log('observer1');
}),
observer2: Ember.observer('selected', function() {
console.log('observer2');
}),
The first one only prints observer2 and the second one prints observer1 and observer2.
In the first one, computed is just a getter. So if you don't use computed in template or other places to react ASAP on changing, it's not called by changing selected.
But in second one, alias creates setter too. So if you change selected, computed changes after that quickly.
If you use computed in first one in a template, the same result comes.
In short, I want to get a value from a child component and check what it is in the parent. I have a working implementation using computed properties and a reference via v-ref on the child component, but I was wondering if I am doing it the right way and if there's a better/proper way to do it.
To get to specifics, I have a component with checkboxes, the checked checkboxes' values within this component are kept in the components data in an array variable named selected. Outside of the component I want to conditionally show a <div> using v-if however I'm unsure how to correctly grab the child component's selected value.
Here's a brief overview of my code:
component mark up
<student-table
v-ref:student-table
:data="students"
:course="course"
:columns="columns"
>
</student-table>
component registration
Vue.component('student-table', {
/* unrelated code */
data: function () {
return {
selected: []
}
},
/* unrelated code */
})
main vue instance
var vueApp = new Vue({
/* unrelated code */
computed: {
selected: function () {
return this.$refs.studentTable.selected.length
}
},
/* unrelated code */
})
Then in my html I can reference selected and I'll get the length of StudentTable.selected and thus be able to use it in my v-if
Thanks for any guidance or help!
Edit
I'm getting this in my console:
[Vue warn]: Error when evaluating expression "function () {
return this.$refs.studentTable.selected.length
}". Turn on debug mode to see stack trace.
There are several ways to share data between parents / components such as 2-way binding between parent/child and also sending and listening for events.
Here is an events example with $broadcast and $dispatch:
parent vue:
var parentVue = new Vue({
...
compiled: function(){
this.$on('receiveDataFromChild', function(){
//do something with the data from the child
});
},
methods: {
checkChildForData: function(){
this.$broadcast('pleaseSendDataToYourMama');
}
}
});
child vue:
var childVue = new Vue({
...
compiled: function(){
this.$on('pleaseSendDataToYourMama', function(){
this.$dispatch('receiveDataFromChild',this.someImportantData);
});
}
});
This is how I now have it working, I'm not sure this is the best way but I'm not getting any console.warn alerts in my console. Would love any feedback. Many thanks to #Douglas.Sesar
// child
Vue.component('student-table', {
parent: vueApp,
data: function () {
return {
selected: []
}
},
watch: {
selected: function() {
this.$dispatch('updateSelected', this.selected);
}
},
})
// parent
var vueApp = new Vue({
components: {
child: studentTable
},
data: {
selected: []
},
events: {
updateSelected: function(selected) {
this.selected = selected;
}
},
})
I am trying to use a component. I am already ractive components successfully, however for some reason this component just doesn't work:
var Ractive = require("ractive")
var log = console.log.bind(console);
var uploadComponent = Ractive.extend({
isolated: false,
template: 'test',
data: {}
});
module.exports = uploadComponent;
Produces the error:
Ractive.js: Could not find template for partial "previewVideo"
Update: component in being used as follows:
In myapp/index.js
var previewVideo = require('component-preview-video');
new Ractive({
el: $('.myapp-ui'),
template: myAppTemplate,
components: {
previewVideo,
...many other components that work
},
oncomplete: function(){....}
},
In myapp.mustache:
{{>previewVideo}}
How can the template be missing? It clearly exists. Where is ractive trying to look to find it? How can I debug this?
Partials and components are different (although there's some overlap in terms of concept and functionality) – if previewVideo is a component, it needs to be included like so:
<previewVideo/>
If it is a partial (though I'm guessing not, from the sample code) then you'd register it with Ractive via partials and not components:
var previewVideo = require('component-preview-video');
new Ractive({
el: $('.myapp-ui'),
template: myAppTemplate,
partials: {
previewVideo
},
oncomplete: function(){....}
});