Vuejs - Grab items passed to component template - vue.js

So I have my markup looking like this:
<slider>
<img src="{{ gallery_image('HM722_Silver_Creek_9978.jpg', 'full') }}" alt="HM722 Silver Creek" style="margin-top:-15%;" />
<img src="{{ gallery_image('HM722_Silver_Creek_9978.jpg', 'full') }}" alt="HM722 Silver Creek" style="margin-top:-15%;" />
<img src="{{ gallery_image('HM722_Silver_Creek_9978.jpg', 'full') }}" alt="HM722 Silver Creek" style="margin-top:-15%;" />
<img src="{{ gallery_image('HM722_Silver_Creek_9978.jpg', 'full') }}" alt="HM722 Silver Creek" style="margin-top:-15%;" />
</slider>
It's using the slider component which you can see below:
var Slider = Vue.component('slider', {
template: '#homepage-slideshow',
replace: true,
data: {
current: 1,
speed: 1000,
margin: 0,
slideLength: 4
},
props: {
sliderWidth: 800,
slideWidth: 800,
height: 500,
dataSlide: 1
},
ready: function() {
this.sliderWidth = screen.width * 4;
this.slideWidth = screen.width;
this.height = screen.height;
console.log( this.img );
},
methods: {
thumbnailClick: function(e) {
var slide = $(e.target).data('slide');
var index = $('.slide').index( $('#' + slide) );
this.current = index + 1;
this.margin = this.slideWidth * (index);
this.animateSlides();
},
animateSlides: function() {
var self = this;
$('.slides').animate({
'margin-left': '-' + self.margin
}, self.speed, function() {
if( self.current === self.slideLength )
{
self.current = 1;
$('.slides').css('margin-left', 0);
}
});
}
}
});
The methods are still a mess so disregard those, but I want to try and do a v-repeat of the img tags being passed in since those will be looped through with a #foreach functionality. So there won't always be a definite 4.. The data attribute has a slideLength of 4 and the template has 4 areas, but what I REALLY want is to loop through whatever images are passed into the template.
Thanks for any direction.

I seemed to have gotten past this by passing a prop to the component like so:
<slider img-count="4">
And then accessing it with this.imgCount in my component. I don't know why I didn't think of this before!
Then in your component template after setting this.count = this.imgCount in your ready method:
<script type="text/x-template" id="homepage-slideshow">
<div class="slides" style="width:#{{ sliderWidth }}px">
<article v-repeat="count" id="slide#{{ $index }}" class="slide" style="width:#{{ slideWidth }}px;height:#{{ height }}px">
<content select="img:nth-of-type(#{{ $index + 1 }})"></content>
</article>
</div>
<div class="thumbnails">
<div class="thumbnail-wrapper container">
<img v-repeat="count" src="/img/thumbnail.png" data-slide="slide#{{ $index }}" v-on="click: thumbnailClick" style="height:#{{ thumbnailHeight }}px" />
</div>
</div>
</script>

Related

How to access a function variable as a data property in Vue

I have this accordion with three arrows. When you click on the arrow it should transform 180deg. I want to have a function that takes the parameter set in the function and tells the data property to change to zero.
I've tried
let vm = this;
vm.$data.arrowOne = 0;
And this[arrow] = 0,
And this.arrow = 0
And it's not working.
Here is my code.
<div class="uk-accordion-ekstra">
<ul uk-accordion="multiple: true">
<li>
<a #click="rotate(arrowOne)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowOne + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
<li>
<a #click="rotate(arrowTwo)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowTwo + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
<li>
<a #click="rotate(arrowThree)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowThree + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
</ul>
</div>
And VUE
new Vue({
el: '.uk-accordion-ekstra',
data: {
arrowOne: 180,
arrowTwo: 180,
arrowThree: 180
},
methods: {
arrow: function(arrow) {
if(this.arrow = 0) {
return this.arrow = 180;
}
}
}
});
I have succesfully managed to do it with a switch. But it aren't as beautiful. And im unsure of what i don't get. So help is appreciated.
You have a structural and a scope problem.
You are referencing this.arrow in a method called arrow, and you pass an argument called arrow - try to differentiate with the names, or you can get messed up
You are repeating stuff that you don't need to. Vue is great for creating components for the smallest of elements - you can use it to make your code shorter, more readable and more effective.
The snippet below makes everything easier:
you have one arrow per component, and if you solve the rotation problem in one place, then it's solved everywhere, and the arrow referenced always connects to the component you edit
you can encapsulate your arrow rotation into one component - no need to "litter" the Vue instance with such small animation
I added #click.prevent that's like preventDefault(), so there won't be a jump to the top when you click an <a></a>
Vue.component('accordion', {
props: ['title', 'text'],
template: '<div><a #click.prevent="rotateArrow()" class="uk-accordion-title" href="#">{{ title }} <i class="fa fa-arrow-circle-o-up" :style="`transform: rotate(${rotation}deg)`"></i></a><div class="uk-accordion-content"><p>{{ text }}</p></div></div>',
data() {
return {
rotation: 0
}
},
methods: {
rotateArrow() {
this.rotation = !this.rotation ? 180 : 0
}
}
})
new Vue({
el: '.uk-accordion-ekstra',
data: {
accordionItems: [{
title: 'Headline 1',
text: 'Text 1'
},
{
title: 'Headline 2',
text: 'Text 2'
},
{
title: 'Headline 3',
text: 'Text 3'
},
]
}
});
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="uk-accordion-ekstra">
<ul uk-accordion="multiple: true">
<li v-for="accordionItem in accordionItems" :key="accordionItem.title">
<accordion :title="accordionItem.title" :text="accordionItem.text"></accordion>
</li>
</ul>
</div>

Dynamic v-model problem on nuxt application

I am trying to create a dynamic v-model input. All seems well except for the following.
The on click event that triggers the checkAnswers method only works if you click out of the input then click back into the input and then press the button again. It should trigger when the button is pressed the first time.
Does anyone have any ideas? Thanks in advance.
<template>
<div class="addition container">
<article class="tile is-child box">
<div class="questions">
<ul v-for="n in 5">
<li>
<p>{{ randomNumberA[n] }} + {{ randomNumberB[n] }} = </p>
<input class="input" type="text" maxlength="8" v-model.number="userAnswer[n]">
<p>{{ outcome[n] }}</p>
</li>
</ul>
</div>
<div class="button-container">
<button #click="checkAnswers" class="button">Submit Answer</button>
</div>
</article>
</div>
</template>
<script>
export default {
data() {
return {
randomNumberA: [] = Array.from({length: 40}, () => Math.floor(Math.random() * 10)),
randomNumberB: [] = Array.from({length: 40}, () => Math.floor(Math.random() * 10)),
userAnswer: [],
outcome: [],
}
},
methods: {
checkAnswers() {
for (var i = 0; i < 6; i++) {
if (this.userAnswer[i] === (this.randomNumberA[i] + this.randomNumberB[i])) {
this.outcome[i] = 'Correct';
} else {
this.outcome[i] = 'Incorrect';
}
}
}
}
}
</script>
You have some basic issues with your use of the template syntax here. According to the vue docs:
One restriction is that each binding can only contain one single
expression, so the following will NOT work: {{ var a = 1 }}
If you want to populate your arrays with random numbers you would be better calling a function on page mount. Something like this:
mounted() {
this.fillArrays()
},
methods: {
fillArrays() {
for (let i = 0; i < 5; i++) {
this.randomNumberA.push(Math.floor(Math.random() * 10))
this.randomNumberB.push(Math.floor(Math.random() * 10))
this.answer.push(this.randomNumberA[i] + this.randomNumberB[i])
}
}
}
Then you can use template syntax to display your arrays.
It looks like you are setting up a challenge for the user to compare answers so I think you'd be better to have a function called on input: Something like:
<input type="whatever" v-model="givenAnswer[n-1]"> <button #click="processAnswer(givenAnswer[n-1])>Submit</button>
Then have a function to compare answers.
Edit
I have basically rewritten your whole page. Basically you should be using array.push() to insert elements into an array. If you look at this you'll see the randomNumber and answer arrays are populated on page mount, the userAnswer array as it is entered and then the outcome on button click.
<template>
<div>
<div >
<ul v-for="n in 5">
<li>
<p>{{ randomNumberA[n-1] }} + {{ randomNumberB[n-1] }} = </p>
<input class="input" type="text" maxlength="8" v-model.number="userAnswer[n-1]">
<p>{{ outcome[n-1] }}</p>
</li>
</ul>
</div>
<div class="button-container">
<button #click="checkAnswers" class="button">Submit Answers</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
randomNumberA: [],
randomNumberB: [],
answer: [],
userAnswer: [],
outcome: [],
}
},
mounted() {
this.fillArrays()
},
methods: {
checkAnswers() {
this.outcome.length = 0
for (var i = 0; i < 6; i++) {
if (this.userAnswer[i] === this.answer[i]) {
this.outcome.push('Correct');
} else {
this.outcome.push('Incorrect');
}
}
},
fillArrays() {
for (let i = 0; i < 5; i++) {
this.randomNumberA.push(Math.floor(Math.random() * 10))
this.randomNumberB.push(Math.floor(Math.random() * 10))
this.answer.push(this.randomNumberA[i] + this.randomNumberB[i])
}
}
}
}
</script>

Vue: How to switch between displaying input and label with v-if

I need to be able to switch between an input field and a label. When the button "Add Location" is clicked (which create a new div), the input field must be visible. But when the div "Expandable" is maximized it must be hidden and the label visible instead!
The input field should only be visible right after the mentioned button is clicked, else the label has to take its place. What is the best way to achieve this? I was thinking about using some sort of toggle since I am using that in other places.
The label and the input field is placed in the div class "switch".
You can also see the code in this jsFiddle!
Html
<div id="lotsOfDivs">
<addingdivs></addingdivs>
</div>
Vue
var gate = 0;
Vue.component('addingdivs', {
template: `
<div>
<div id="header">
<button class="addDiv" type="button" #click="createDiv">ADD LOCATION</button>
</div>
<div class="parent" v-for="div in divs" :style=" div.height ? { 'height': div.height }: null">
<div class="big" v-if="div.expanded" :key="'expanded' + div.id">
<div class="switch">
<input type="text" v-if="inputFieldInfo">
<label class="propertyLabel" v-else>
<div class="firstChild">
<button class="done" #click="increaseLimit">INCREASE</button>
</div>
<div class="secondChild">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
<div class="small" v-else :key="'collapsed' + div.id">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
</div>
`,
data: function() {
return {
gate: gate,
height: "",
count: 0,
locationsArr: ["one", "two", "three"],
divs: [],
InputFieldInfo: false
}
},
methods: {
expand: function(div) {
if (div.expanded) {
div.expanded = false
this.height = ''
} else {
div.expanded = true
this.height = '7vh'
}
},
createDiv: function() {
if (this.count <= gate) { // Here you can decide how many divs that will be generated
// this.count++;
this.divs.push({
id: this.count,
expanded: true,
inputFieldInfo: true,
height: '',
});
this.count++
}},
increaseLimit: function() {
// Here you can increase the number of divs that it's possible to generate
gate++;
}
}
});
new Vue({
el: '#lotsOfDivs',
});
The template had a few compilation errors:
The <label> needs a closing tag (and text content to be useful)
The <div class="big"> needs a closing tag
The v-if was bound to inputFieldInfo, but that variable was declared as InputFieldInfo (note the uppercase I), but based on your behavior description, this field should be unique per location container, so a single data property like this wouldn't work (if I understood your description correctly).
Each location container should have a variable to contain the location name (e.g., locationName) and another variable to contain the show/hide Boolean for the <input> and <label> (i.e., inputFieldInfo):
createDiv: function() {
this.divs.push({
// ...
inputFieldInfo: true,
locationName: ''
});
}
Then, we could bind div.inputFieldInfo and div.locationName to the <input>. We bind to v-model so that the user's text is automatically reflected to the div.locationName variable:
<input v-if="div.inputFieldInfo" v-model="div.locationName">
The <label>'s content should be div.locationName so that it contains the text from the <input> when shown:
<label class="propertyLabel" v-else>{{div.locationName}}</label>
To switch the <input> with the <label> when the expand-button is clicked, we update expand() to set div.inputFieldInfo to false but only when div.locationName is not empty (this gives the user a chance to revisit/re-expand the container to fill in the location later if needed):
expand: function(div) {
if (div.expanded) {
div.expanded = false
if (div.locationName) {
div.inputFieldInfo = false
}
// ...
updated jsfiddle
You had some missing closing tags and an error with InputFieldInfo, it should have a lowercase i.
var gate = 0;
Vue.component('addingdivs', {
template: `
<div>
<div id="header">
<button class="addDiv" type="button" #click="createDiv">ADD LOCATION</button>
</div>
<div class="parent" v-for="div in divs" :style=" div.height ? { 'height': div.height }: null">
<div class="big" v-if="div.expanded" :key="'expanded' + div.id">
<div class="switch">
<input type="text" v-if="inputFieldInfo">
<label class="propertyLabel" v-else>Label</label>
<div class="firstChild">
<button class="done" #click="increaseLimit">INCREASE</button>
</div>
<div class="secondChild">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
</div>
<div class="small" v-else :key="'collapsed' + div.id">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
</div>
`,
data: function() {
return {
gate: gate,
height: "",
count: 0,
locationsArr: ["one", "two", "three"],
divs: [],
inputFieldInfo: true
}
},
methods: {
expand: function(div) {
this.inputFieldInfo = false
if (div.expanded) {
div.expanded = false
this.height = ''
} else {
div.expanded = true
this.height = '7vh'
}
},
createDiv: function() {
this.inputFieldInfo = true
if (this.count <= gate) { // Here you can decide how many divs that will be generated
// this.count++;
this.divs.push({
id: this.count,
expanded: true,
inputFieldInfo: true,
height: '',
});
this.count++
}
},
increaseLimit: function() {
// Here you can increase the number of divs that it's possible to generate
gate++;
}
}
});
new Vue({
el: '#lotsOfDivs',
});
You just basically toggle the inputFieldInfo data, whenever each button is pressed.
You can do that by using toggle variable like this
Vue.component('addingdivs', {
template: `
<div>
<div>
<input type="text" v-if="takeinput">
<label v-if="!takeinput">
<button #click="toggleInput()">
</div>
</div>
`,
data: function() {
return {
takeinput:true,
}
},
methods: {
toggleInput: function(){
let vm = this;
vm.takeinput = ( vm.takeinput == true) ? false : true
}
}
});
new Vue({
el: '#lotsOfDivs',
});
In this example, we are just toggeling value of takeinput on click , so according the value either label or input will be showed.
This is very basic exmpale. But you can extend it as your need

vue.js - Dynamically render items on scroll when visible

I have list of 100+ items and rendering takes too much time. I want to show just the once that are visible, and rest on scroll.
What's the best approach?
I have this snippet below, but the vue.set() isn't working.
var dbItems = [{name: 'New item'}, {name:'Another'}, {name:'Third'}];
var app = new Vue({
el: '#app',
data: {
// if I put items : dbItems, then for some reason the Vue.set() doesn't work!!
items : [],
},
methods: {
init: function () {
this.items = dbItems; // we add all items
},
makeItemVisible : function(id) {
console.log("Making visible #"+id);
this.items[id].show = 1;
Vue.set(this.items, id, this.items[id]);
}
}
});
app.init();
app.makeItemVisible(1); // this works
$(document).on('scroll', function(){
// function to show elements when visible
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app" v-cloak>
<button v-on:click="makeItemVisible(0)">MAKE VISIBLE - This button doesn't work</button>
<div class="items" v-show="items.length">
<!-- I dont know why, but (key, item) had to be switched compared to VUE documentation! -->
<div v-for="(key, item) in items">
<div v-if="item.show" style="border:2px solid green;height:700px">
You can see me: {{ item.name }} | ID: {{ key }}
</div>
<div class="item-blank" data-id="{{ key }}" v-else style="border:2px solid red;height:700px">
{{ item.name }} invisible {{ key }}
</div>
</div>
</div>
</div>
Solved.
Edit: This Vue.js is only useable in Chrome... otherwise it is incredibly slow (Firefox is slowest), it works better when loading the whole document in HTML at once.
var dbItems = [{name: 'New item'}, {name:'Another'}, {name:'Third'}];
var app = new Vue({
el: '#app',
data: {
items : dbItems
},
methods: {
makeItemVisible : function(id) {
console.log("Making visible #"+id);
Vue.set(this.items[id], 'show', 1);
}
}
});
function isScrolledIntoView(elem)
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return (elemTop <= docViewBottom && elemTop >= docViewTop) || (elemBottom >= docViewTop && elemBottom <= docViewBottom);
}
var fn = function(){
$('.item-blank').each(function(){
if(isScrolledIntoView(this)) {
app.makeItemVisible($(this).attr('data-id'));
}
});
};
$(window).scroll(fn);
fn(); // because trigger() doesn't work
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app" v-cloak>
<div class="items" v-show="items.length">
<div v-for="(item, index) in items">
<div v-if="item.show" style="border:2px solid green;height:700px">
You can see me: {{ item.name }} | ID: {{ index }}
</div>
<div class="item-blank" :data-id="index" v-else style="border:2px solid red;height:700px;position:relative;">
{{ item.name }} invisible {{ index }}
</div>
</div>
</div>
</div>

Vue.js data value not updating when adding new value

When new bars value are set, the data is not updated. Please check below;
I'm planning to do dynamic bars and buttons. Once the buttons are clicked, the new values will be updated to bars values.
Here is the link
HTML
<h1><span>Vue.js</span> Progress Bar</h1>
<div id="app">
<div v-for="(index, bar) in bars" class="shell">
<div class="bar" :style="{ width: bar + '%' }" .>
<span>{{ bar }}%</span>
<input type="radio" id="radio-bar" value="{{ index }}" v-model="picked" v-on:change="set(index)">
</div>
</div>
<span v-for="(index, button) in buttons">
<button value="{{ button }}" #click.prevent="makeProgress(button)">{{ button }}</button>
</span>
<br>
<p>Selected Bar: {{ picked }}</p>
<p>Button Value: {{ buttonVal }}</p>
</div>
JS
var dataURL = 'http://pb-api.herokuapp.com/bars';
var vm = new Vue({
el: "#app",
data: {
maxColor: "#F22613",
bars: [],
buttons: [],
limit: 0,
selectedBar: 0,
buttonVal: 0
},
methods: {
fetchData: function(params) {
this.$http.get(dataURL, function(data) {
this.$set('bars', data.bars);
this.$set('buttons', data.buttons);
this.$set('limit', data.limit);
console.log(params);
});
},
set: function(index) {
this.selectedBar = index;
},
makeProgress: function(button) {
var self = this;
self.buttonVal += button;
this.reachMax();
Vue.set(this.bars, 0, 55);
},
reachMax() {
if(this.buttonVal >= this.limit){
alert('Reached Limit' + this.limit)
}
}
},
created: function() {
this.fetchData();
}
});