vue-18n - how to force reload in computed function when changing language - laravel-5

I am using vue-i18n but I also have some content which is stored in database. I would like my text to be updated when the user changes the language.
I am using laravel and vuejs2.
Thanks in advance, I am not super familiar with vuejs yet. I hope it's clear enough.
in ContenuComponent.vue
<template>
<div>
{{$i18n.locale}} <== this changes well
<div v-html="textcontent"></div>
<div v-html="textcontent($i18n.locale)"></div> <== this won't work, I am wondering how to put params here (more like a general quetsion)
</div>
</template>
<script>
export default {
name:'contenu',
props: {
content: {
type: String,
default: '<div></div>'
}
},
computed: {
textcontent: function () {
console.log(navigator.language); <== this gives me the language as well, so i could use it if I can make it reload
var parsed = JSON.parse(this.content);
parsed.forEach(element => {
if(navigator.language == element['lang']){
return element['text'];
}
});
}
}
}
</script>
in ContentController
public function getcontent(){
$content = DB::connection('mysql')->select( DB::connection('mysql')->raw("
SELECT text, lang from content
"));
return view('myvue', ['content' => json_encode($content)]);
}
in content.blade.php
<div id="app">
<contenu content="{{ $content }}"></contenu>
</div>

You SHOULD NOT pass parameters to computed props! They are not methods and you should create method instead:
methods: {
textcontent () {
var parsed = JSON.parse(this.content)
parsed.forEach(element => {
if (navigator.language == element['lang']){
return element['text']
}
})
}
}
Also you should consider ES6 syntax:
methods: {
textcontent () {
var parsed = JSON.parse(this.content)
const content = parsed.find(element => navigator.language == element['lang'])
return content['text']
}
}
Much cleaner!
Please make sure to read about computed props and how they are different than methods or watchers: docs

Related

How to use init() in multiple x-data spreads?

I've split up my methods into two functions in a separate JS file. Both parts need x-init, but only the second part's init() method is triggered:
<div
x-data="{
...part1(),
...part2(),
}">
<p>Check the console</p>
</div>
document.addEventListener('alpine:init', () => {
Alpine.data('part1', () => ({
init(){
// Not triggered
console.log("Part 1 init");
}
})
)});
document.addEventListener('alpine:init', () => {
Alpine.data('part2', () => ({
init(){
console.log("Part 2 init");
}
})
)});
Codepen
Can we have 2 init()s in one x-data?
The simplest solution here, if it's an option, would be to use two separate
DOM elements:
<div x-data="part1()">
<div x-data="part2()">
<p>Check the console</p>
</div>
</div>
However, I understand this might not be desirable.
Due to the way the object spread operator works, your init method from part1 is being overwritten with the one from part2 before AlpineJS even sees it. If you plan to do a lot of this type of merged data object, you might consider writing a helper such as:
Alpine.magic('merge', (...inputs) => inputs.reduce((state, next) => {
const prevInit = typeof state.init === 'function' ? state.init : () => {};
return {
...state,
...next,
init() {
prevInit.call(this);
next.init.call(this);
}
};
}, {});
<div x-data="$merge(part1(), part2())">...</div>
Depending on what you're doing, you might consider writing this as a directive instead. At least as of Alpine v3, it's possible to add multiple x-init directives as long as they're namespaced. For example:
<span x-init.foo="console.log('foo')" x-init.bar="console.log('bar')"></span>
You would implement this as:
document.addEventListener("alpine:init", () => {
Alpine.directive('part1', (el, {expression}) => {
Alpine.bind(el, {
['x-init.part1']() {
console.log('part 1 init');
}
})
});
Alpine.directive('part2', (el, {expression}) => {
Alpine.bind(el, {
['x-init.part2']() {
console.log('part 2 init');
}
})
});
});
You might end up with issues using this approach, though. In general simpler is better.

vue not loading data into child component

I've a hard time in understanding the methods of vue. In my put-request users can edit, delete images. In parent component the get-request loads the images and the are pushed to an image-gallery (the child-component) via properties. In my set up the console.log is always empty.
//PARENT COMPONENT
<template>
<div class="form-group">
<image-gallery :serverData="serverMap"/>
</div>
</template>
<script>
import ImageGallery from './ImageGallery.vue';
export default {
components:{ImageGallery},
data: () => ({
serverMap: {
title: '',
file: ''
}
}),
mounted () {
//AJAX ETC get servermap
.then((response) => {
this.serverMap = response.data
})
}
Just a normal straight parent-child situation. Here under the child-component
<template>
</template>
<script>
export default {
name: 'ImageGallery',
//incoming data
props: {
serverData: {
type: Object,
default () {
return {
hasLabels: true,
isHorizontal: false
}
}
}
},
created: function () {
this.loadImages()
},
methods: {
loadImages () {
console.log(this.serverData.file)
//do something with the serverData
//prepare for fileReader function
//together with new image validation
}
}
The method 'loadImages' should be automatically delevering the serverData via computed.But is doesn t. Who can help?
There is race condition.
Either not render a child until data is available; serverMap needs to be null instead of empty object in order to be distinguished from populated object:
<image-gallery v-if="serverMap" :serverData="serverMap"/>
Or delay data access in a child until it's available instead of doing this immediately in created:
watch: {
serverData(data) {
if (data)
this.loadImages()
}
}

Render content with Vue syntax / component string through AJAX call?

I have this HTML pattern:
<div id="New"> == ajax loaded content == </div>
It was easy to render HTML at server side and use innerHTML to inject the content into the right place.
Now I am trying to use Vue.js to do the same thing but render HTML at the client side. I can make this pattern into a component, let's say componentA, with template:
componentA
template:
`<div><slot></slot></div>`
It works if the HTML page content is something like:
<componentA>
<componentB></componentB> and some other none component content
</componentA>
The componentB is rendered and replaced the slot in componentA.
The problem is how do I use AJAX call (the call is made outside of componentA) to load
<componentB></componentB> and some other none component content
into the slot of componentA, and still make componentB to render correctly?
In real situation, the content from AJAX call can be
<componentB>, <componentC>, <componentD> ...
The following will treat componentB as regular string
in HTML:
<componentA>
<div id="New"></div>
</componentA>
in JS:
document.getElementById('New').innerHTML =
'<componentB></componentB> And some other none component content';
Is there a proper way to render string from AJAX return with Vue syntax as Vue?
One solution is put the ajax response like <component></component> to Component.template inside render function (Vue Guide: Render Function).
Like below demo:
const Foo = Vue.component('foo', {template: '<p>Foo - {{flag}}</p>', props: ['flag']})
const Bar = Vue.component('bar', {template: '<p>Bar - {{flag}}</p>', props: ['flag']})
const Generic = Vue.component('generic', {
render: function (createElement) {
return createElement('div', [
createElement('h3', 'Title'),
createElement('button', {on: {click: this.loadComponent}}, 'Load Component'),
this.dynamicComponent
&& createElement(Vue.component('v-fake-slot', {template:this.dynamicComponent, props: ['flag']}), {
props: {
flag: this.parent
}
})
])
},
props: ['parent'],
data () {
return {
components: ['<foo :flag="flag"></foo>', '<bar :flag="flag"></bar>'],
index: 0,
dynamicComponent: ''
}
},
methods: {
loadComponent: function () {
setTimeout(() => {
this.index += 1
this.dynamicComponent = this.components[this.index % 2]
}, 1000)
}
}
})
new Vue({
el: '#app',
data () {
return {
test: 'root'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<generic :parent="test"></generic>
</div>

Get 2 data from API laravel

i have 2 data from API
1. Category Food
2. Finish Good
how can i show 2 data from API in 1 page vue,
I only can show 1 data from API
this is what i tried
export default {
data(){
items:[],
finish_goods:[],
created() {
let uri = 'http://192.168.10.75:8000/api/finish_goods'; // Data 1
this.axios.get(uri).then(response => {
this.items = response.data.data;
});
},
created() {
let uri = 'http://192.168.10.75:8000/api/cat_foods'; // Data 2
this.axios.get(uri).then(response => {
this.finish_goods = response.data.data;
});
}
},
methods: {}
}
You're along the right lines, but it looks like your template syntax is a bit messed up...
// Make sure axios is installed via npm, you can skip this step
// if you've declared window.axios = axios somewhere in your app...
import axios from 'axios';
export default {
// Data must be a function that returns the initial state object...
data() {
return {
finishGoods: [],
catFoods: []
};
},
// Created is a hook and can only be defined once, think of it like an event listener...
created() {
let finishGoodsUri = 'http://192.168.10.75:8000/api/finish_goods';
// Fetch finish goods, note that I'm not calling this.axios...
axios.get(finishGoodsUri).then(response => {
this.finishGoods = response.data.data;
});
let catFoodsUri = 'http://192.168.10.75:8000/api/cat_foods';
// Fetch cat foods...
axios.get(catFoodsUri).then(response => {
this.catFoods = response.data.data;
});
}
}
Now in your template you can do the following:
<template>
<div>
<div v-for="finishGood in finishGoods">
{{ finishGood.attribute }}
</div>
<div v-for="catFood in catFoods">
{{ catFood.attribute }}
</div>
</div>
</template>
my advice, combine the API as 1
created() {
let uri = 'http://192.168.10.75:8000/api/combine_data'; // sample
this.axios.get(uri).then(response => {
this.finish_goods = response.data.data.goods;
this.items = response.data.data.foods;
});
}

Vuejs 2 using filterBy and orderBy in the same computed property

I am having a hard time trying to joing a filterBy with orderBy, on vuejs 2.0, with all research I have found about this subject, as of link on the bottom of my question.
This is my filter, which is working:
// computed() {...
filteredResults() {
var self = this
return self.results
.filter(result => result.name.indexOf(self.filterName) !== -1)
}
A method called in the component:
// methods() {...
customFilter(ev, property, value) {
ev.preventDefault()
this.filterBook = value
}
In the component:
// Inside my component
Name..
And another filter, which works as well:
// computed() {...
orderByResults: function() {
return _.orderBy(this.results, this.sortProperty, this.sortDirection)
}
To comply with my orderBy I have this method:
// methods() {...
sort(ev, property) {
ev.preventDefault()
if (this.sortDirection == 'asc' && this.sortProperty == property ) {
this.sortDirection = 'desc'
} else {
this.sortDirection = 'asc'
}
this.sortProperty = property
}
And to call it I have the following:
// Inside my component
Name..
I have found in the docs how we use this OrderBy, and in this very long conversation how to use filter joint with sort, but I could really not implement it...
Which should be some like this:
filteredThings () {
return this.things
.filter(item => item.title.indexOf('foo') > -1)
.sort((a, b) => a.bar > b.bar ? 1 : -1)
.slice(0, 5)
}
I could not make this work...
I tried in many forms as of:
.sort((self.sortProperty, self.sortDirection) => this.sortDirection == 'asc' && this.sortProperty == property ? this.sortDirection = 'desc' : this.sortDirection = 'asc' )
But still, or it does not compile or it comes with errors, such as:
property not defined (which is defines such as I am using it in the other method)
method of funcion not found (is happens when comment my method sort.. maybe here is what I am missing something)
Thanks for any help!
The ideas of your approach seem valid, but without a full example it's hard to tell what might actually be wrong.
Here's a simple example of sorting and filtering combined. The code can easily be extended e.g. to work with arbitrary fields in the test data. The filtering and sorting is done in the same computed property, based on the parameters set from the outside. Here's a working JSFiddle.
<div id="app">
<div>{{filteredAndSortedData}}</div>
<div>
<input type="text" v-model="filterValue" placeholder="Filter">
<button #click="invertSort()">Sort asc/desc</button>
</div>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
testData: [{name:'foo'}, {name:'bar'}, {name:'foobar'}, {name:'test'}],
filterValue: '',
sortAsc: true
};
},
computed: {
filteredAndSortedData() {
// Apply filter first
let result = this.testData;
if (this.filterValue) {
result = result.filter(item => item.name.includes(this.filterValue));
}
// Sort the remaining values
let ascDesc = this.sortAsc ? 1 : -1;
return result.sort((a, b) => ascDesc * a.name.localeCompare(b.name));
}
},
methods: {
invertSort() {
this.sortAsc = !this.sortAsc;
}
}
});
</script>

Resources