I'm using a vue component which is two way bound with an input field.
I want to append a +- and a % sign to this value solely in the input field view. I don't want to change the actual value as this will cause troubles with the component.
Here is what I am looking for:
Here is what I have:
Using this code:
<form class="form-container">
<label for="changePercent" class="move-percent-label">Move Market</label>
<input class="move-percent" id="changePercent" v-model="value.value" type="number">
<span class="middle-line"></span>
<vue-slider v-bind="value" v-model="value.value"></vue-slider>
<div class="control-buttons">
<button #click="" class="primary-button">Apply</button>
<button #click.prevent="value.value = 0;" class="tertiary-button">Reset</button>
</div>
</form>
------------------UPDATE-------------------
As per answer below using a computed property.
Good:
Not Good
So I need this to work both ways
To have another value always formated a computed property:
new Vue({
el: '#app',
data: {
value: {value: 0},
// ..
},
computed: {
readableValue() {
return (this.value.value => 0 ? "+" : "-" ) + this.value.value + "%";
}
}
})
Creating an editor for the slider and showing formatted
To get what you want we will have to do a litte trick with two inputs. Because you want the user to edit in a <input type="number"> but want to also show +15% which can't be shown in a <input type="number"> (because + and % aren't numbers). So you would have to do some showing/hiding, as below:
new Vue( {
el: '#app',
data () {
return {
editing: false,
value: {value: 0},
}
},
methods: {
enableEditing() {
this.editing = true;
Vue.nextTick(() => { setTimeout(() => this.$refs.editor.focus(), 100) });
},
disableEditing() {
this.editing = false;
}
},
computed: {
readableValue() {
return (this.value.value > 0 ? "+" : "" ) + this.value.value + "%";
}
},
components: {
'vueSlider': window[ 'vue-slider-component' ],
}
})
/* styles just for testing */
#app { margin: 10px; }
#app input:nth-child(1) { background-color: yellow }
#app input:nth-child(2) { background-color: green }
<div id="app">
<input :value="readableValue" type="text" v-show="!editing" #focus="enableEditing">
<input v-model.number="value.value" type="number" ref="editor" v-show="editing" #blur="disableEditing">
<br><br><br>
<vue-slider v-model="value.value" min="-20" max="20">></vue-slider>
</div>
<script src="https://unpkg.com/vue#2.5.13/dist/vue.min.js"></script>
<script src="https://nightcatsama.github.io/vue-slider-component/dist/index.js"></script>
Related
How do I check if an image is available and render it if so?
So far I have tried something like this, but does not give a true condition.
<div v-for="owner in organisation.owners"
<div v-if="`https://intranet.com/Lists/CustomPortraits/${owner.employeeId}.jpg`" :style="`background-image:url(https://intranet.com/Lists/CustomPortraits/${owner.employeeId}.jpg)`" class="user-profile-image"/>
<div v-else class="user-avatar"> <span/>{{ owner.name | nameInitials }} </div>
Checking image exists on the server in JS is well described in another SO question - Check if image exists on server using JavaScript?
So it is easy just to choose one of the solutions and integrate it into Vue. Since you want to use it in the v-for loop, plain function is a bad solution. Better will be to introduce computed property and extend existing object with some new properties:
Update
Deleted my previous code - computed is a bad choice either in this case because array/objects returned from computed prop is not reactive data (changing the content of object returned by computed will NOT trigger Vue re-render)
New example below shows solution combining deep watching input data (organisation) + maintaining extended data in the data part of the component...
const vm = new Vue({
el: '#app',
data() {
return {
// this if external input in question (prop maybe)
organisation: {
owners: [{
employeeId: 100,
name: "Bill",
},
{
employeeId: 200,
name: "Murray",
fail: true // just for demo purposes
},
{
employeeId: 300,
name: "Billy",
},
]
},
ownersWithImage: [],
}
},
methods: {
checkImageExists(owner) {
const img = new Image()
img.onload = () => {
owner.doesImageExist = true
}
img.onerror = () => {
owner.doesImageExist = false
}
img.src = owner.imageUrl
},
extendOwnerWithImage(owner) {
const extendedOwner = {
...owner,
imageUrl: 'https://www.fillmurray.com/250/' + owner.employeeId,
doesImageExist: false // default
}
if (!owner.fail) // just for demo purposes
this.checkImageExists(extendedOwner)
return extendedOwner
}
},
watch: {
'organisation.owners': {
handler: function() {
this.ownersWithImage = this.organisation.owners.map(this.extendOwnerWithImage)
},
immediate: true,
deep: true
}
},
})
.user-profile-image {
width: 150px;
height: 150px;
background-size: cover;
background-position: top center;
border-radius: 50%;
background-color: grey;
text-align: center;
vertical-align: middle;
line-height: 150px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="owner in ownersWithImage" :key="owner.employeeId">
<div v-if="owner.doesImageExist" :style="`background-image:url(${owner.imageUrl})`" class="user-profile-image">
<span>{{ owner.name }}</span>
</div>
<div v-else class="user-profile-image"><span>{{ owner.name }}</span></div>
</div>
</div>
I got the problem when implementing form input validation in React.
Once I open the form, there is always [object object] appear as the default input.
Here is the display:
Ideally, I want to have the below display:
And here is my code, any idea how shall I fix it?
const ErrorValidationLabel = ({ txtLbl }) => (
<label htmlFor="" style={{ color: "red" }}>
{txtLbl}
</label>
);
const txtFieldState = {
value: "",
valid:true,
}
class Setting extends Component {
constructor() {
this.state = {
name: {...txtFieldState, fileName:"name", required:true, requiredTxt:"Device ID is required"} ,
}
this.handleNameChange = this.handleNameChange.bind(this);
this.handleNameSearch = this.handleNameSearch.bind(this);
handleNameChange(event) {
this.setState({ name: event.target.value });
}
handleNameSearch(event) {
//http request call logic
}
}
render() {
const renderNameError = this.state.name.valid? "" : <ErrorValidationLabel txtLbl={this.state.name.requiredTxt} />;
return (
<form onSubmit={this.handleNameSearch}>
<label >
Enter Your Device Id:
</label>
<Input name="name" type="text" placeholder="ex:12345678910" value={this.state.name} onChange={this.handleNameChange} required/>
{renderNameError}
<Input type="submit" value="Search Device" />
</form>
);
}
You're setting the value of your input to this.state.name, but this.state.name is an object.
this.state = {
name: {...txtFieldState, fileName:"name", required:true, requiredTxt:"Device ID is required"}
}
You should set it to this.state.name.value.
I should warn you, however, that the code this.setState({ name: event.target.value }) will overwrite your entire name object, which likely isn't what you want.
You're using a spread operator already, so I would suggest
this.setState(state => ({ name: { ...state.name, value: event.target.value } }))`.
I want to use an input field (or something similar) that suggests an autocomplete, based on records from a data source.
In Vue I retrieve an array from a database table containing 3000+ records:
data(){
return{
inboundRelation_Trelation_data: [],
}
},
mounted(){
axios.get('/app/wms/allRelations', {
params: {
"dbConn": this.connString,
}
})
.then(response => this.inboundRelation_Trelation_data = response.data);
},
Based on this data, I want an autocomplete input field and/or dropdown. I've found 2 approaches online.. 1:
<select v-model="form.CUSTOMER_NAME">
<option v-for="(relation, index) in inboundRelation_Trelation_data" :value="relation.RELATIONCODE" v-text="relation.COMPANYNAME + ' | ' + relation.RELATIONCODE"></option>
</select>
This populates a dropdown, but my users experience this as tedious, as they need to type their letters quickly, otherwise after a small pause (like <0.5s), the next typed letter will start a new search and the results are inconsistent.
The other approach is using a data-list:
<input list="allRelations" type="text" #focus="$event.target.select()" v-model="form.CUSTOMER_NAME">
<datalist id="allRelations">
<option v-for="(relation, index) in inboundRelation_Trelation_data" :value="relation.RELATIONCODE" v-text="relation.COMPANYNAME + ' | ' + relation.RELATIONCODE"></option>
</datalist>
This works perfectly for small amounts of data. But when dealing with 100+ records (or 3000+ in this case), the whole browser freezes upon typing a letter. For some reason this is a very resource-heavy implementation. I've found some people with similar issues, but no solutions.
At the end of the day, I just want my users to be able to search in a huge list of 3000+ records. How do I approach this?
You can use vue-autosuggest package by this github link :
https://github.com/darrenjennings/vue-autosuggest
I am using this package and my data loads as my expect.
This is the template that you can use:
<template>
<div class="autosuggest-container">
<vue-autosuggest
v-model="form.CUSTOMER_NAME"
:suggestions="filteredOptions"
#focus="focusMe"
#click="clickHandler"
#input="onInputChange"
#selected="onSelected"
:get-suggestion-value="getSuggestionValue"
:input-props="{
class: 'form-control',
id: 'autosuggest__input',
field: 'CUSTOMER_NAME',
placeholder: 'Enter customer name for auto suggest',
}"
>
<div
slot-scope="{ suggestion }"
style="display: flex; align-items: center"
>
<img
:style="{
display: 'flex',
width: '25px',
height: '25px',
borderRadius: '15px',
marginLeft: '10px',
}"
:src="suggestion.item.avatar"
/>
<div style="{ display: 'flex', color: 'navyblue'}">
{{ suggestion.item.CUSTOMER_NAME }}
</div>
</div>
</vue-autosuggest>
</div>
And the Script section:
<script>
export default {
data() {
return {
searching: false,
query: '',
selected: '',
suggestions: [],
}
},
computed: {
filteredOptions() {
return [
{
data: this.suggestions.filter((option) => {
return (
option.name.toLowerCase().indexOf(this.query.toLowerCase()) > -1
)
}),
},
]
},
},
methods: {
clickHandler() {
//
},
onSelected(item) {
this.selected = item.item
},
async onInputChange(text = '') {
this.searching = true
await this.$axios
.get(`/app/wms/allRelations`,{
params: {
"dbConn": this.connString,
})
.then((res) => {
this.suggestions = res.data.data
})
.catch((e) => console.log(e))
.finally(() => (this.searching = false))
},
getSuggestionValue(suggestion) {
return suggestion.item.name
},
focusMe(e) {
this.onInputChange()
},
},
}
</script>
If still your browser freeze, you have to change your API response limit to something like descended 10 items.
I have a vue app and a component. The app simply takes input and changes a name displayed below, and when someone changes the name, the previous name is saved in an array. I have a custom component to display the different list items. However, the component list items do not render immediately. Instead, the component otems render as soon as I type a letter into the input. What gives? Why would this not render the list items immediately?
(function(){
var app = new Vue({
el: '#app',
components: ['name-list-item'],
data: {
input: '',
person: undefined,
previousNames: ['Ian Smith', 'Adam Smith', 'Felicia Jones']
},
computed: {
politePerson: function(){
if(!this.person) {
return 'Input name here';
}
return "Hello! To The Venerable " + this.person +", Esq."
}
},
methods: {
saveInput: function(event){
event.preventDefault();
if(this.person && this.previousNames.indexOf(this.person) === -1) {
this.previousNames.push(this.person);
}
this.setPerson(this.input);
this.clearInput();
},
returnKey: function(key) {
return (key + 1) + ". ";
},
clearInput: function() {
this.input = '';
},
setPerson: function(person) {
this.person = person;
}
}
});
Vue.component('name-list-item', {
props: ['theKey', 'theValue'],
template: '<span>{{theKey}} {{theValue}}</span>'
});
})()
And here is my HTML.
<div id="app">
<div class="panel-one">
<span>Enter your name:</span>
<form v-on:submit="saveInput">
<input v-model="input"/>
<button #click="saveInput">Save</button>
</form>
<h1>{{politePerson}}</h1>
</div>
<div class="panel-two">
<h3>Previous Names</h3>
<div>
<div v-for="person, key in previousNames" #click='setPerson(person)'><name-list-item v-bind:the-key="key" v-bind:the-value="person" /></div>
</div>
</div>
</div>
You are not defining your component until after you have instantiated your Vue, so it doesn't apply the component until the first update.
I am writing a form, in that I required dynamic elements which will come from a json script.
here is a sample.
<div ng-repeat='element in elements'>
<input type="button" ng-click="{{element.clickedAction}}" />
</div>
But this ng-click is not updating as in json script. How to proceed?
Thanks in advance.
<div ng-repeat='element in elements'>
<input type="button" ng-click="element.clickedAction()" />
</div>
Presuming that clickedAction is a function on your element in the elements array.
$scope.elements = [
{
clickedAction: function () { /* do something */ }
},
{
clickedAction: function () { /* do something */ }
},
{
clickedAction: function () { /* do something */ }
}
];
This is an unusual scenario though. More often people will be doing something like this:
$scope.doSomething = function(item) {
item.name += '!';
};
$scope.items = [ { name: 'test' }, { name: 'foo' }, { name: 'bar' } ];
<div ng-repeat="item in items">
<button ng-click="doSomething(item)">Click Me</button>
</div>