use vanilla-lazyload with vuejs - vue.js

I'm pretty new in Vue-js and I'm trying to use this.
My App is not a SPA and I'm also working with Laravel.
I've tried this and it works fine:
const app = new Vue({
el: '#app',
mounted() {
this.myLazyLoad = new LazyLoad({
elements_selector: '.lazy',
class_loaded: 'lazy-loaded',
load_delay: 500, //adjust according to use case
threshold: 100, //adjust according to use case,
callback_enter: function(el) {
console.log(el.getAttribute("data-src"));
}
});
}
});
<img data-src="{{ $featuredItem->anime->getPortraitImg()}}"
class="lazy img-fluid" src="/img/placeholder-anime.jpg">
But there is a problem when I try to use the lazyload in a Component.
For example:
export default {
mounted() {
console.log('Component mounted.');
console.log(Vue.prototype.LazyLoad);
this.myLazyLoad = new LazyLoad({
// elements_selector: '.lazyx',
container: document.getElementById('lazyContainer')
});
// myLazyLoad.loadAll();
},
data() {
return {
episodes: {},
currentPage: 2
}
},
methods: {
loadMoreEpisodes() {
let uri = '/api/v1/episodes?page=' + this.currentPage;
this.axios.get(uri).then(response => {
console.log(response.data.data);
if (this.episodes.length === undefined) {
this.episodes = response.data.data;
} else {
this.episodes = this.episodes.concat(response.data.data);
}
this.myLazyLoad.update();
this.myLazyLoad.loadAll();
});
}
}
}
The new data inserted by axios is not recognized by the lazyload plugin.
I'm using this.myLazyLoad.update(); as stated in the documentation, but I'm not able to get it to work. Any suggestions?

I think DOM is not updated when you call update() method. Can you try using $nextTick?
this.$nextTick(() => {
this.myLazyLoad.update()
})

Related

Vue2 create component based on data

I want to create a component based on ajax api response or data which include:
template
data
methods - there may be several methods
Remark: response or data is dynamic and it is not saved in file.
I have tried to generate and return result like :
<script>
Vue.component('test-component14', {
template: '<div><input type="button" v-on:click="changeName" value="Click me 14" /><h1>{{msg}}</h1></div>',
data: function () {
return {
msg: "Test Componet 14 "
}
},
methods: {
changeName: function () {
this.msg = "mouse clicked 14";
},
}
});
</script>
and do compile above code :
axios.get("/api/GetResult")
.then(response => {
comp1 = response.data;
const compiled = Vue.compile(comp1);
Vue.component('result-component', compiled);
})
.catch(error => console.log(error))
I got error on Vue.compile(comp1) -
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as
<script>, as they will not be parsed.
Thanks in advance
Your Api should return a JSON with every property required by a Vue component (name, data, template, methods), note that methods needs to be converted into an actual js function (check docs about that)
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
data() {
return {
apiComponent: { template: '<div>Loading!</div>' }
};
},
methods: {
loadApiComponent() {
setTimeout(() => {
this.buildApiComponent(JSON.parse('{"name":"test-component14","template":"<div><input type=\\\"button\\\" v-on:click=\\\"changeName\\\" value=\\\"Click me 14\\\" /><h1>{{msg}}</h1></div>","data":{"msg":"Test Componet 14 "},"methods":[{"name":"changeName","body":"{this.msg = \\\"mouse clicked 14\\\";}"}]}'));
}, 2000);
},
buildApiComponent(compObject) {
const {
name,
template,
data,
methods
} = compObject;
const compiledTemplate = Vue.compile(template);
this.apiComponent = {
...compiledTemplate,
name,
data() {
return { ...data
}
},
methods: methods.reduce((c, n) => {
c[n.name] = new Function(n.body);
return c;
}, {})
};
}
},
mounted() {
this.loadApiComponent();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component :is="apiComponent" />
</div>

Reactive localStorage object across browser tabs using Vue.js

I'm trying to make a localStorage object reactive, so that if it updates, it will also update in other tabs using that same object. I'm using Vue.js and have tried to create a computed property that returns the localStorage object, but this doesn't work. How can I make the object reactive and make it update in other browser tabs also?
computed: {
localStorageObject() {
return JSON.parse(localStorage.getItem('object'));
}
}
#Matthias has the start of an answer, but it won't react to changes in other tabs. To do that, you can handle the StorageEvent. Here's a rough sketch that you could enhance as desired.
const app = new Vue({
el: '#app',
data: {
name: ''
},
methods: {
onStorageUpdate(event) {
if (event.key === "name") {
this.name = event.newValue;
}
}
},
mounted() {
if (localStorage.name) {
this.name = localStorage.name;
}
window.addEventListener("storage", this.onStorageUpdate);
},
beforeDestroy() {
window.removeEventListener("storage", this.onStorageUpdate);
},
watch: {
name(newName) {
localStorage.name = newName;
}
}
});
There is an example of how to achieve this on the Vue.js cookbook page: https://v2.vuejs.org/v2/cookbook/client-side-storage.html
In the example, a name is stored in local Storage.
const app = new Vue({
el: '#app',
data: {
name: ''
},
mounted() {
if (localStorage.name) {
this.name = localStorage.name;
}
},
watch: {
name(newName) {
localStorage.name = newName;
}
}
});

this.$http.get is not working inside methods vue js

I am working with Laravel + spark + vue js.
Blade file
<draggable class="col-md-12" :list="emergencies" :element="draggableOuterContainer" #end="onEnd">
Js file
import draggable from 'vuedraggable'
module.exports = {
data() {
return {
emergencies:[]
};
},
components: {
draggable,
},
created() {
this.getEmergencies();
},
methods: {
getEmergencies() {
this.$http.get('/ajax-call-url')
.then(response => {
this.emergencies = response.data;
});
},
onEnd: function(evt){
var counter = 1;
this.emergencies.forEach(function(user, index) {
this.$http.get('/ajax-call-url/')
.then(response => {
});
counter++;
});
}
}
};
Here I have drag and Drop, On Drop, I call "onEnd" function and getting following error.
TypeError: this is undefined
Here this.emergencies.forEach is working but it is giving error on this.$http.get
Any suggestions, what can be the solutions?
Instead of using function syntax, use arrow functions, as scope of this changes inside function:
onEnd: function(evt){
var counter = 1;
this.emergencies.forEach((user, index) => {
this.$http.get('/ajax-call-url/')
.then(response => {
});
counter++;
});
}
Check this for explanation.

VueJS: Setting data initially based on http response

So I have a template .vue file:
<template>
<div id="app">
<textarea v-model="input" :value="input" #input="update"></textarea>
<div v-html="compiledMarkdown"></div>
</div>
</template>
<script>
var markdown = require('markdown').markdown;
export default {
name: 'app',
data() {
return {
input: '# Some default data'
}
},
mounted: function () {
this.$nextTick(function () {
this.$http.get(window.location.pathname + '/data').then((response) => {
this.input = response.body.markdown;
}) })
},
computed: {
compiledMarkdown: function() {
this.$http.post(window.location.pathname, {
"html": markdown.toHTML(this.input)}).then(function() {
},function() {
});
return markdown.toHTML(this.input);
}
},
methods: {
update: function(e) {
this.input = e.target.value
}
}
}
</script>
In the mounted function I am trying to set input equal to the response of an HTTP request, but when you view this file this.input is still the same as it was initially declared. How can I change this.input inside the compiledMarkdown function to be this.input in the mounted function. What other approaches might I take?
You can not call a async method from a computed property, you can use method or watcher to run asynchronous code, from docs
This is most useful when you want to perform asynchronous or expensive operations in response to changing data.
You have to ran that relevant code when input changes, like following:
var app = new Vue({
el: '#app',
data: {
input: '# Some default data',
markdown : ''
},
methods: {
fetchSchoolData: function (schoolId) {
var url = this.buildApiUrl('/api/school-detail?schoolId=' + schoolId);
this.$http.get(url).then(response => {
this.schoolsListData = response.data;
}).catch(function (error) {
console.log(error);
});
},
},
mounted: function () {
this.$nextTick(function () {
this.$http.get(window.location.pathname + '/data').then((response) => {
this.input = response.body.markdown;
})
})
},
watch: {
// whenever input changes, this function will run
input: function (newInput) {
this.$http.post(window.location.pathname, {
"html": markdown.toHTML(this.input)}).then(function() {
},function() {
this.markdown = markdown.toHTML(this.input);
});
}
},
Have a look at my similar answer here.

Dynamic html elements in Vue.js

How is it possible to add elements dynamically to the content? Example below:
<template>
{{{ message | hashTags }}}
</template>
<script>
export default {
...
filters: {
hashTags: function(value) {
// Replace hash tags with links
return value.replace(/#(\S*)/g, '<a v-on:click="someAction()">#$1</a>')
}
}
}
</script>
Problem is that if I press the link no action will fire. Vue do not see new elements.
Update:
Based on this answer, you can do a similar dynamic-template component in Vue 2. You can actually set up the component spec in the computed section and bind it using :is
var v = new Vue({
el: '#vue',
data: {
message: 'hi #linky'
},
computed: {
dynamicComponent: function() {
return {
template: `<div>${this.hashTags(this.message)}</div>`,
methods: {
someAction() {
console.log("Action!");
}
}
}
}
},
methods: {
hashTags: function(value) {
// Replace hash tags with links
return value.replace(/#(\S*)/g, '<a v-on:click="someAction">#$1</a>')
}
}
});
setTimeout(() => {
v.message = 'another #thing';
}, 2000);
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="vue">
<component :is="dynamicComponent" />
</div>
Vue bindings don't happen on interpolated HTML. You need something Vue sees as a template, like a partial. However, Vue only applies bindings to a partial once; you can't go back and change the template text and have it re-bind. So each time the template text changes, you have to create a new partial.
There is a <partial> tag/element you can put in your HTML, and it accepts a variable name, so the procedure is:
the template HTML changes
register new partial name for the new template HTML
update name variable so the new partial is rendered
It's a little bit horrible to register something new every time there's a change, so it would be preferable to use a component with a more structured template if possible, but if you really need completely dynamic HTML with bindings, it works.
The example below starts out with one message, link-ified as per your filter, and after two seconds, changes message.
You can just use message as the name of the partial for registering, but you need a computed that returns that name after doing the registering, otherwise it would try to render before the name was registered.
var v = new Vue({
el: 'body',
data: {
message: 'hi #linky'
},
computed: {
partialName: function() {
Vue.partial(this.message, this.hashTags(this.message));
return this.message;
}
},
methods: {
someAction: function() {
console.log('Action!');
},
hashTags: function(value) {
// Replace hash tags with links
return value.replace(/#(\S*)/g, '<a v-on:click="someAction()">#$1</a>')
}
}
});
setTimeout(() => {
v.$set('message', 'another #thing');
}, 2000);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<partial :name="partialName"></partial>
I just learned about $compile, and it seems to fit your need very nicely. A very simple directive using $compile avoids all the registrations.
Vue.directive('dynamic', function(newValue) {
this.el.innerHTML = newValue;
this.vm.$compile(this.el);
});
var v = new Vue({
el: 'body',
data: {
message: 'hi #linky'
},
computed: {
messageAsHtml: function() {
return this.message.replace(/#(\S*)/g, '<a v-on:click="someAction()">#$1</a>');
}
},
methods: {
someAction: function() {
console.log('Action!');
}
}
});
setTimeout(() => {
v.$set('message', 'another #thing');
}, 2000);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div v-dynamic="messageAsHtml"></div>
In Vue.js 2 it's easier:
new Vue({
...,
computed: {
inner_html() {
return ...; // any raw html
},
},
template: `<div v-html='inner_html'></div>`,
});
The best solution I found which works fine with custom html is looks like this, it's like you kind of create new component each times the html property changes. No actually one did this, we just use computed property for creating new component.
That is how it looks:
new Vue({
el: "#root",
data: {
value: '',
name: 'root',
htmlData: '<div><input #input="onInputProxy($event)" ' +
'v-model="value" ' +
'v-for="i in 3" ' +
':ref="`customInput${i}`"></div>'
},
computed: {
// our component is computed property which returns the dict
htmlDataComponent () {
return {
template: this.htmlData, // we use htmlData as template text
data() {
return {
name: 'component',
value: ''
}
},
created () {
// value of "this" is formComponent
console.log(this.name + ' created');
},
methods: {
// proxy components method to parent method,
// actually you done have to
onInputProxy: this.onInput
}
}
}
},
methods: {
onInput ($event) {
// while $event is proxied from dynamic formComponent
// value of "this" is parent component
console.log(this.name + ' onInput');
// use refs to refer to real components value
console.log(this.$refs.htmlDataComponent.value);
console.log(this.$refs.htmlDataComponent.$refs.customInput1);
console.log(this.$refs.htmlDataComponent.$refs.customInput2);
console.log(this.$refs.htmlDataComponent.$refs.customInput3);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js">
</script>
<div id="root">
<component ref="htmlDataComponent"
v-if="htmlData"
:is="htmlDataComponent"></component>
</div>
I did not check it for memory efficiency, but it looks like works just fine.
Modified version of #RoyJ's answer, works in Vue.js v2.6.10
new Vue({
...,
computed: {
inner_html() {
return ...; // any raw html
},
},
directives: {
dynamic: {
bind(el, binding) {
el.innerHTML = binding.value;
},
update(el, binding) {
el.innerHTML = binding.value;
},
},
},
template: `<div v-dynamic='inner_html'></div>`,
});
Since partial has been removed from VueJS 2 (https://v2.vuejs.org/v2/guide/migration.html#Vue-partial-removed)
A better way may be to create a component which processes its content and create appropriate DOM elements
The above component will replace hashtags by clickable links
<process-text>Hi #hashtag !</process-text>
Vue.component('process-text', {
render: function (createElement) {
var hashtagRegex = /(^|\W)(#[a-z\d][\w-]*)/ig
var text = this.$slots.default[0].text
var list = text.split(hashtagRegex)
var children = []
for (var i = 0; i < list.length; i++) {
var element = list[i]
if (element.match(hashtagRegex)) {
children.push(createElement('a', {
attrs: {
href: 'https://www.google.fr/search?q=' + element,
target: "_blank"
},
domProps: {
innerHTML: element
}
}))
} else {
children.push(element)
}
}
}
return createElement('p', {}, children) // VueJS expects root element
})