How to v-model 2 values? - html

I was using buefy <b-autocomplete> component and there is one property called v-model which is binding values to the input field
now I wanna bind Full Name into the field but the data consist with list[index].first_name and list[index].last_name, and the index is from a v-for loop.
Since v-model cannot bind a function (it has specific index so I cannot just concat it on computed then pass it on) so it's either v-model="list[index].first_name" or v-model="list[index].last_name"
How do I make it bind's these two?

You need a settable computed for full name, and you can v-model that. You just have to decide on a rule for where extra spaces go and what to do if there is no space.
new Vue({
el: '#app',
data: {
firstName: 'Joe',
lastName: 'Smith'
},
computed: {
fullName: {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(newValue) {
const m = newValue.match(/(\S*)\s+(.*)/);
this.firstName = m[1];
this.lastName = m[2];
}
}
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
First: {{firstName}}<br>
Last: {{lastName}}<br>
Full Name: <input v-model="fullName">
</div>

I am not sure if i get the question,but i am assuming that you have a list of names and last names and you want to give the ability to user to change those proprties of list.For more See the example in action
The "html" part
<div id="app">
<template v-for="item in list" :key="list.id">
<input type="text" :value="item.name" #input="changeList($event, item.id, 'name')">
<input type="text" :value="item.last_name" #input="changeList($event, item.id, 'last-name')">
Your full name is {{item.name}} {{item.last_name}}
<hr>
</template>
</div>
The "javascript(vue)" part
new Vue({
el: "#app",
data: {
list: [
{ id: 1, name: "name1", last_name: 'last_name 1' },
{ id: 2, name: "name2", last_name: 'last_name 2' },
{ id: 3, name: "name3", last_name: 'last_name 3' },
{ id: 4, name: "name4", last_name: 'last_name 4' }
]
},
methods: {
changeList(event, id, property) {
let value = event.target.value
for (item of this.list) {
if (item.id === id) {
if(property === 'name') {
item.name = value
}else if (property === 'last-name') {
item.last_name = value
}
}
}
}
}
})

As it's been said you can't use 2 values in v-model
But if you want to use <b-autocomplete> that means you already have the data and you can compute it in any way you want.
If you have an array of user objects (with firstname and lastname for example) you can do something like:
data() {
return {
users: [
//...
],
computedUsers: [],
}
},
mounted() {
this.users.forEach(user => {
this.computedUsers.push(user.firstname + ' ' + user.lastname)
})
}
and use this new array in the filteredDataArray (from the Buefy autocomplete example)
Fiddle example here

Related

Why useReducer does not update state in react-typescript

I have a problem with Typescript with React and useReducer hook. The state does not wanted to be updated and it is 100% typescript problem (I mean I need a typescript solution because in javascript it works perfect). So I want to make reducer as short as possible so I use "name" in HTML and get this name as a name of object in initialState. When I return ( title:{}, description: {}) It works but when I use [action.field] it does not work. action.field is the name of HTML element.
const initialStateReducer: inputsFormState = {
title: {
val: "",
isValid: false,
},
description: {
val: "",
isValid: false,
},
};
const RecipeForm = () => {
const inputReducer = (
state: inputsFormState,
action: inputsFormAction
): inputsFormState => {
console.log(action.type, action.content, action.field);
let isValid: boolean;
const { content } = action;
isValid = content.length > 0;
return {
[action.field]: {
val: content,
isValid: isValid,
},
...state,
};
};
const [formIsValid, setFormIsValid] = useState<boolean>(false);
const [inputsValues, dispatchReducer] = useReducer(
inputReducer,
initialStateReducer
);
const changeTextHandler = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
dispatchReducer({
type: ActionKind.changeVal,
field: e.target.name,
content: e.target.value,
});
};
return (
<React.Fragment>
<Input
name="title"
onChange={(e) => changeTextHandler(e)}
placeholder="Name for your recipe"
/>
<Textarea
name="description"
onChange={(e) => changeTextHandler(e)}
placeholder="Description"
cols={20}
rows={20}
resize="none"
/>
</React.Fragment>
);
};
Typescript is only a superset of JS that adds on Typing while writing code, it has no effect on the actually running of the JS (as it gets compiled into JS before running).
From looking at the above code I think the issue is your return in the reducer:
return {
[action.field]: {
val: content,
isValid: isValid,
},
...state,
};
Should be:
return {
...state,
[action.field]: {
val: content,
isValid: isValid,
}
};
As (and this may not be a great explanation) but right most arguments overwrite the preceding values, so you're effectively overwriting the new with the original.

Vue v-model on input as a dynamic ref

I created this <input> Vue component that handles a v-model property.
Adding the two-way data binding wasn't a problem, but I now want to add the current input value to the "state" so that I can clear it by just modifying a ref.
clearValue is called as expected but updating inputValue doesn't update the UI. What should I be doing differently ?
<template>
<div>
<input
:id="id"
type="text"
:value="inputValue"
#input="updateValue"
/>
<UiIcon
type="x"
#click.native="clearValue"
/>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from '#nuxtjs/composition-api';
import UiIcon from '~/components/ui/UiIcon.vue';
export default defineComponent({
name: 'UiInput',
components: { UiIcon },
props: {
id: {
type: String,
required: true,
},
value: {
type: String,
default: undefined,
},
},
setup(props, { emit }) {
const inputValue = ref(props.value);
const clearValue = () => {
inputValue.value = '';
};
const updateValue = (event: Event) => {
emit('input', (event.target as HTMLInputElement).value);
};
return {
updateValue,
clearValue,
inputValue,
};
},
});
Usage example
data() {
return { text: '' };
},
template: `
<div>
<UiInput :id="id" v-model="text" />
</div>
`,
I think you just need to emit 'input' event with empty value inside clearValue function
emit('input', '');

Vue: text field with typeahead

I want to make a text field with typeahead. I have a list of words and when you start typing them, a suggestion appears with the word(s). The thing is, it needs to be able to do it multiple times, every new word you type, it can show you a suggestion.
Anyone know how I can do this?
You can use vue-suggestion to accomplish this easily. Take a look at the demo to see if this suites you.
This is my implementation of App.vue which differs slightly.
<template>
<div>
{{ items }}
<vue-suggestion :items="results"
v-model="item"
:setLabel="setLabel"
:itemTemplate="itemTemplate"
#changed="inputChange"
#selected="itemSelected">
</vue-suggestion>
</div>
</template>
<script>
import itemTemplate from './item-template.vue';
export default {
data () {
return {
item: {},
items: [
{ id: 1, name: 'Golden Retriever'},
{ id: 2, name: 'Flying Squirrel'},
{ id: 3, name: 'Cat'},
{ id: 4, name: 'Catfish'},
{ id: 5, name: 'Squirrel'},
],
itemTemplate,
results: {}
}
},
methods: {
itemSelected (item) {
this.item = item;
},
setLabel (item) {
return item.name;
},
inputChange (text) {
// your search method
this.results = this.items.filter(item => item.name.includes(text));
// now `items` will be showed in the suggestion list
},
},
};
</script>

Formgroup binding in select boxes with Angular

I have a nested JSON array which i have to iterate to HTML using formgroup or formarray. This response is to be iterated into dynamically created select boxes depending on the length of array.
The JSON response coming in is:
var result = [{
id: 1,
options: [
{ option: 'Ram', toBeSelected: false },
{ option: 'Ravi', toBeSelected: true }
]
},
{
id: 2,
options: [
{ option: 'Pooja', toBeSelected: false },
{ option: 'Prakash', toBeSelected: false }
]
}
]
I have to iterate this into HTML in such a way that if any of these options have toBeSelected as true, that option should be preselected in HTML and if not, placeholder text can be shown.
According to the JSON in question, you can make it like:
ngOnInit() {
this.form = this._FormBuilder.group({
selections: this._FormBuilder.array([])
});
// the api call should be made here
this.jsonResponse.map(item => {
const opts = item.options.filter(o => {
return o.toBeSelected
});
if (opts.length) {
this.addSelections(opts[0].option);
} else {
this.addSelections();
}
});
}
get selections() {
return this.form.get('selections') as FormArray
}
addSelections(value?: string) {
this.selections.push(
this._FormBuilder.control((value ? value : ''))
);
}
Live view here.
Stackblitz link: https://stackblitz.com/edit/dynamic-form-binding-kx7nqf
Something along the lines of this?
<div *ngFor="let result of results">
<p>ID - {{ result.id }}</p>
<div *ngFor="let option of result.options">
<input
type="checkbox"
[checked]="option.toBeSelected">
{{ option.option }}
</div>
</div>
This isn't an example of FormGroup though but should help you understand how it can be done.
Sample StackBlitz

How to clear a Text Box in angularJS, when click any link in the page AngularJS

I've a search box and multiple categories links available in the HTML page.
as soon as user input in searchBox, i want to show:
p in Products | filter {serachedText}
When user clicks a category hyperlink, i want to show
p in Products | filter {categoryID}
But because serachedText is still avialble, result showing for
p in Products | filter {categoryID} | filter {serachedText}
Is there any way i can clear the serachedText as soon as user clicks on anylink.
That would be really easy to do in angularjs.
In html.
<input ng-model="mytext">
<a href ng-click="mytext=''">Clear Text</a>
jsfiddle is here.
Your filter expression is wrong.
if your data is a JSON array having category and name properties like so:
self.Products = [
{ category: 1, name: 'Pencil' },
{ category: 1, name: 'Notebook' },
{ category: 2, name: 'Kitten' }
];
And you are binding the following things for the selected category and search text:
self.category = 1;
self.searchText = 'pen';
You could create a complex filter expression like so:
filter: { category: vm.category | name: vm.searchText }
This will filter both on category and searchText or in combination.
To clear out the searchText, you can watch if category changes using $scope.$watch and when it changes, clear up the searchText.
Take a look at the example below or at http://plnkr.co/edit/OEDvOn. In the example, my filter expression is a bit more complicated since the selected category is actually an object containing value and name properties for the selected category, thus I need to add .value to get the right thing to pass to the filter.
Another point: For doing client side filtering, this is fine, but if you are filtering on server side, I'd rather get the filtering done on a service layer and just returned the filtered result instead of all possible data... save bandwidth and transfer time.
(function(undefined) {
'use strict';
angular.module('myApp',[]);
angular.module('myApp')
.controller('searchCtrl', searchCtrl);
searchCtrl.$inject = ['$log', '$scope'];
function searchCtrl($log, $scope) {
/* jshint validthis: true */
var self = this;
self.searchText = undefined;
self.categories = [
{ value: undefined, name: 'All' },
{ value: 1, name: 'Fruit' },
{ value: 2, name: 'Snacks' },
{ value: 3, name: 'Flower' },
{ value: 4, name: 'Pet' },
{ value: 5, name: 'Stationary' }
];
self.category = self.categories[0];
self.data = [
{ category: 1, name: 'Apple' },
{ category: 1, name: 'Grapes' },
{ category: 2, name: 'Dorito' },
{ category: 2, name: 'KitKat' },
{ category: 3, name: 'Roses' },
{ category: 3, name: 'Orchid' },
{ category: 4, name: 'Hamster' },
{ category: 4, name: 'Kitten' },
{ category: 5, name: 'Pencil' },
{ category: 5, name: 'Notebook' }
];
$scope.$watch(function() { return self.category; }, function(val, old) {
self.searchText = undefined;
});
}
}());
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="container" ng-app="myApp" ng-controller="searchCtrl as vm">
<div class="form-group">
<label>Category</label>
<select class="form-control" ng-options="cat.name for cat in vm.categories" ng-model="vm.category">
</select>
</div>
<div class="input-group">
<input class="form-control" type="textbox" ng-model="vm.searchText" placeholder="Search text here..." />
<span class="input-group-btn">
<button type="button" class="btn btn-primary">
<i class="glyphicon glyphicon-search"></i> Search</button>
</span>
</div>
<div class="well well-sm" style="margin-top:20px">
<ul ng-repeat="item in vm.data | filter:{category:vm.category.value, name:vm.searchText}">
<li>{{item.name}}</li>
</ul>
</div>
</div>
I'd suggest directive will be the best option
MarkUp
<input ng-model="mytext">
<a href ng-click="mytext=''" clear="mytext">Clear Text</a>
Directive
app.directive('a', function() {
return {
restrict: 'E',
scope: {
clear: '='
},
compile: function(scope, ele, attrs) {
ele.on('click', function() {
scope.$apply(function() {
scope.clear = undefined;
})
})
}
}
})