vuejs-paginator example html markup - vue.js

I have a pretty daft question re: vuejs-paginator but I am having a hard time getting to run this example (I am a backend dev here).
So, I have on my head:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuejs-paginator/2.0.0/vuejs-paginator.js"></script>
Then as described in the page, I have:
<script>
new Vue({
el: '#app',
data () {
return {
// The resource variable
animals: [],
// Here you define the url of your paginated API
resource_url: 'http://hootlex.github.io/vuejs-paginator/samples/animals1.json'
}
},
components: {
VPaginator: VuePaginator
},
methods: {
updateResource(data){
this.animals = data
}
}
});
</script>
Now, what the F I should have in my HTML, I have no clue, and the author seems to say, I use:
<v-paginator resource_url="api/animals" #update="updateResource"></v-paginator>
but, this I think is just for the pagination. What shoud the app element contain and where is it? The docs dont seem to show this? he author then shows some random markup:
<ul>
<li v-for="animal in animals">
{{ animal.name }}
</li>
</ul>
Where should this go? Should I have an app div element?

You can add content to your app by adding it to the template key:
<script>
new Vue({
el: '#app',
data () {
return {
// The resource variable
animals: [],
// Here you define the url of your paginated API
resource_url: 'http://hootlex.github.io/vuejs-paginator/samples/animals1.json'
}
},
components: {
VPaginator: VuePaginator
},
methods: {
updateResource(data){
this.animals = data
}
},
template: `
<div>
<ul>
<li v-for="animal in animals">
{{ animal.name }}
</li>
</ul>
<v-paginator :resource_url="resource_url" #update="updateResource"></v-paginator>
</div>
`
});
</script>
Something like that.
You also need an initial <div id="app"></div> in your html. This is the element where Vue will mount the app and load the content.
EDIT
The plugin uses this.$http which requires some kind of dependency that it doesn't specify.
I have made an alteration in the following codepen and it works properly now:
https://codesandbox.io/s/competent-dream-zngzt

Related

VueJs: bind `v-on` on a custom component to replace an existing one

In order to ease the styling of my page, I'd like to create a bunch of mini components like, and exploit how attributes are merged in VueJs. So for example, here is a minimal js file also hosted on this JSFiddle:
Vue.component('my-button', {
template: '<button style="font-size:20pt;"><slot></slot></button>'
})
var app = new Vue({
el: "#app",
data: {
message: "world",
},
methods: {
sayHello: function () {
alert("Hello");
}
}
})
and then in my html I just want to use <my-button> instead of button:
<div id="app">
Hello {{message}} <my-button #click="sayHello" style="color:red;">Style works, but not click</my-button> <button v-on:click="sayHello" style="color:red;">Both works</button>
</div>
Unfortunately, it seems that attributes are merged, but not listeners, so it means that I can't do v-on:click on my new button... Any way to make it possible?
Thanks!
-- EDIT --
I saw the proposition of Boussadjra Brahim of using .native, and it works, but then I found this link that explains why it's not a great practice and how to use v-on="$listeners" to map all listeners to a specific sub-button. However, I tried, to just change my template with:
template: `<button style="font-size:20pt;" v-on="$listeners"><slot></slot></button>`,
but I get an error:
Vue warn: Property or method "$listeners" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option."
Here is the JSFiddle.
Your fiddle didn't work because you were using an old version of Vue, $listeners was added in Vue 2.4.0.
Here's a demo:
Vue.component('my-button', {
template: '<button style="color: red" v-on="$listeners"><slot/></button>'
})
new Vue({
el: '#app',
methods: {
sayHello() {
alert('Hello')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-button #click="sayHello">Custom Button</my-button>
<button #click="sayHello">Ordinary Button</button>
</div>

Is it possible to DRY html in vuejs components without creating more components?

Lets say you have:
<template>
<div>
<!-- html for buttons -->
<!-- your form -->
<!-- html for buttons -->
</div>
</template>
<!-- rest of your component -->
Is it possible to DRY up the html for the html for buttons without using a separate component? It seems a lot of work to keep adding components just to save repeating 3-4 lines of html?
I don't know any Vue api that allows to do that properly, however there is a way.
There is v-html which would serve you for DRY html, but it would get rendered as plain HTML, so you cannot use Vue events from there -which I guess your buttons do-.
For instance:
//template
<div id="app">
<div v-html="dryContent"></div>
<p>{{content}}</p>
<div v-html="dryContent"></div>
<div v-html="computedString"></div>
</div>
//script
new Vue({
el: '#app',
data: {
content: 'some sentence',
dryContent: `<div>
<p>Hello world!</p>
</div>`
},
computed: {
computedString() {
return `<p>${this.content}</p>`
}
}
});
Will render the HTML properly. But you cannot setup vue event listeners in the rendered HTML.
You can still, however, setup native listeners:
dryContent: `<div>
<p onclick="console.log('foo')">Hello world!</p>
</div>`
And it will work.
And, well, there is this really obscure pattern which I totally don't suggest but that actually will fit your needs:
new Vue({
el: '#app',
data: {
content: 'some sentence',
dryContent: `<div>
<p onclick="modifyContent()">Hello world!</p>
</div>`
},
computed: {
computedString() {
return `<p>${this.content}</p>`
}
},
created() {
window.modifyContent = function() {
this.content = 'modified!!';
}.bind(this);
}
});
You export the component method to a window property, so you can call it from native code.
Don't know your use case, but I'm pretty sure I would just duplicate the HTML code or setup a new component instead of doing this.

Vue.js Dynamic Component - Template not showing components data

I'm trying to build a quiz-game with VueJs and up until now everything worked out smoothly, but now that I'm started using dynamic components I'm running into issues with displaying the data.
I have a start component (Start View) that I want to be replaced by the actual Quiz component ("In Progress") when the user clicks on the start button. This works smoothly. But then, in the second components template, the data referenced with {{ self.foo }} does not show up anymore, without any error message.
The way I implemented is the following:
startComponent:
startComponent = {
template: '#start-component',
data: function () {
return {
QuizStore: QuizStore.data
}
},
methods: {
startQuiz: function () {
this.QuizStore.currentComponent = 'quiz-component';
}
}
}
};
And the template:
<script type="x-template" id="start-component">
<div>
<button v-on:click="startQuiz()">
<span>Start Quiz</span>
</button>
</div>
</script>
Note: I'm using x-templates since it somehow makes the most sense with the rest of the application being Python/Flask. But everything is wrapped in {% raw %} so the brackets are not the issue.
Quiz Component:
quizComponent = {
template: '#quiz-component',
data: function () {
return {
QuizStore: QuizStore.data,
question: 'foo',
}
};
And the template:
<script type="x-template" id="quiz-component">
<div>
<p>{{ self.question }}</p>
</div>
</script>
And as you might have seen I'm using a QuizStore that stores all the states.
The store:
const QuizStore = {
data: {
currentComponent: 'start-component',
}
};
In the main .html I'm implementing the dynamic component as follows:
<div id="app">
<component :is="QuizStore.currentComponent"></component>
</div>
So what works:
The Start screen with the button shows up.
When I click on the Start Button, the quizComponent shows up as expected.
What does not work:
The {{ self.question }} data in the QuizComponent template does not show up. And it does not throw an error message.
it also does not work with {{ question }}.
What I don't understand:
If I first render the quizComponent with setting QuizStore.currentComponent = 'startComponent', the data shows up neatly.
If I switch back to <quiz-component></quiz-component> (rather than the dynamic components), it works as well.
So it seems to be the issue that this. does not refer to currently active dynamic component - so I guess here is the mistake? But then again I don't understand why there is no error message...
I can't figure out what the issue is here - anyone?
You may have some issues with your parent component not knowing about its child components, and your construct for QuizStore has a data layer that you don't account for when you set currentComponent.
const startComponent = {
template: '#start-component',
data: function() {
return {
QuizStore: QuizStore.data
}
},
methods: {
startQuiz: function() {
this.QuizStore.currentComponent = 'quiz-component';
}
}
};
const QuizStore = {
data: {
currentComponent: 'start-component',
}
};
new Vue({
el: '#app',
data: {
QuizStore
},
components: {
quizComponent: {
template: '#quiz-component',
data: function() {
return {
QuizStore: QuizStore.data,
question: 'foo'
}
}
},
startComponent
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<script type="x-template" id="start-component">
<div>
<button v-on:click="startQuiz()">
<span>Start Quiz</span>
</button>
</div>
</script>
<script type="x-template" id="quiz-component">
<div>
<p>{{ question }}</p>
</div>
</script>
<div id="app">
<component :is="QuizStore.data.currentComponent"></component>
</div>
The following worked in the end:
I just wrapped <component :is="QuizStore.currentComponent"></component> in a parent component ("index-component") instead of putting it directly in the main html file:
<div id="app">
<index-component></index-component>
</div>
And within the index-component:
<script type="x-template" id="index-component">
<div>
<component :is="QuizStore.currentComponent"></component>
</div>
</script>
Maybe this would have been the right way all along, or maybe not, but it works now :) Thanks a lot Roy for your help!

Vue.js 2 - Sharing XHR Data between Views WITHOUT using Vuex?

I'm teaching myself Vue.js 2. My task is to pull a list of posts from the Hacker News API. Upon clicking a list post, a new view is to display some of the data from that specific post.
I'm having a very tough time understanding how to get the REST API data populated in the 2nd view upon routing to the 2nd view, from the 1st view.
(I'm using vue-router and vue-resource(?), and not Vuex (it's such a small application).)
Below are the Item.vue and List.vue. From the List.vue, I'm trying to route to the Item.vue by clicking on a list item. For example, click on a list item called "Guy Has Tough Time with Vue", then open a 2nd view to display a title, score, URL, and comments of the post "Guy Has Tough Time with Vue".
List.vue (creates list, XHR request)
<template>
<div class="list-container">
<h1>List Vue</h1>
<ul class="item-list" v-for="(item, index) in this.items">
<li>
<router-link class="list-item" :to="/views">
{{index + 1}}. {{item.title}}
<div class="points">
Points: {{item.points}}
</div>
</router-link>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'List',
props:[]
data(){
return {
items: []
}
},
mounted: function(){
console.log("created");
this.fetchList();
},
methods: {
fetchList(){
this.$http.get('http://hn.algolia.com/api/v1/search?query=javascript&hitsPerPage=25').then((response) => {
this.items = response.data.hits;
})
}
}
}
Item.vue (Receives item-specific data from List.vue)
<template>
<video id="bgvid" playsinline autoplay loop>
<source src="./src/assets/rad.mp4" type="video/mp4">
<div class="item-container">
<h1>Item Vue</h1>
<div class="post-title"></div>
<div class="post-score"></div>
<div class="post-url"></div>
<ul class="post-comments">
<li class="sngl-comment">
</li>
</ul>
</div>
</video>
</template>
<script>
export default {
name: 'Item',
data(){
return {
item: {}
}
},
mounted: function(){
console.log("created");
},
methods: {
}
}
</script>
Thanks in advance!
The problem is that the first view isn't a direct descendant of the second view so you can't pass data to it through props. I actually wouldn't be using vuex for this, instead I would pass an id through the route for the specific list item and fetch that individual item by the id, as an example:
const View1 = {
template: `<div>
<ul>
<li v-for="item in list"><router-link :to="'view2/'+item.id">{{item.name}}</router-link></li>
</ul>
</div>`,
data() {
return {
list: [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 3,
name: 'baz'
}]
}
}
}
const View2 = {
template: `<div>Fetch item {{id}} <br /><router-link to="/">back</router-link></div>`,
created(){
console.log('Fetch data for item '+ this.id);
},
computed:{
id(){
return this.$route.params.id
}
}
}
const routes = [{
path: '/',
component: View1
}, {
path: '/view2/:id',
component: View2
}]
const router = new VueRouter({
routes // short for `routes: routes`
})
var app = new Vue({
router
}).$mount('#app')
Here, I've set the route on View2 to be: view2/:id (see: Dynamic route matching), now in the View2 component I can access that id via this.$route.params.id (which I've put in a computed). Once I've got that id I can simply make an ajax request for the data for the specific item.
And here's the JSFiddle: https://jsfiddle.net/craig_h_411/3hwr6mcd/
Using Vuex
If you are unable to retrieve a record by the specific id for some reason and you don't want to duplicate the call, you will need to share state between non-descendant components and that's where Vuex comes in.
There is a misconception with vuex that it's complicated, but if you only want to share state between a couple of components, it's really quite simple (and it's less than 10kb in size) so there really isn't much use trying to avoid it, all you need to add to your project is:
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = {
state:{
item: {}
},
mutations:{
setItem(state, value){
state.item = value;
}
}
}
export default new Vuex.Store(store)
Then in your app.js (or wherever you have you main Vue instance) you would do something like:
import Vue from 'vue'
import store from './store'
import App from './components/App.vue'
new Vue({
store, // this will be available in components via this.$store
render: h => h(App)
}).$mount('#app')
You will also have to make a few changes, such as committing the state before pushing to the route, adding a computed to get the state from the store and removing the id from the route as it's no longer needed.
I've updated the JSFiddle to show you how that would work: https://jsfiddle.net/craig_h_411/bvswc0kb/

How to load external html file in a template in VueJs

I'm new to vue js.
I'm just creating a simple project where I just include vuejs through CDN. not using node/npm or cli.
I keep all my html markup in single html which looks messy as it grows. I tried to split html to views and want to include it by something analogous to ng-include of angularJs
I have worked in angular previously where there is ng-include to load external html files. I'm looking for something similar to that in vue. the whole point is to split my html files into more maintainable separate files.
have come across <template src="./myfile.html"/> but it doesn't work
Can somebody help me out
It's actually remarkably easy, but you need to keep something in mind. Behind the scenes, Vue converts your html template markup to code. That is, each element you see defined as HTML, gets converted to a javascript directive to create an element. The template is a convenience, so the single-file-component (vue file) is not something you'll be able to do without compiling with something like webpack. Instead, you'll need to use some other way of templating. Luckily there are other ways of defining templates that don't require pre-compiling and are useable in this scenario.
1 - string/template literals
example: template: '<div>{{myvar}}</div>'
2 - render function 🤢
example: render(create){create('div')}
Vue has several other ways of creating templates, but they just don't match the criteria.
here is the example for both:
AddItem.js - using render 😠 functions
'use strict';
Vue.component('add-item', {
methods: {
add() {
this.$emit('add', this.value);
this.value = ''
}
},
data () {
return {
value: ''
}
},
render(createElement) {
var self = this
return createElement('div', [
createElement('input', {
attrs: {
type: 'text',
placeholder: 'new Item'
},
// v-model functionality has to be implemented manually
domProps: {
value: self.value
},
on: {
input: function (event) {
self.value = event.target.value
// self.$emit('input', event.target.value)
}
}
}),
createElement('input', {
attrs: {
type: 'submit',
value: 'add'
},
on: {
click: this.add
}
}),
])
}
});
ListItem.js - using template literals (back-ticks)
'use strict';
Vue.component('list-item', {
template: `<div class="checkbox-wrapper" #click="check">
<h1>{{checked ? '☑' : '☐'}} {{ title }}</h1>
</div>`,
props: [
'title',
'checked'
],
methods: {
check() {
this.$emit('change', !this.checked);
}
}
});
and the html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.0/vue.js"></script>
<script src="ListItem.js"></script>
<script src="AddItem.js"></script>
</head>
<body>
<div id="app">
<add-item #add='list.push({title:arguments[0], checked: false})'></add-item>
<list-item v-for="(l, i) in list" :key="i" :title="l.title" :checked="l.checked" #change="l.checked=arguments[0]"></list-item>
</div>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
newTitle: '',
list: [
{ title: 'A', checked: true },
{ title: 'B', checked: true },
{ title: 'C', checked: true }
]
}
});
</script>
</body>
</html>
TL; DR;
See it in action at : https://repl.it/OEMt/9
You cant. You must use async components - read guide here
Actually you can. This is kinda easy. Depends on your needs and situation. However, this code is NOT technically correct, however it will explain to you how it might work, gives you massive freedom and makes your original vue instance smaller.
To make this work, you will need vue router (cdn is ok) and in this case axios or fetch (if you dont care about supporting older browsers).
The only downfall in my opinion is that in content files you will need to add additional call parameter $parent . This will force vue to work.
index
<div id="app">
<router-link v-for="route in this.$router.options.routes" :to="route.path" :key="route.path">{{ route.name }}</router-link>
<section style="margin-top:50px;">
<component :is="magician && { template: magician }" />
</section>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
const viewer = axios.create({ baseURL: location.origin });
const routes = [
{"name":"Hello","slug":"hello","path":"/lol/index.html"},
{"name":"Page One","slug":"page_one","path":"/lol/page-one.html"},
{"name":"Page Two","slug":"page_two","path":"/lol/page-two.html"}
];
const app = new Vue({
router,
el: '#app',
data: {
magician: null,
},
watch: {
$route (to) {
this.loader(to.path);
}
},
mounted() {
this.loader(this.$router.currentRoute.path);
},
methods: {
viewer(opt) {
return viewer.get(opt);
},
loader(to) {
to == '/lol/index.html' ? to = '/lol/hello.html' : to = to;
this.viewer(to).then((response) => {
this.magician = response.data;
}).catch(error => {
alert(error.response.data.message);
})
},
huehue(i) {
alert(i);
}
}
});
</script>
hello.html content
<button v-on:click="$parent.huehue('this is great')">Button</button>
page-one.html content
<select>
<option v-for="num in 20">{{ num }}</option>
</select>
page-two.html content
// what ever you like
router explanation
To make this work perfectly, you will need to find a correct way to configure your htaccess to render everything if current page after first view is not index. Everything else should work fine.
As you can see, if it is index, it will load hello content file.
I faced the same issue and this is how I solved it , I also made a video about this question https://www.youtube.com/watch?v=J037aiMGGAw
create a js file ,for your component (logic) let's call it "aaaa.vue.js"
create an HTML file for your template that will be injected in your "aaaa.vue.js" and let's call it "aaaa.html"
Component file (Logic file javascript)
const aaaa = {
name:"aaaa",
template: ``,
data() {
return {
foo:"aaaa"
};
},
methods: {
async test() {
alert(this.foo)
},
},
};
Template file (HTML)
<!--template file-->
<div>
<button #click="test" > click me plz </button>
</div>
index.html
<html>
<head>
<title>my app</title>
</head>
<body>
<div id="app" class="main-content col-12">
<aaaa></aaaa>
</div>
</body>
</html>
<script src="axios.min.js"></script>
<script src="vue.js"></script>
<!-- load js file (logic) -->
<script src="aaaa.vue.js"></script>
<script>
document.addEventListener("DOMContentLoaded", async function () {
//register components
let html = await axios.get("aaaa.html"); // <---- Load HTML file
aaaa.template = html.data;
Vue.component("aaaa", aaaa);
new Vue({
el: "#app",
name: "main",
//... etc
});
});
</script>
Update :
I also created an example on github to see it in action
https://github.com/nsssim/Vue-CDN-load-component
Sure you can, this is the way we are doing it in all our components of our app.
<template src="../templates/the_template.html"></template>
<script>
export default {
name: 'ComponentName',
props: {},
computed: {},
methods: {},
};
</script>
<style lang="scss">
#import '../styles/myscss_file';
</style>
Will need to add
runtimeCompiler: true
to your vue.config.js file. That's it.