How can I implement this using Vue.js? - javascript

I am new to Vue.js so I am not sure of the proper way to implement this task correctly. I am looking to build a slider that replaces an image every 4 seconds with a set Interval. Right now it works fine, but I want to use the full power of Vue.js for achieving this goal. I would also like to know how I should terminate the interval when it is unmounted, in order to avoid memory leaks.
<template>
<div class="trending col-4">
<div class="trending-div">
<h1 class="headline">Trending</h1>
<div class="trending-articles">
<div class="trending-articles-names" #click="changeIamge">
<p class="trending-articles-names-number activeButton">1</p>
<p class="trending-articles-names-articleTitle activeBold">Lorem ipsum </p>
</div>
<div class="trending-articles-names" #click="changeIamge">
<p class="trending-articles-names-number">2</p>
<p class="trending-articles-names-articleTitle">Lorem, ipsum </p>
</div>
<div class="trending-articles-names" #click="changeIamge">
<p class="trending-articles-names-number">3</p>
<p class="trending-articles-names-articleTitle">Lorem ipsum </p>
</div>
</div>
<div class="trending-carousel">
<div #click="pic" :style="{ backgroundImage: `url(path/to/image)`}" class="trending-carousel-image active"></div>
<div #click="pic" :style="{ backgroundImage: `url(path/to/image)`}" class="trending-carousel-image"></div>
<div #click="pic" :style="{ backgroundImage: `url(path/to/image)`}" class="trending-carousel-image"></div>
</div>
</div>
</div>
</template>
<script>
export default{
data: function() {
return {
counter: 0,
}
},
mounted: function() {
let carouselImages = document.querySelectorAll('.trending-carousel-image');
let buttons = document.querySelectorAll('.trending-articles-names-number');
let title = document.querySelectorAll('.trending-articles-names-articleTitle');
setInterval(() => {
carouselImages[this.counter].classList.remove("active")
buttons[this.counter].classList.remove("activeButton")
title[this.counter].classList.remove("activeBold")
if(this.counter === 3) {
carouselImages[0].classList.add("active");
buttons[0].classList.add("activeButton");
title[0].classList.add("activeBold");
this.counter = 0;
}else {
carouselImages[this.counter+1].classList.add("active");
buttons[this.counter+1].classList.add("activeButton");
title[this.counter+1].classList.add("activeBold");
this.counter++;
}
},4000);
}
}
</script>

When working with vue you should avoid directly manipulating the dom.
If you want to dynamically add / remove classes to an element you can use the object syntax for v-bind:class
If you need animations / transitions, you can use Vue transitions (transition / transition-group)
If you have many similar elements (e.g. .trending-articles-names in your example code), you can use v-for to create them for you
A very simple carousel thats purely implemented in vue might look like this:
(codepen is available here)
new Vue({
el: '#root',
data: function() {
return {
articles: [{
name: "Lorem Ipsum",
image: "https://via.placeholder.com/300x50.png/FF0000/FFFFFF?text=First"
}, {
name: "Lorem Ipsum 2",
image: "https://via.placeholder.com/300x50.png/00FF00/FFFFFF?text=Second"
}, {
name: "Lorem Ipsum 3",
image: "https://via.placeholder.com/300x50.png/0000FF/FFFFFF?text=Third"
}],
activeImage: 0,
interval: null
}
},
mounted() {
this.interval = setInterval(this.next, 5000);
},
destroyed() {
// remember to clear the interval once the component is no longer used
clearInterval(this.interval);
},
methods: {
prev() {
this.activeImage--;
if (this.activeImage < 0)
this.activeImage = this.articles.length - 1;
},
next() {
this.activeImage++;
if (this.activeImage >= this.articles.length)
this.activeImage = 0;
},
pic(article) {
console.log("CLICKED ON", article);
}
}
});
.carousel {
overflow: hidden;
width: 300px;
display: flex;
}
.carousel img {
transition: transform 0.5s ease;
}
button {
margin: 6px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="root">
<div class="trending col-4">
<div class="trending-div">
<div class="trending-articles">
<div v-for="(article, idx) in articles" :key="idx" class="trending-articles-names" #click="activeImage = idx">{{article.name}}</div>
</div>
<div class="carousel">
<img v-for="(article, idx) in articles" :key="idx" #click="pic(article)" :src="article.image" :style="{transform: `translateX(-${activeImage * 100}%)`}" />
</div>
<button type="button" #click="prev">PREV</button>
<button type="button" #click="next">NEXT</button>
</div>
</div>
</div>
I would suggest using a pre-made carousel component for production projects though.
There are several good libaries available for carousels in vue, to name a few:
Vuetify Carousels (Vuetify is a material design based vue framework)
vue-carousel (a lightwight carousel component)
bootstrap-vue carousel (based on boostrap)

Related

jquery doesn't work correctly when I call data with Fetch in VueJS?

Owlcarousel-slider works fine when I use below code.
HTML Code:
<div class="banner">
<div class="main-banner owl-carousel" id="homeage-data">
<div class="item" v-for="slider in sliders">
<div v-bind:class="slider.class_type"> <img v-bind:src="slider.image_src" alt="Electrro">
<div class="banner-detail">
<div class="container">
<div class="row">
<div class="col-md-3 col-3" v-if="slider.align_right"></div>
<div class="col-md-9 col-8">
<div class="banner-detail-inner">
<span class="slogan">{{ slider.label }}</span>
<h1 class="banner-title" v-html="slider.banner_title"></h1>
<span class="offer">{{ slider.banner_desc }}</span>
</div>
<a class="btn btn-color big" v-bind:href="slider.button_link" v-if="slider.button_np" target="_blank">{{ slider.button_text }}</a>
<a class="btn btn-color big" v-bind:href="slider.button_link" v-else-if="!slider.button_np">{{ slider.button_text }}</a>
</div>
<div class="col-md-3 col-4" v-if="!slider.align_right"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
JavaScript Code:
<script>
new Vue({
el: '#homeage-data',
data: {
sliders: [
{
"label": "Discover",
"banner_title": "Top Branded for <br>headphone",
"banner_desc": "best selling, and popular.",
"image_src": "images/banner1.jpg",
"button_text": "Shop Now!",
"button_link": "#link",
"class_type" : "banner-1",
"button_np": true,
"align_right": false
},
{
"label": "curved tv",
"banner_title": "Get latest TV models",
"banner_desc": "Get the top brands for TV",
"image_src": "images/banner2.jpg",
"button_text": "Shop Now!",
"button_link": "#link",
"class_type" : "banner-2",
"button_np": false,
"align_right": true
},
{
"label": "Premium",
"banner_title": "drone cameras",
"banner_desc": "The latest camera up to 30% off",
"image_src": "images/banner3.jpg",
"button_text": "Shop Now!",
"button_link": "#link",
"class_type" : "banner-3",
"button_np": true,
"align_right": false
}
]
},
});
</script>
Output Image: (Very Nice!)
But when I call the code remotely with Fetch, the image is broken.
JavaScript Code: (I uploaded the slider array to the api.php file and called it remotely.)
<script>
new Vue({
el: '#homeage-data',
data: {
sliders: [
]
},
created() {
fetch("/api/api.php")
.then((response) => { return response.json() })
.then((response) => {
this.sliders = response.sliders;
});
}
});
</script>
Output Image:
As I understand it, it cannot render JQuery code because it was added later.
No matter what I did, I couldn't find the right way.
I have not tried the code below. It didn't happen :(
<script>
new Vue({
el: '#homeage-data',
data: {
sliders: [
]
},
created() {
fetch("/api/api.php")
.then((response) => { return response.json() })
.then((response) => {
this.sliders = response.sliders;
});
$(".main-banner, #client").owlCarousel({
//navigation : true, Show next and prev buttons
items: 1,
nav: true,
slideSpeed : 300,
paginationSpeed : 400,
loop:true,
autoPlay: false,
dots: true,
singleItem:true,
nav:true
});
}
});
</script>
Demo of using VueOwlCarousel in Vue2, without npm:
Vue.use(VueOwlCarousel);
new Vue({
el: '#app',
data: () => ({
slides: 0,
options: {
autoplay: true,
autoplaySpeed: 800,
autoplayTimeout: 1800,
lazyLoad: true,
autoplayHoverPause: true
}
}),
mounted() {
setTimeout(() => {
this.slides = 7
}, 2000)
},
})
div.owl-carousel {
width: 400px;
height: 180px;
}
.owl-stage-outer {
height: 111px;
}
.loader {
height: 180px;
display: flex;
align-items: center;
width: 400px;
justify-content: center;
}
h2 {
color: #ddd;
}
body {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
margin: 0;
}
<script src="https://v2.vuejs.org/js/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-owl-carousel#2.0.3/dist/vue-owl-carousel.min.js"></script>
<div id="app">
<vue-owl-carousel :items="1"
v-if="slides"
v-bind="options">
<img v-for="s in slides"
:key="s"
:src="`https://placeimg.com/400/111/any?${s}`">
</vue-owl-carousel>
<div v-else class="loader">
<h2>Loading...</h2>
</div>
</div>
Personal advice: do not mix jQuery with Vue. You only want one source of truth for your DOM manipulations.
That's the underlying cause of your issue.
Note: Owl carousel can't be initialised with empty slides. For this reason, I've placed a v-if="slides" (or, if slides would have been an array, v-if="slides.length").
Also note Owl takes control over the carousel DOM element and, from that moment on, even if you change the slides array, it won't react to the change. If you wanted to update the slides, you'd need the owl instance, change its internal slides (which are built from the original DOM) and then call its internal .refresh() method.

How to conditionally append an element in a scoped Vue Component?

I am trying to create a Component for titles that can be edited when they get double clicked.
The Component takes the h-tag that should be used and the title as props and should produce a normal h-tag, that turns into an input field once double clicked.
This already works if there is only one title on the page, however once there are multiple Components used on one page, it breaks as the Component is not scoped properly. But I can't figure out how.
Here is the code:
<template>
<div class="edit-on-click">
<input
:class="sizeClass"
type="text"
v-if="edit"
v-model="editedTitle"
#blur="finishEdit"
#keyup.enter="finishEdit"
v-focus="true"
/>
<span v-show="!edit" #dblclick.prevent="edit = true"></span>
</div>
</template>
The mounted hook I can't figure out how to scope:
mounted() {
let node = document.createElement(this.size); // Takes h-tag (h1, h2 etc.)
let titleText = document.createTextNode(this.finalTitle); // Takes title
node.appendChild(titleText);
node.classList.add("editable-title");
// This breaks the code once there are multiple components in the document
document.getElementsByTagName("span")[0].appendChild(node);
},
How can I scope this in an efficient way? Thank you very much in advance!
Well, with Vue, you'll probably want to avoid creating DOM elements the "native" way whenever possible, as you might run into race condition where Vue is unaware of the existence of these elements which you probably want be reactive at some point in time (in your case, the <span> double-clicking).
What you could do instead, is perhaps to dynamically "switch between" these different headings with this <component> and the v-bind:is prop. Consider the following example:
Vue.component('EditableHeading', {
template: '#editable-heading',
props: {
size: {
type: String,
default: 'h1'
},
value: {
type: String,
required: true
}
},
data() {
return {
editing: false
}
},
methods: {
confirm(e) {
this.$emit('input', e.target.value);
this.close();
},
start() {
this.editing = true;
this.$nextTick(() => {
this.$el.querySelector('input[type="text"]').select();
});
},
close() {
this.editing = false;
}
}
})
new Vue({
el: '#app',
data: () => ({
titleList: [],
text: 'New Title',
size: 'h3'
}),
methods: {
addNewTitle() {
this.titleList.push({
text: this.text,
size: this.size
});
}
}
})
.edit-on-click {
user-select: none;
}
.heading-size {
margin-top: 1rem;
width: 24px;
}
p.info {
background-color: beige;
border: 1px solid orange;
color: brown;
padding: 4px 5px;
margin-top: 2rem;
}
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="app">
<editable-heading
v-for="(title, index) of titleList" :key="index"
v-model="title.text"
:size="title.size">
</editable-heading>
<div>
<label>
Heading size:
<input v-model="size" class="heading-size" />
</label>
</div>
<div>
<label>
Title:
<input v-model="text" />
</label>
</div>
<div>
<button #click="addNewTitle()">Add new title</button>
</div>
<p class="info">
[double-click]: Edit <br />
[enter]: Confirm <br />
[esc/mouseleave]: Cancel
</p>
</div>
<script id="editable-heading" type="text/x-template">
<div class="edit-on-click">
<input
type="text"
v-if="editing"
:value="value"
#blur="close"
#keydown.enter="confirm"
#keydown.esc="close" />
<component :is="size" v-else #dblclick="start">{{value}}</component>
</div>
</script>

Vue.js, change the Array items order and make that change in the DOM as well

In a Vue instance, I have an Array with the name of "block" that holds 4 values. I render this Array to the DOM with a v-for:
<div class="block" #click="shuffleArray()">
<div v-for="(number, index) in block">
<span :class="[`index--${index}`]">{{ number }}</span>
</div>
</div>
this creates a div with 4 spans inside, each of which has a class of "index--0", "index--1", etc.
When clicked, the values of the Array change order:
shuffleArray: function() {
const shifted = this.block.shift();
this.block.push( shifted );
}
While the values do change, they don't move in the actual DOM, how can I achieve that when clicked, the spans actually change place in the DOM? Each span has a style applied to it so I would like a visual representation that the values do change order:
span.index--0 {
background-color: tomato;
}
span.index--1 {
background-color: khaki;
}
span.index--2 {
background-color:lavenderblush;
}
span.index--3 {
background-color: lightcoral;
}
Maybe there is a CSS only solution that does not require DOM manipulation.
I recommend to use list tranisition in order to make that fancy like :
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#list-demo',
data: {
items: [1,2,3,4,5,6,7,8,9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
}
})
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active, .list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to /* .list-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateY(30px);
}
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="list-demo">
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>

Vue.js Change css style of child component on hover

I created a "box" component that I re-use several times. Each element has a #mouseenter event that the parent listens to. My goal is to change the border-color of the child element. Because I declared the from the parent with a loop I can't change only one of the childs properties but they all change
<template>
<div>
<div id="container">
<div id="row" v-for="i in 11" :key="i">
<div>
<box-component v-for="j in 7" :key="j" :color="getColor(i, j)" v-bind:borderColor="getBorder(i, j)" :row="i" :col="j" v-on:changeBorder="highlightBorder($event)"></box-component>
</div>
</div>
</div>
</div>
</template>
The problem is with this part:
v-bind:borderColor="getBorder(i, j)"
Because i and j have changed I don't know how to only affect one child.
I know that I could remove the loop and copy paste the same code but there must be another solution to this. I also know that this particular example could be implemented directly on the child component but I need to be able to do it from the parent.
You can do it this way:
<box-component v-on:change-border="highlightBorder(i, j)"></box-component>
From the docs:
Unlike components and props, event names will never be used as variable or property names in JavaScript, so there’s no reason to use camelCase or PascalCase. Additionally, v-on event listeners inside DOM templates will be automatically transformed to lowercase (due to HTML’s case-insensitivity), so v-on:myEvent would become v-on:myevent – making myEvent impossible to listen to.
For these reasons, we recommend you always use kebab-case for event names.
Interactive demo
Vue.component('parent-component', {
template: '#parent-component',
data() {
return {
defaultStyles: {
color: '#555',
borderColor: '#bbb'
},
highlightedStyles: {
color: '#f50',
borderColor: 'orange'
},
highlighted: {x: null, y: null}
};
},
methods: {
isHighlighted(x, y) {
return x === this.highlighted.x && y === this.highlighted.y;
},
getStyles(x, y) {
return this.isHighlighted(x, y) ? this.highlightedStyles : this.defaultStyles;
},
getColor(x, y) {
return this.getStyles(x, y).color;
},
getBorder(x, y) {
return this.getStyles(x, y).borderColor;
},
highlightBorder(x, y) {
this.highlighted = {x, y};
}
}
});
Vue.component('box-component', {
template: '#box-component',
props: ['color', 'borderColor']
});
var vm = new Vue({
el: '#app'
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.row:after {
content: '';
display: block;
clear: both;
}
.box {
float: left;
padding: .5em;
border-width: 4px;
border-style: solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
<div id="app">
<parent-component></parent-component>
</div>
<template id="parent-component">
<div>
<div id="container">
<div class="row" v-for="y in 11" :key="`row-${y}`">
<div>
<box-component
v-for="x in 7"
:key="`cell-${x}`"
:color="getColor(x, y)"
:border-color="getBorder(x, y)"
:col="x" :row="y"
#change-border="highlightBorder(x, y)"
></box-component>
</div>
</div>
</div>
</div>
</template>
<template id="box-component">
<div
class="box"
:style="{background: color, borderColor: borderColor}"
#mouseenter="$emit('change-border')"
></div>
</template>

Vue Same class divs, expand only the child, without (this) parent

All children and parents div are with the same class. I'd attached v-on:clickmethod to toggled only the child one of the clicked parent, not all children. If I add (this) the method doesn't work. Thank you.
<div class="expand-box" v-on:click="textToggle()">
<div class="expand-title">Lorem ipsum</div>
<div class="expand-text">Child</div>
</div>
<div class="expand-box" v-on:click="textToggle()">
<div class="expand-title">Lorem Ipsum</div>
<span class="expand-text">Child.</span>
</div>
mounted(){
this.hideText();
},
methods: {
hideText: function() {
$(".expand-text").hide();
},
textToggle: function() {
$(".expand-text").toggle(300);
}
}
First when you are using v-on:click you need set function name like v-on:click="textToggle" if you are not passing parameters.
It's not recommanded to use jQuery alond side Vuejs for animation and dom manipulation,
you need to use css or Vuejs animation :
CSS ANIMATION :
https://codepen.io/khofaai/pen/qJXbbr
http://optimizely.github.io/vuejs.org/guide/transitions.html
.msg {
transition: all .3s ease;
height: 30px;
padding: 10px;
background-color: #eee;
overflow: hidden;
}
.msg.v-enter, .msg.v-leave {
height: 0;
padding: 0 10px;
opacity: 0;
}
<p class="msg" v-if="show" v-transition>Hello!</p>
You can pass $event as a parameter of textToggle so that it becomes v-on:click="textToggle($event)" then your method will use this reference to toggle that element using,
textToggle: function(elem) {
$(elem.target).toggle(300);
}
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
mounted() {
this.hideText();
},
methods: {
hideText: function() {
$(".expand-text").hide();
},
textToggle: function(elem) {
$(elem.target).toggle(300);
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue"></script>
<div id='app'>
<div class="expand-box" v-on:click="textToggle($event)">
<div class="expand-title">Lorem ipsum</div>
<div class="expand-text">Child</div>
</div>
<div class="expand-box" v-on:click="textToggle($event)">
<div class="expand-title">Lorem Ipsum</div>
<span class="expand-text">Child.</span>
</div>
</div>
You can also use ref in your HTML element to get the reference of that element like:
HTML
<div class="expand-box" v-on:click="textToggle('el1')" ref="el1">
<div class="expand-title">Lorem ipsum</div>
<div class="expand-text">Child</div>
</div>
<div class="expand-box" v-on:click="textToggle('el2')" ref="el2">
<div class="expand-title">Lorem Ipsum</div>
<span class="expand-text">Child.</span>
</div>
METHOD
textToggle: function(refId) {
this.$refs[refId].toggle(300);
}

Categories

Resources