Accessing variable from main Vue.js instance in component VM - javascript

Here's a very simple Vue app that is passing a prop from the main VM to a component. This value is grabbed via vue-resource in my actual application.
https://jsbin.com/tazuyupigi/1/edit?html,output
Of course it works, but I'm struggling to access this value from the component VM itself.
https://jsbin.com/hurulavoko/1/edit?html,output
As you can see, my alert shows undefined. How can I fix this? Seems to be an issue with order of execution, but using compiled() or created() in place of ready() makes no difference.

It is a problem with order of execution. The main Vue instance isn't "ready" until the things inside it have been compiled. This includes the hello component.
If you need to know that bar has been set before you use it, you can monitor for that in a couple of ways. You could $broadcast an event in the parent to let the child know that bar has been loaded. Or you could use a watch function in the child component to make changes when bar is updated.
Your example does work if you $set bar in created(), however with vue-resource you're going to have a delay anyway, so you need to account for the fact that greeting won't be ready during the child's ready() function. You'll either have to design your DOM structure to handle the fact that greeting could be undefined, or you'll have to use an event or watch function to wait for it yourself.
Example with $broadcast:
Vue.component('hello', {
template: 'From hello tag: {{ greeting }}',
props: ['greeting'],
ready: function() {
},
events:{
'bar-ready':function(){
alert(this.greeting)
}
}
});
new Vue({
el: '#app',
created: function() {
this.$http.get('/path')
.then(function(response){
this.$set('bar',response.data);
this.$broadcast('bar-ready')
}.bind(this))
}
});
$broadcast docs: https://vuejs.org/api/#vm-broadcast

Related

Tracking data changes in Vue.js on a Javascript that already exists?

I have a project that I've been working on for awhile that I might want to use vuejs for some of the UI elements. I fired up a tutorial and tried to piece together a basic example.
I have a basic javascript object like so:
var hex = {};
hex.turn_phase = 'unit_placement';
On my template, I have:
var app = new Vue({
el: '#vue-test',
data: {
message: 'Hello Vue!',
turn_phase: hex.turn_phase,
},
delimiters: ["<%","%>"],
mounted: function() {
this.fetch_turn_phase();
},
methods: {
fetch_turn_phase: function() {
Vue.set(this, 'turn_phase', hex.turn_phase);
},
}
});
This renders the correct turn_phase on the template initially, but if I change the hex.turn_phase in the browser console, the template doesn't react.
Is there something that I missed in this basic example?
It looks like you may have made things unnecessarily difficult. Just access your Vue instance via app?
Always make sure you go through the setters generated by Vue.js. These are watched and will re-render your component.
Instead, try using
app.turn_phase = 'unit_placement';
You can get a better understanding here, Reactivity
Vue creates all the data variables, computed properties and values returned from methods reactive. in your case since you
are changing hex, which is not a vue variable, so vue will not detect any changes in this variable. However if you change message variable, it will be reflective and will be changed in the template.

Why does v-if not get affected when the data is updated later?

If I were to create an element such as that below:
<div id="helloWorldDiv" v-if="visible">
Hello World
</div>
and then create a Vue instance for it:
var helloWorld = new Vue({
el: "#helloWorldDiv",
data: {
visible: false
}
});
I would expect that the line 'helloWorld.visible = true;' would show the element, but it has not effect. Can anyone explain why this doesn't work?
JSFiddle
I think you are getting started with Vue.js, which is great!
There are several changes you need to do in your sample app. Check this fiddle: https://jsfiddle.net/mani04/f20Ld806/
As you can see in that example, your show() function needs to be defined inside the methods of your Vue app. Also the show button needs be part of the app template, not outside of it.
You can find a lot more in Vue docs and tutorials online.
First off, I would suggest not manipulating the Vue instance from the outside. It'll make it harder to maintain the app as it grows.
However, as to your question, there's a number of issues with your JSFiddle:
You never set visible to true.
In your show function, you misspelled helloWorld as helloWolrd.
The show function and helloWorld variable need to be attached to the window so they can be globally accessed from the onclick event.
So if you update your javascript to:
var helloWorld = new Vue({
el: "#helloWorldDiv",
data: {
visible: false
}
});
function show() {
helloWorld.visible = true;
};
window.helloWorld = helloWorld;
window.show = show;
Your code works as expected.

What is the difference between a ractive template, partial and component

I've built a ractive.js app using partials. These partials are loaded via fetch/ajax - and all works nicely.
I then decided I wanted to encapsulate data along with the partial so looked at components - as I understood a component to do just that: Isolate a template/partial with its data.
I then looked to load the components in: http://ractivejs.github.io/ractive-load/
However, I don't really see the advantage of this approach - as it appears with the loader you can only load in the components template, not the entire encapsulated component (data, templates etc). You still have to put the data onto the main ractive instance (as you would with a partial).
I'm trying to dyanamically update the component. I'm also using page.js for routing. I'm trying to separate out all the concerns.
I'm probably not explaining myself very well - here is my code... most of it was taken from martydpx's answer here How to create Ractive's subcomponents dynamically and change them programmatically )
....
<dynamic name='{{name}}'/>
</script>
<script>
// Component loader
Ractive.load({
home: '/components/home.html', // seems this can only contain a template. Is it possible for it to contain everything - data and all?
packs: '/components/packs.html',
....
addplayer: '/components/addplayer.html',
notfound: '/components/notfound.html',
}).then( function ( components ) {
Ractive.components[ 'home' ] = components.home;
Ractive.components[ 'packs' ] = components.packs;
....
Ractive.components[ 'addplayer' ] = components.addplayer;
Ractive.components[ 'notfound' ] = components.notfound;
// dynamically load component based on route
Ractive.components.dynamic = Ractive.extend({
template: '<component/>',
components: {
component: function() {
this.set('foo','bar'); // I can dynamically set the data here.. but how would I add defaults for each component, within the component?
return this.get('route');
}
},
oninit: function(){
this.observe('route', function(){
this.reset();
},
{ init: false}
);
}
});
var r = new Ractive({
el: document.body,
template: '#template',
data: {
route: 'home'
}
});
// Routing. Sets the route... which triggers the component
page('/', index);
...
page();
function index() {
console.log('index');
r.set('route','home')
}
EDIT
I've read this - which has been a great help :)
https://github.com/ractivejs/component-spec/blob/master/authors.md
In the dynamic component scenario - how would I dynamically update component specific data. I seem to be able to do it when the component tag is hardwired into the page... but not when the component tag is dynamically created. After much playing about in the console - its as if it doesn't see the dynamic component. So things like r.findComponent('home').get() don't work.
Yet, if I put a <home/> tag in the template - it does work.
Also, do components automatically 'tear down' when they're un-rendered?
I'm not 100% sure what you are looking for.
First you create a child component -
var MyWidget = Ractive.extend({
template: '<div>{{message}}</div>',
data: {
message: 'No message specified, using the default'
}
});
You register this with Ractive runtime
Ractive.components.widget = MyWidget;
Then you create a parent component
var Parent = Ractive.extend({
template: '<div>
<MyWidget message={{widget}} />
</div>'
});
You use the parent instance to pass the data to child
// Live instance of parent
new Parent({
el: 'id',
data : {
widget: {
message : 'Waddup kiddo'
}
}
});
data.widget gets mapped to MyWidget's data, in-turn gets the message data.
For more info refer this
Generally there are 3 types of components you will be creating & using -
Self-sufficient Components - It knows everything it needs to know by itself. You don't pass anything to it. It creates it's own data or knows where to get it from. Ex: A logo component which knows by itself where to get the image from.
Dumb Components - They have no intelligence and all the data that it needs should be passed from parent. Like in our example - MyWidget has no idea where and what message stands for. Just renders it. No questions asked. Parent will fetch message and just pass it on.
Smart Components - Components which do some heavy lifting. An example would be Profile component. Parent will pass just a profileID to this, and it knows where to get profile data from, does some ajax calls, knows how to parse and interpret the data, may be even starts a socket and listens to changes etc.
So you decide how you want to make your components, who takes responsibility and think about data-encapsulation then.

Synchronous component registration

I have a knockout component which looks something like this:
define(['knockout', 'text!./my-component.html', 'pubsub'], function(ko, htmlString, pubsub) {
function viewModel(params) { }
return {
viewModel: {
createViewModel: function(params, componentInfo) {
var vm = new viewModel(params);
pubsub('updateViewModel').subscribe(function(){
// update vm
});
return vm;
}
},
template: htmlString
};
});
I use the createViewModel function to subscribe to an update event, which I use later on to trigger an update of the components viewmodel from other components.
I include the compnent on my page like this:
<!-- ko component: "my-component" -->
<!-- /ko -->
What I would like some verification on is the load order of things. I want to be sure that the createViewModel has been invoked before I might trigger the event.
This is my current order of calls:
// register my-component here
ko.applyBindings(myMainViewModel);
// code that might trigger the component update event here
I've read that ko.applyBindings is sychronous. But does that also include an implicit applybindings to all registered components, like my-component above? Do I need to set the synchronous property to true on the component in order to achieve this? If I understand it correctly, that flag is only related to rendering.
What I want to avoid is a race condition where I trigger the update event before it has been subscribed.
ko.applyBindings can act synchronously if the following conditions are satisfied BEFORE calling:
All components are registered
ALL component viewmodels are loaded in memory (fetched from network..)
ALL component templates are loaded in memory
Its when the component viewmodel and templates are not in memory that applyBindings becomes async (event if you set the synchronous=true).
This synchronous flag comes in to play in applybindings from the component binding. Notice that component binding does a ko.components.get call and passes a call back which will render the component on the DOM.
knockout/src/components/loaderRegistry.js has the definition of ko.components.get. The synchronous flag says that if the component is already cached (in memory) DON'T relinquish control of the thread. Its only when you release control of the thread (setTimeout, DOM insert/wait, ..) that applyBindings will return.
The only thing I'm not too sure about is how RequireJS will interact in here. There is code in knockout which will try to resolve the component using require first.
In any case the following steps will bring you closer (NOT PERFECT. See notes bellow)
//Load component vm, template and register it with synchronous=true
ko.appplyBinding(....)
ko.components.get("my-component" , function() {
//trigger component update event
})
There are few problems with this, and there are solutions to all of them.
Need to wait for multiple components to finish loading
[to solve this you can create a promise array for each component and resolve each of them via ko.components.get. Finally you can $.when(mypromiseArray, myCallback) to synch up all the promises]
ko.component.get does NOT tell you when the component is finally rendered on the DOM.
This is a much more challenging problem. I will share the solution if you need this level of precision (you need to know with in 50ms of when the component is loaded, and rendered on the UI).

MV* in Polymer, models and services as polymer-elements?

Say I want two views (polymer-elements) to share a model for example.
In Angular the model would live in a singleton service that gets injected into the views, both views read from the same source.
I tried emulating this approach with Polymer so I can do something like:
<polymer-element name="view1">
<template>
<my-model></my-model>
...
</template>
...
</polymer-element>
<polymer-element name="view2">
<template>
<my-model></my-model>
...
</template>
...
</polymer-element>
I like this approach because it's a declarative way of defining dependencies, and it basically works the same as <core-ajax> and other "out of the box" Polymer elements.
With this way I need to wait for the domReady lifecycle callback before I can interface with any element declared in the template, so this is where I'm holding my initialisation logic at the minute. The problem is that this callback gets called once for each <my-model> element declared (so <my-model> would be initialised twice in this example because it's present both in <view1> and <view2>). To make sure that my model follows the singleton pattern I have to move state outside of the element instance, something like this:
<polymer-element name="my-model">
<script>
(function(){
// private shared state
var instances = [], registered; // pattern variables
var foo; // state, model, whatever
// element init logic
Polymer('my-model', {
// Polymer callbacks
domReady: function(){
if (registered === (registered=true)) return;
// singleton init logic
foo = 'something';
// event handlers
this.addEventListener('foo', function(){ foo += 'baz'; });
},
attached: function() { instances.push(this); },
detached: function(){
instances = instances.filter(function(instance){
return instance !== this;
}.bind(this));
},
// element API
update: doSomething,
get state() { return foo; }
});
// private functions
function doSomething(){ foo += 'bar' }
})();
</script>
</polymer-element>
So it works but it looks wrong to me. Is using <polymer-element> generally incompatible with the singleton pattern? Should I move away from Polymer for models and services? How do Polymer core-elements get away with it?
[EDIT] I added some event listeners to the initialising code above. They're only registered in one instance to avoid the listeners triggering multiple times across multiple instances. What would happen if the instance where the event handlers are declared gets removed? Will that not break the asynchronous logic?
I'd go like this:
Define your model on the main page and call it from your views.
if it gets removed you could:
1 - listen for the "detached" lifecycle callback and inside it register it imperatively or
2 - store stuff on a prototype build in a higher level object and access it the way you fancy the most.
3 - if all fails, (i'm not sure it will but i guess so as i've yet to use this kind of implementation, as of now i talk to php and pass around objects i need persistent) you could use a "prepareForRemoval" knowing you will leave the instance, local storage your stuff and do number 1 then "recoverFromRemoval" if you know what i mean by camel casing prototype suggestions.
Anyways i'm not very fond of singletons. Polymer is powerful front-end stuff but i'm not sure it's the best way to go about it.
in the API docs they do not mention the possibility of getting it cut off (as you can see)
but i honestly think you're right and you would lose your stuff.
That's just my 2 cents actually just a inellegant sollution i came up for at this very moment, maybe #ebidel, #DocDude or #dodson can help us in that matter but you can't really tag em here on SO i'll tag em on G+ for us, you sir got me intrigued.
BTW why would you move away from your main page? there's no point for it in polymer you should change the content dynamically not get away from it. what would be the usage scenario?
ps.: sorry, i hate capitalizing proper nouns.Get over it
EDIT (wouldn't fit on the comments):
I expressed myself wrong. Anyways i strongly think i wasn't understanding what you wanted.
Well, if i got it right this time yes it will fire multiple times (they are supposed to), but it shouldn't cut others out once a particular view gets removed.
As for your initialisation logic i would go about adding a listener to the window or document (i think window is more advisable) itself waiting for the 'polymer-ready' event.
"To make sure that my model follows the singleton pattern I have to
move state outside of the element instance"
Yes thats right. but don't wait for the domready in it's prototype, instead use a construct or contruct-like and call it it as the callback of the aforementioned event listener. i'll edit my answer to make it clearer (if it's not, let me know) when i get back home. i hope you got i meant.
if you don't i'll be back soon.
In browsers, window == singleton object by definition.
Simple use:
var window.instances = [];
var window.registered;
var window.foo;
instead.
Another way is to use Polymer core-meta element:
<core-meta id="x-foo" label="foo"></core-meta>
<core-meta id="x-bar" label="bar"></core-meta>
<core-meta id="x-zot" label="zot"></core-meta>
<core-meta id="apple" label="apple" type="fruit"></core-meta>
<core-meta id="orange" label="orange" type="fruit"></core-meta>
<core-meta id="grape" label="grape" type="fruit"></core-meta>
<h2>meta-data</h2>
<template id="default" repeat="{{metadata}}">
<div>{{label}}</div>
</template>
<h2>meta-data (type: fruit)</h2>
<template id="fruit" repeat="{{metadata}}">
<div>{{label}}</div>
</template>
<script>
document.addEventListener('polymer-ready', function() {
var meta = document.createElement('core-meta');
document.querySelector('template#default').model = {
metadata: meta.list
};
var fruitMeta = document.createElement('core-meta');
fruitMeta.type = 'fruit';
document.querySelector('template#fruit').model = {
metadata: fruitMeta.list
};
});
</script>

Categories