I've created an application that takes an address or city and displays the most Favorited tweet at that location using Vuejs. I've written three methods for each thing that needs to happen.
First, I grab an auth key from a url fragment. Second,
I pass the address to google's api to get cordinates. Finally, I use the key and location from the first two methods to make the final Api request to get the content I want.
As it stands I have three buttons that appear on the appropriate step and trigger their method with an #click. Is there a way to trigger a sequence of methods with #click? It seems like I may be able to use $emit to chain them together but I am new to developing on the web and don't fully understand what I've read so far.
Id like to just have one button do all three.
My solution as it stands:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<title></title>
</head>
<body>
<div id="app">
<button #click="method1">button 1</button>
<button #click="method2">button 2</button>
<button #click="method3">button 3</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.js">
</script>
<script>
new Vue({
el: "#app",
methods: {
method1: function(){
alert("first thing happened");
},
method2: function(){
alert("second thing happend");
},
method3: function(){
alert("third thing happened")
}
}
});
I think it would be much more easy and intuitive to use a Promise(http://www.html5rocks.com/en/tutorials/es6/promises/). Promises have been around for a while now, but they are not supported by old IE (http://caniuse.com/#feat=promises). You can use a polyfill, though.
I started using them about 6 months ago and they are extremely powerful, and combined with vue-resource (https://github.com/vuejs/vue-resource) they are just amazing. Your code would look like this:
new Vue({
el: "#app",
methods: {
method1: function(){
this.$http(authCallUrl, data1)
.then(function (response) {
console.log('First call was successful', response);
return this.$http(googleApiURL, data2);
})
.then(function (response) {
console.log('Second call was successful', response);
return this.$http(finalAPIRequestURL, data3);
})
.then(function (response) {
console.log('Everything was top-notch', response);
})
}
}
});
I know it seems like a lot of new things but believe me, Promises are gonna improve you life big times!
Cheers!
If you weren't relying on that asynchronous API call, you could have created a method that fired off all three sequentially and bound your #click to that. Because you are waiting on an AJAX call, though, the $emit() and custom event handling is probably the way to go. This will let you pass data between your methods, too (in case your API call is dependent on the result of your auth key extraction, et cetera).
new Vue({
el: "#app",
methods: {
method1: function() {
var resultOfMethod1 = "foo ";
alert( "resultOfMethod1: " + resultOfMethod1 );
this.$emit( "ready.method1", resultOfMethod1 );
},
method2: function( token ) {
var resultOfMethod2 = token + "bar ";
alert( "resultOfMethod2: " + resultOfMethod2 );
this.$emit( "ready.method2", resultOfMethod2 );
},
method3: function( token ) {
var resultOfMethod3 = token + "baz ";
alert( "resultOfMethod3: " + resultOfMethod3 );
}
},
events: {
"ready.method1": function ( token ){
this.method2( token );
},
"ready.method2": function ( token ){
this.method3( token );
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.js"></script>
<div id="app">
<button #click="method1">button 1</button>
</div>
Related
I have question related to data of Vue.
I created data with an empty object.
data(){
return {
myObj: {}
}
}
and function like this:
methods: {
changeMyObj() {
this.myObj.newKey = 'aaaa';
}
}
Then I show it on template by click
<a #click="changeMyObj">Click change</a>
{{myObj.newKey}}
With this click, the nested key is not rendered on template. How can I resolve this issue?
Note: I do not meet this issue with Vuex or state of React.
This happens because of vue.js reactivity. In fact, here you are modifying a value that was not declared when the component mounted and Vue cannot track the changes.
You can update values by using the Vue.set method which allows Vue to track the data.
Vue.set(this.myObj, "myKey", "Hello world")
You can also make a deep copy of the object instead of just adding the key.
This can be done using the spread operator.
For example
this.myObj = {...this.myObj, myKey: "Hello world"}
Here is a small example using the two versions
new Vue({
el: "#app",
data: () => ({
myObj: {}
}),
methods: {
addKey(){
this.myObj = {...this.myObj, myKey: "Hello world foo"}
},
addKey2(){
Vue.set(this.myObj, "myKey", "Hello world bar")
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
{{ myObj.myKey }}
<button #click="addKey">Add key</button>
<button #click="addKey2">Add key 2</button>
</div>
I found the solution for this.
this.myObj = {...this.myObj, newKey:'aaaa'}
I do not think it is solution while it has no difference with:
this.myObj['newKey'] = 'aaaa';
or
this.myObj.newKey = 'aaaa';
If someone can explain why please let me know. Thanks
Correct way to assign a new property in an existing object is Vue.set(this.myObj, 'newKey', 'aaaa') to make it reactive instead of this.myObj.newKey = 'aaaa'
Demo :
new Vue({
el: '#app',
data: {
myObj: {}
},
methods: {
changeMyObj() {
Vue.set(this.myObj, 'newKey', 'aaaa')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="changeMyObj">Click change</button>
{{ myObj.newKey }}
</div>
I want to write simple app that takes data from server, compute result and display it.
Here is my code:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<script src="https://unpkg.com/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-resource#1.5.1"></script>
<body>
<div id="app">
<p>{{ my_data }}</p>
<div> computed value: {{myvalue}} </div> <!-- I need to get access to myvalue from computed -->
</div>
</body>
<script>
var app = new Vue({
el: '#app',
data: {
my_data: {},
mess: 'hello world'
},
computed: {
myvalue: function () {
return Number(this.my_data['2018-07-10']['april']) + Number(this.my_data['2018-07-10']['may'])
}
},
created: async function(){
url = 'http://dlang.ru/test'
const response = await fetch(url);
this.my_data = await response.json();
}
})
</script>
</html>
The page displays:
{ "2018-07-10": { "may": 9, "april": 10 } }
computed value: 19
But in console I am getting error: Error in render: "TypeError: Cannot read property 'april' of undefined"
If I right understand it's trying display mustached {{my_data}} before server return data and computed will calculate it.
What is the right way to prevent this error?
You're understanding the problem correctly, you have a race condition in your code because of your async created lifecycle hook, there are a couple approaches you can take to solve this problem:
Fetch the data before initializing the Vue app and pass it to the Vue app as a prop or
Check if my_data has been populated with data in your computed property before trying to use it.
Currently, I do v-if="Object.keys(video).length > 0" on a div to ensure that the video only gets sent to the video-player component when it is actually available.
If I omit the v-if check, the video, initialized as null gets sent to the video-player component and the video won't load, even though a second later, the video data is populated with a URL from an Ajax call. Is there a better way do solve this issue without resolving to v-if directives all over the place?
You might use Async Components, it allows you to handle asynchronous code and resolve your component only when is ready, also provides advance features for loading and handle errors, here is a simple example:
Vue.component('async-component',
function (resolve, reject) {
fetch('https://randomuser.me/api/?results=10')
.then((resp) => resp.json())
.then(function(data) {
this.authors = data.results || [];
resolve({
template:
`
<div>
<img
v-for="author in authors"
:src='author.picture.medium'
/>
</div>
`,
data() {
authors: []
}
});
});
});
var vm = new Vue({
el: '#app',
template:
`
<async-component />
`
});
You can also check the working example.
I'm new with Vue.js, and I notice some content re-render after changing any data that is not part of that content, here is an example:
https://jsfiddle.net/gustavompons/rtxqhyv2/1/
HTML
<div id="app">
<input v-model="foo1">
<div v-html="showFoo1()"></div>
<div v-html="showFoo2()"></div>
</div>
JS
new Vue({
el: '#app',
data: {
foo1: 'foo1',
foo2: 'foo2'
},
methods: {
showFoo1 () {
console.log('this is ok to execute on input')
return this.foo1
},
showFoo2 () {
console.log('this should NOT execute on input')
return this.foo2
}
}
})
So every time I type on the input, I get "this should NOT re-render on input" in the console, which I think it's not ok because there is no reason to execute that piece of code every time.
Is this the way Vue work or am I doing something wrong?
I'm using vue.js v2
The results of methods are not cached and will be executed every time the component is re-rendered. If you want caching and dependency tracking, use computed properties instead:
computed: {
showFoo1 () {
console.log('this is ok to execute on input')
return this.foo1
},
showFoo2 () {
console.log('this should NOT execute on input')
return this.foo2
}
}
And get rid of the () when accessing them.
I have experience with both single page apps and multi-page apps (classic websites). In the past I have used AngularJS 1.x on each page, it has been useful because all components can live in separate files and be executed as they appear on each page.
I'm now looking at VueJS to replace AngularJS, but not finding it easy to understand how to architect my multi-page app.
As expected I want to use some components on all the pages, and some on only a few pages.
Example:
I came across SFC - single file components using ES2015 which looked promising, but my backend is Java which outputs my html from JSPs. It appears that .vue files are precompiled by webpack, but if my templates are only ready when the page is rendered that won't be possible will it?
How would one architect a solution so that each component is modular but utilize either an x-template in the html and somehow attach it to a .vue SFC, or is there some other way to have components in separate files which can be imported using ES2015?
I hope this is making sense, can't seem to figure it out.
One possible approach would be to set the template for the Vue Component inline. So this would be to have a Component File like
Home.vue:
<script>
export default {
data() {
return {
msg: 'text',
}
}
}
</script>
import it as a global component for Vue (using require, import, etc.)
Vue.component('home', require('./components/Home.vue'));
and in your server generated HTML you'd have to use an inline template, which will have all the flexibility from normal templates
home.jsp:
<home inline-template>
<h2 v-text="msg"></h2>
</home>
Update
I've added an example on GitHub here
If I understand your question, you have want to make single file components out of HTML.
If this is the case, you should make use of the render() function and regular components.
The render function decides what to use as a template for a component:
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app">
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<script type="text/javascript">
new Vue({
el: '#app',
render (createElement) {
return createElement({
template: '<div>Hello World</div>'
})
},
})
</script>
</body>
</html>
will render Hello World to the screen.
Now, let's see how this function is reactive:
<script type="text/javascript">
new Vue({
el: '#app',
data: {
count: 0
},
render (createElement) {
return createElement({
template: '<div>Hello World ' + this.count + '</div>'
})
},
created () {
setTimeout(() => {
this.count++
}, 2000)
}
})
</script>
Here, after 2 seconds, the counter in <div>Hello World ' + this.count + '</div> will increment from 0 to 1.
Now, what if we want to separate the template from the data?
<script type="text/javascript">
new Vue({
el: '#app',
render (createElement) {
return createElement({
template: '<div>Hello World {{ count }}</div>',
data () {
return {foo: 'bar'}
}
})
}
})
</script>
This code will display Hello World bar.
Now, let's see what happen if we try to load our template over http. We'll use the axios library to do so. Let's create a remote.html file to contain our html code:
<div>
I'm a remote component {{ foo }}
</div>
Now, let's try to load it via Ajax:
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.2/axios.min.js"></script>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
template: null
},
render (createElement) {
return createElement({
template: this.template ? this.template : '<div>Hello World {{ foo }}</div>',
data () {
return {foo: 'bar'}
}
})
},
created () {
axios({
url: '/remote.html',
method: 'get'
}).then(response => {
this.template = response.data
})
}
})
</script>
This code will display I'm a remote component {{ foo }} as soon as remote.html has been loaded from the browser.
Note that the object passed to the createElement function is actually a component structure. You can use the same methods on it:
render (createElement) {
return createElement({
template: this.template ? this.template : '<div>Hello World {{ foo }}</div>',
data () {
return {foo: 'bar'}
},
mounted () {
alert('Hello from mounted')
}
})
}
will trigger an alert on the browser.
Anyway, here is a complete example with nested components:
Index.html
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app">
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.2/axios.min.js"></script>
<script type="text/javascript">
const headerComponent = {
data () {
return {
template: '<div>Loading...</div>'
}
},
render (createElement) {
return createElement({
template: this.template,
data () {
return {
search: ''
}
}
})
},
created () {
axios('/header.html').then(response => {
this.template = response.data
})
}
}
new Vue({
el: '#app',
data: {
template: null
},
render (createElement) {
return createElement({
template: this.template ? this.template : 'Loading...',
data () {
return {foo: 'bar'}
},
components: {
'my-header': headerComponent
}
})
},
created () {
axios({
url: '/remote.html',
method: 'get'
}).then(response => {
this.template = response.data
})
}
})
</script>
</body>
</html>
header.html
<div>
<label>Search</label>
<input v-model="search" name=""> The search is: {{ search }}
</div>
I'm not sure that this is really the best approach and if I'm really responding to the question, but it will at list give you some tips on how Vue handles rendering and components...