VueJS 2 update contenteditable in component from parent method - vue.js

I have editable element updated by component method, but i have also json import and i want to update element my parent method. I can update model, but editable element doesn´t bind it. If i insert content to component template, it will bind updated model, but then i can´t really edit it.
Here´s my example: https://jsfiddle.net/kuwf9auc/1/
Vue.component('editable', {
template: '<div contenteditable="true" #input="update"></div>', /* if i insert {{content}} into this div, it wil update, but editing behave weird */
props: ['content'],
mounted: function () {
this.$el.innerText = this.content;
},
methods: {
update: function (event) {
console.log(this.content);
console.log(event.target.innerText);
this.$emit('update', event.target.innerText);
}
}
})
var app = new Vue({
el: '#myapp',
data: {
herobanner: {
headline: 'I can be edited by typing, but not updated with JSON upload.'
}
},
methods: {
uploadJSON: function (event) {
var input = event.target;
input.src = URL.createObjectURL(event.target.files[0]);
var data = input.src;
$.get(data, function(data) {
importdata = $.parseJSON(data);
this.$data.herobanner = importdata.herobanner;
}.bind(this));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<main id="myapp" class="container-fluid">
<input type="file" id="file" name="file" #change="uploadJSON" style="display: none; width: 1px; height: 1px"/>
<a href="" onclick="document.getElementById('file').click(); return false" title="Import settings from JSON file">
upload JSON
</a>
<h1>
<editable :content="herobanner.headline" #update="herobanner.headline = $event"></editable>
</h1>
Real value of model:
<br>
<h2>{{herobanner.headline}}</h2>
</main>

Working example:
Vue.component('editable', {
template: `
<div contenteditable="true" #blur="emitChange">
{{ content }}
</div>
`,
props: ['content'],
methods: {
emitChange (ev) {
this.$emit('update', ev.target.textContent)
}
}
})
new Vue({
el: '#app',
data: {
herobanner: {
headline: 'Parent is updated on blur event, so click outside this text to update it.'
}
},
methods: {
async loadJson () {
var response = await fetch('https://swapi.co/api/people/1')
var hero = await response.json()
this.herobanner.headline = hero.name
},
updateHeadline (content) {
this.herobanner.headline = content
}
}
})
<main id="app">
<button #click="loadJson">Load JSON data</button>
<h1>
<editable
:content="herobanner.headline"
v-on:update="updateHeadline"
>
</editable>
</h1>
<h2>{{herobanner.headline}}</h2>
</main>
<script src="https://unpkg.com/vue#2.5.3/dist/vue.min.js"></script>

Related

vue.js – get new data information

I'm building a chrome extension using vue.js. In one of my vue components I get tab informations of the current tab and wanna display this information in my template. This is my code:
<template>
<div>
<p>{{ tab.url }}</p>
</div>
</template>
<script>
export default {
data() {
return {
tab: {},
};
},
created: function() {
chrome.tabs.query({ active: true, windowId: chrome.windows.WINDOW_ID_CURRENT }, function(tabs) {
this.tab = tabs[0];
});
},
};
</script>
The Problem is, that the template gets the data before it's filled through the function. What is the best solution for this problem, when the tab data doesn't change after it is set once.
Do I have to use the watched property, although the data is only changed once?
// EDITED:
I've implemented the solution, but it still doesn't work. Here is my code:
<template>
<div>
<div v-if="tabInfo">
<p>set time limit for:</p>
<p>{{ tabInfo.url }}</p>
</div>
<div v-else> loading... </div>
</div>
</template>
<script>
export default {
data() {
return {
tabInfo: null,
};
},
mounted() {
this.getData();
},
methods: {
getData() {
chrome.tabs.query({ active: true, windowId: chrome.windows.WINDOW_ID_CURRENT }, function(tabs) {
console.log(tabs[0]);
this.tabInfo = tabs[0];
});
},
},
};
</script>
The console.log statement in my getData function writes the correct object in the console. But the template only shows the else case (loading...).
// EDIT EDIT
Found the error: I used 'this' in the callback function to reference my data but the context of this inside the callback function is an other one.
So the solution is to use
let self = this;
before the callback function and reference the data with
self.tab
You could initialize tab to null (instead of {}) and use v-if="tabs" in your template, similar to this:
// template
<template>
<div v-if="tab">
{{ tab.label }}
<p>{{ tab.body }}</p>
</div>
</template>
// script
data() {
return {
tab: null,
}
}
new Vue({
el: '#app',
data() {
return {
tab: null,
}
},
mounted() {
this.getData();
},
methods: {
getData() {
fetch('https://reqres.in/api/users/2?delay=1')
.then(resp => resp.json())
.then(user => this.tab = user.data)
.catch(err => console.error(err));
}
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<div v-if="tab">
<img :src="tab.avatar" width="200">
<p>{{tab.first_name}} {{tab.last_name}}</p>
</div>
<div v-else>Loading...</div>
</div>

Vue.JS: turn every matching case to a hyperlink

I'm trying to turn every matching case from an array to a link
// I call the message component using this from another component
<ul>
<li
v-for="message in message"
v-bind:message="message"
is="message"
</ul>
// in the message component I have a mounted that reads the text message
// from the server and filters hashtags
data () {
return {
hashtagsInMessages: ''
};
},
mounted () {
const filterMessageHashtags = _.filter( this.message,text.split( ' ' ), value => hashtags.indexOf(value) != -1 );
this.hashtagsInMessages = filterMessageHashtags;
}
With this, how can I turn a message to a link, for instance:
hey how you doing #cool #fire #water bro?
To this, using Vue.js
hey how you doing #cool #fire #water bro?
This is my solution:
Vue.component("message-el", {
template: "#message",
props: ["message"],
created: function (){
this.messages = this.message.split(" ");
this.hashTags = this.messages.filter(function (message){
return message[0] === "#";
});
},
data: function (){
return {
messages: [],
hashTags: [],
};
},
methods: {
}
});
var app = new Vue({
el: "#app",
data: function (){
return {
}
},
methods: {
}
});
.none {
display: none;
}
<script src="https://unpkg.com/vue#2.5.2/dist/vue.js"></script>
<div id="app">
<message-el message="this is a message #tag1 #tag2">
</message-el>
</div>
<div id="message" class="none">
<div>
<div>
<template v-for="message in messages">
<a v-if="hashTags.indexOf(message) > -1" :href="'#' + message">
{{ message }}
</a>
<span v-else>
{{ message }}
</span>
&nbsp
</template>
</div>
</div>
</div>

Pass data from child to parent in Vuejs (is it so complicated?)

I have read about it:
vuejs update parent data from child component
https://forum.vuejs.org/t/passing-data-back-to-parent/1201/2
The concept is the same, I need to pass a data object from child to parent. I have used $emit to pass data to parent component but it doesn't works. Do you know what is wrong? You can check my code here:
Vue.component('list-products', {
delimiters: ['[[', ']]'],
template: '#list-products-template',
props: ['products'],
data: function () {
return {
productSelected: {}
}
},
methods: {
showDetailModal: function (product) {
console.log('click product in child, how can i pass this product to productSelected data in parent?');
console.log(product);
this.productSelected = product;
this.$emit('clickedShowDetailModal', product);
}
}
});
var app = new Vue({
delimiters: ['[[', ']]'],
el: '#resultComponent',
data: {
listProducts: [
{'name':'test1',id:1},
{'name':'test2',id:2},
{'name':'test3',id:3}
],
productSelected: {}
},
methods: {
clickedShowDetailModal: function (value) {
console.log('value');
console.log(value);
this.productSelected = value;
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="resultComponent" data-toggler=".small-up-2" class="row small-up-1">
<list-products :products="listProducts"></list-products>
</div>
<script type="text/x-template" id="list-products-template">
<div>
<div class="column column-block" v-for="(product, index) in products" :product="product" :index="index" :key="product.id">
<li class="more-benefits">
<a #click="showDetailModal(product)">Click me [[ product.name ]] and check console.log »</a>
</li>
</div>
</div>
</script>
Props are for parent -> child
You can use $emit for child -> parent
v-on directive captures the child components events that is emitted by $emit
Child component triggers clicked event :
export default {
methods: {
onClickButton (event) {
this.$emit('clicked', 'someValue')
}
}
}
Parent component receive clicked event:
<div>
<child #clicked="onClickChild"></child>
</div>
...
export default {
methods: {
onClickChild (value) {
console.log(value) // someValue
}
}
}
You aren't listening to the event. I changed the event name to clicked-show-detail. Try this.
In the showDetailModal method of your component.
this.$emit('clicked-show-detail', product);
In your Vue.
<list-products :products="listProducts" #clicked-show-detail="clickedShowDetailModal"></list-products>
Example.
Nightmare to find "hello world" example out there for $emit so I added the example below (Minimal lines of code + semantic names of functions).
"Hello world" On click change parent data
Vue.component('child', {
template: `
<div class="child">
<button v-on:click="childMethod">CLICK - child Method pass data from product component</button>
</div>
`,
data: function () {
return {
child_msg: "message from child"
}
},
methods: {
childMethod: function() {
this.$emit('child-method', this.child_msg)
}
}
})
var app = new Vue({
el: '#app',
data: {
msg: "I am the blue parent!!!!!!!!!!!!!!!!!!",
},
methods: {
updateParent(value_from_child) {
this.msg = value_from_child;
alert("hello child" + value_from_child)
}
}
})
.child{ background: gray; padding: 15px; }
button{ cursor: pointer; }
#app{ border: 1px red dashed; padding: 15px; background: lightblue; color: blue;
}
<div id="app">
<p>{{msg}}</p>
<!-- ###### The trick happens her ###### -->
<child class="child" v-on:child-method="updateParent"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
codepen: https://codepen.io/ezra_siton/pen/YzyXNox?editors=1010

Focus input of freshly added item

So I have a list of items and list of inputs linked to each item via v-for and v-model.
I click a button and add new item to that list. I want to focus input which is linked to newly added item.
Can't figure out how to achieve this goal.
<div id="app">
<div v-for="item in sortedItems">
<input v-model="item">
</div>
<button #click="addItem">
add
</button>
</div>
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
Here's fiddle with sorted list
https://jsfiddle.net/sfL91r95/1/
Thanks
Update: inspired by pkawiak's comment, a directive-based solution. I found that calling focus in the bind section didn't work; I had to use nextTick to delay it.
Vue.directive('focus-on-create', {
// Note: using Vue 1. In Vue 2, el would be a parameter
bind: function() {
Vue.nextTick(() => {
this.el.focus();
})
}
})
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app">
<div v-for="item in sortedItems">
<input v-focus-on-create v-model="item">
</div>
<button #click="addItem">
add
</button>
</div>
Original answer:
Make your input a component so that you can give it a ready hook.
const autofocus = Vue.extend({
template: '<input v-model="item" />',
props: ['item'],
ready: function() {
this.$el.focus();
}
})
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
components: {
autofocus
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app">
<div v-for="item in sortedItems">
<autofocus :item="item"></autofocus>
</div>
<button #click="addItem">
add
</button>
</div>
I Like to extend #Roy's answer.
if you are using any UI framework then it will create DIV and within the DIV input tag will be created so this Snippet will handle that case.
Vue.directive('focus-on-create', {
bind: function(el) {
Vue.nextTick(() => {
el.querySelector('input').focus()
})
}
})

How to handle getting new data even?

I have got Vue-JS app. After use click variable rasters_previews_list get new data. I would like to generate list with them. I can't understand which directive I should use to handle this even.
Here is my code:
var userContent = Vue.extend({
template: `
<div class="LayersMenuSectionContent" v-if="rasters_previews_list.length">
<ul v-for="img in rasters_previews_list">
<li>{{img.id}}</li> // generate list here
<ul>
</div>
`,
data: function () {
return {
rasters_previews_list: [] // when come new data I should generate li with then
}
},
ready: function()
{
},
methods:
{
}
});
Should I use v-on or v-if?
When rasters_previews_list is changed, list is autorendered in v-for.
new Vue({
el: '#app',
template: `
<div class="LayersMenuSectionContent" v-show="rasters_previews_list.length">
<ul v-for="img in rasters_previews_list">
<li>{{img.id}}</li>
<ul>
</div>
<button #click="add">Add</button>
`,
data: function () {
return {
rasters_previews_list: [] // when come new data I should generate li with then
}
},
ready: function(){ },
methods: {
add(){
this.rasters_previews_list.push({id: 'hello'},{id: 'world'});
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app"></div>
Extra: You must use v-show instead of v-if for this case