Vue JS - Two way data binding on a button with v-model - javascript

The later versions of Vue JS 2 are complaining about using v-model on a button. It shows the error:
<button v-model="settings.primary_color">: v-model is not supported on this element type. If you are working with contenteditable, it's recommended to wrap a library dedicated for that purpose inside a custom component.
Here is my button:
<button type="button" class="colorpicker" v-model="settings.primary_color" :data-color="settings.primary_color" :style="{'background-color' : settings.primary_color}"></button>
Is it possible to achieve two way data binding using something like the data-color property rather than an input value?

Off course it is possible, that's the goal of modern JS frameworks.
What the error says is that binding data on "button" tag is not possible. The reason is that "button" is a native html element.
So if you want to bind datas on a button, just create a new component like "my-button", then you could achieve ti !
<my-button
class="colorpicker"
:type="button"
v-model="settings.primary_color"
:data-color="settings.primary_color"
:style="{'background-color' : settings.primary_color}"
></my-button>

By better understanding your problem, I propose you this solution
Just change your button by an input[type="color"]
The component :
<template>
<div>
<input type="color" v-model="color"/>
{{ color }}
</div>
</template>
<script>
export default {
name: "ColorPicker",
data: () => ({
color: "#000000",
}),
};
</script>
And because we have now an input, v-model is possible
EDIT :
So, stay with your button (i guess, that your colorpicker class is important because you already did some work on it)
According to VueJs documentation, to create your own two ways data binding, you have to get the prop "value" and emit the signal "input" on the component
(I just made a generateRandomColor() function to be faster)
Create a button component :
<template>
<div>
<button class="colorpicker" #click="triggered">Click me</button>
</div>
</template>
<script>
export default {
name: "ColorPicker",
props: {
value: String,
},
data: () => ({
value_: "#000000",
}),
methods: {
triggered: function () {
this.value_ = this.getRandomColor();
this.$emit("input", this.value_);
},
getRandomColor: function () {
var letters = "0123456789ABCDEF";
var color = "#";
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
},
},
};
</script>
Then, call it in your parent :
<color-picker v-model="colors.primary"></color-picker>
For your use, you just have to update the value_ property in custom button component and value will automatically update in parent

Related

Vue inline template not triggering method even with emit

I'm wondering what I am doing wrong here, from what I can see this is the solution: Vue: method is not a function within inline-template component tag
However the method is still not triggering.
<b-table
:items="filtered"
:fields="fields"
sort-icon-left
responsive="sm"
#card-click="setUpdate"
>
<template v-slot:head()="data">
<span class="text-info">{{ data.label.toUpperCase() }}</span>
<button #click="$emit('card-click', data)">filter</button>
<input
v-show="data.field.showFilters"
v-model="filters[data.field.key]"
:placeholder="data.label"
/>
</template>
</b-table>
methods: {
setUpdate(field) {
console.log("hello");
console.log(field);
this._originalField = Object.assign({}, field);
field.showFilters = true;
}
}
Update
So the #click allowed me to to trigger the event but this lead to the table wouldn't update with the changed data with showFilters. Thanks to MattBoothDev I found event-based-refreshing-of-data, however this oddly now prevents the data from changing. I.e. if field.showFilters is true it's true if I click the button.
methods: {
setUpdate(field) {
this._originalField = Object.assign({}, field);
field.showFilters = !field.showFilters;
this.refreshTable();
console.log(field.showFilters);
},
refreshTable() {
this.$root.$emit("bv::refresh::table", "my-table");
}
}
It looks like you're using Bootstrap Vue?
What you're essentially doing here is putting a listener on the <b-table> tag for card-click but that event is essentially not happening within a child component of <b-table>.
Regardless, I'm not even sure you need the event.
<button #click="$emit('card-click', data)">filter</button>
can easily just be
<button #click="setUpdate(data)">filter</button>
EDIT:
It is good practice to use MVVM for Vue.js as well.
Rather than: #click="$emit('card-click', data)"
Should be: #click="onFilterClicked"
Then:
methods: {
onFilterClicked (data) {
this.$emit('an-event', data.some.property)
}
}
This will make testing your code a lot easier.

Build up some UI with mithril.js and jsx

I'm new to javascript and its ecosystem. I'm trying to build some components using mithril.js. My goal is to have a component that shows some properties and provides a couple of button for each of them. Just to learn about mithril.js and jsx. Here is what I did so far:
const m = require("mithril");
var Something = {
_increase: function(category) {
console.log("increase category: "+category);
},
_decrease: function(category) {
console.log("decrease category: "+category);
},
view: function(vnode) {
return <div>
{Object.keys(vnode.attrs.categories).map((category)=> {
return <div>
<label for={category}>{category}</label>
<input type="number" id={category} value={vnode.attrs.categories[category]} />
<button type="button" onclick="{this._increase(category)}">MORE</button>
<button type="button" onclick="{this._decrease(category)}">LESS</button>
</div>
})}
</div>
}
}
export default Something;
Well, component seems to work fine, node doesn't complain and labels and buttons and fields are displayed on page, but, when I click on a button, nothing happen. It looks like event isn't fired. What's wrong?
Two things: (1) I think you should just put the function into the onclick handler braces instead of encoding the function in a string. (2) It looks like you're immediately invoking the function, not declaring that the onclick handler is a function that uses the category argument. Try passing in an anonymous function with no arguments, that way you when the onclick event is fired it can take in the category as a parameter:
onclick={() => this._increase(category)}
onclick={() => this._decrease(category)}

vue.js put focus on input

HTML
<span :style="{ display : displayTitle }" #dblclick="showInput()">
{{ node.title }}
</span>
<input :style="{ display : displayTitleInput }" type="text"
#blur="hideInput1" #keydown="hideInput2"
#input="changeTitle(node.id , $event.target.value)"
:value="node.title">
JS
data() {
return {
displayTitle: "inline-block",
displayTitleInput: "none"
};
},
showInput() {
this.displayTitle = "none"
this.displayTitleInput = "inline-block"
},
hideInput1() {
this.displayTitle = "inline-block"
this.displayTitleInput = "none"
},
hideInput2(event) {
if (event.keyCode === 13) {
this.hideInput1()
}
},
I am a beginner Japanese web developer. I am not good at English, sorry.
HTML is in "v-for" (v-for="node in list").
When double click text, it turns to <input>.
I want to make it possible to focus on input when it appears.
I tried this but it didn't work.
HTML
<span :style="{ display : displayTitle }" #dblclick="showInput(node.id)">
{{ node.title }}
</span>
<input :ref='"input_" + node.id' :style="{display:displayTitleInput}" type="text"
#blur="hideInput1" #keydown="hideInput2"
#input="changeTitle(node.id , $event.target.value)"
:value="node.title">
JS
showInput(id) {
this.displayTitle = "none"
this.displayTitleInput = "inline-block"
this.$nextTick(this.$refs["input_" + id][0].focus())
},
There was no error on console, but didn't work.
Your primary problem is that $nextTick takes a callback function but you are executing
this.$refs["input_" + id][0].focus()
immediately. You could get your code working correctly with
this.$nextTick(() => {
this.$refs["input_" + id][0].focus()
})
However, I think you'll run in to further problems and your code can be made much simpler.
One problem you'll find is that all your node inputs will become visible when double-clicking on any of them due to your style rules.
You could instead store an "editing" flag somewhere either on the node or in a separate object.
Below is an example that simplifies your code by...
Using the array-like nature of ref when used within a v-for loop, and
Using the enter modifier on your #keydown event binding
new Vue({
el: '#app',
data: {
list: [
{id: 1, title: 'Node #1'},
{id: 2, title: 'Node #2'}
],
editing: {}
},
methods: {
showInput(id, index) {
this.$set(this.editing, id, true)
this.$nextTick(() => {
this.$refs.input[index].focus()
})
},
hideInput(id) {
this.editing[id] = false
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<ul id="app">
<li v-for="(node, index) in list">
<span v-show="!editing[node.id]" #dblclick="showInput(node.id, index)">
{{ node.title }}
</span>
<input v-show="editing[node.id]" type="text"
ref="input" :value="node.title"
#blur="hideInput(node.id)" #keydown.enter="hideInput(node.id)">
</li>
</ul>
The way you use this.$nextTick(); is incorrect. You should pass it a callback function.
this.$nextTick(function () {
this.$refs["input_" + id].focus()
})
https://jsfiddle.net/un65e9oc/7/
I'm not however sure how that array access is working for you, because as I notice, $refs is an object with the keys referring to the ref name.
[Edit: Thanks to #Phil's comment, above is clear.]
The above is the correct solution for your problem. Since you have already got that answer, I'll add something other than that.
The reason why you see this behavior is that because the reference you hold in $refs doesn't get updated when you change the visibility of the text box in your showInput() method. So when you call this.$refs["input_" + id].focus();, it's actually trying to set focus on a hidden element (because the current reference is not updated).
That's why you need to call the $nextTick() to update it. But if you wanted a quick fix to your problem, without calling $nextTick(), you could update it manually like this:
this.displayTitleInput = "inline-block"
this.$refs["input_" + id].style.display = this.displayTitleInput
this.$refs["input_" + id].focus();
This would also work :) Hope it helps!!
if you want to set focus after click on something and show input text box with set focus with vue js
directives: {
focus: {
// directive definition
inserted: function (el) {
el.focus()
}
}
}
and use custom directive for it. In case you need it should work on click then set with click
directives: {
click: {
// directive definition
inserted: function (el) {
el.focus()
}
}
}
and use it
<input v-focus> or <input v-click>
enter code here
The autofocus attribute is your friend:
<input type="text" autofocus />
It's work for me when we validate the form and want to set dynamically focus on each field
this.$validator.validateAll("FORM_NAME").then(valid => {
var errors = this.$validator.errors;
if (valid) {
console.log('All Fields are valid')
} else {
const errorFieldName = this.$validator.errors.items[0].field;
console.log(errorFieldName);
this.$refs[errorFieldName].focus();
}
});

vue.js add event listener to button in Render function

I want using Vue.js Render function to make component in javascript.Now I can make a HTML structure one SPAN and one BUTTON.when I click the button,I expect it output in console,but it just not work.here is my code :
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<counter></counter>
</div>
<script>
var a = {
data () {
return {count: 1}
},
methods: {
inc () {console.log(this.count)}
},
render:function(h){
var self = this
var buttonAttrs ={
on:{click:self.inc}
}
var span = h('span',this.count.toString(),{},[])
var button = h('button','+',buttonAttrs,[])
return h('div'
,{},
[
span,
button
])
}
}
new Vue({
el:'#app',
components:{
counter : a
}}
)
</script>
or on codepen
Any response is welcome and thank you .
Your use of the createElement method is incorrect when building your button, since you are passing the wrong series of arguments.
First off, you should set the inner html + via your button attributes object, not via the second argument which is reserved for the data object, per the documentation:
// {Object}
// A data object corresponding to the attributes
// you would use in a template. Optional.
{
// (see details in the next section below)
},
As such, you should structure your buttonsAttrs object as follows:
var buttonAttrs = {
on: { click: self.inc },
domProps: {
innerHTML: '+'
},
};
Second, you should pass the buttonAttrs as the second argument in your createElement call per the above documentation:
var button = h('button', buttonAttrs, []);
See this working codepen.

how to add css classes to a component in react?

So basically what I am doing is iterating through an array of data and making some kind of list. What I want to achieve here is on clicking on a particular list item a css class should get attached.
Iteration to make a list
var sports = allSports.sportList.map((sport) => {
return (
<SportItem icon= {sport.colorIcon} text = {sport.name} onClick={this.handleClick()} key= {sport.id}/>
)
})
A single list item
<div className="display-type icon-pad ">
<div className="icons link">
<img className="sport-icon" src={icon}/>
</div>
<p className="text-center">{text}</p>
</div>
I am not able to figure out what to do with handleClick so that If I click on a particular list it gets highlighted.
If you want to highlight the particular list item it's way better to call the handleClick function on the list item itself, and you can add CSS classes more accurately with this approach,
here is my sample code to implement the single list component
var SingleListItem = React.createClass({
getInitialState: function() {
return {
isClicked: false
};
},
handleClick: function() {
this.setState({
isClicked: true
})
},
render: function() {
var isClicked = this.state.isClicked;
var style = {
'background-color': ''
};
if (isClicked) {
style = {
'background-color': '#D3D3D3'
};
}
return (
<li onClick={this.handleClick} style={style}>{this.props.text}</li>
);
}
});
Keep a separate state variable for every item that can be selected and use classnames library to conditionally manipulate classes as facebook recommends.
Edit: ok, you've mentioned that only 1 element can be selected at a time,it means that we only need to store which one of them was selected (I'm going to use the selected item's id). And also I've noticed a typo in your code, you need to link the function when you declare a component, not call it
<SportItem onClick={this.handleClick} ...
(notice how handleClick no longer contains ()).
And now we're going to pass the element's id along with the event to the handleClick handler using partial application - bind method:
<SportItem onClick={this.handleClick.bind(this,sport.id} ...
And as I said we want to store the selected item's id in the state, so the handleClick could look like:
handleClick(id,event){
this.setState({selectedItemId: id})
...
}
Now we need to pass the selectedItemId to SportItem instances so they're aware of the current selection: <SportItem selectedItemId={selectedItemId} ....Also, don't forget to attach the onClick={this.handleClick} callback to where it needs to be, invoking which is going to trigger the change of the state in the parent:
<div onClick={this.props.onClick} className={classNames('foo', { myClass: this.props.selectedItemId == this.props.key}); // => the div will always have 'foo' class but 'myClass' will be added only if this is the element that's currently selected}>
</div>

Categories

Resources