Vue watch and display deep changes - javascript

I'm trying to display a message for each category the user is selected
<v-select multiple style="position: relative; top: 20px;"
color="white" v-if="count == 3 && question" solo placeholder="Please Choose"
v-model="selected.category" :items="categories" item-text="name" return-object></v-select>
The user can select multiple categories, in each category there is a specific message i want to display.
I'm using watcher to watch the changes.
watch: {
'selected.category': {
handler(e) {
console.log(e)
this.icon = "mdi-emoticon"
this.text = this.selected.category.chat //This is an array now because multiple categories are selected
this.selection = true
}
},
}
The above code is wrong, it was only used when the user was able to select only one category, now i want them to be able to select multiple. I just can't figure out how to watch these deep changes and display the category message when multiple categories are selected. Is there any way i can send the latest selected category index in the handler or maybe only the selected object?

This made it work.
'selected.category': {
handler(e) {
console.log(e)
this.icon = "mdi-emoticon"
this.text = this.selected.category[this.selected.category.length - 1].chat
this.selection = true
}
},

Related

vuetify v-select with multiple options - getting selected/deselected option

I'm using Vuetify and its v-select component with multiple option enabled to allow selecting multiple options.
These options represent talent(candidate) pools for my CRM software.
What it needs to do is that when some option in v-select is checked, candidates from checked talent pool are fetched from API and saved to some array (let's call it markedCandidates), and when option is deselected, candidates from that pool need to be removed from markedCandidates array.
The problem is that #change or #input events return complete list of selected options. I need it to return just selected/deselected pool and information if it's selected or deselected, to be able to update the markedCandidates array.
My existing code:
<v-select return-object multiple #change="loadCandidatesFromTalentPool" v-model="markedCandidates" :item-text="'name'" :item-value="'name'" :items="talentPoolsSortedByName" dense placeholder="No pool selected" label="Talent Pools" color='#009FFF'>
<template slot="selection" slot-scope="{ item, index }">
<span v-if="index === 0">{{ item.name }}</span>
<span v-if="index === 1" class="grey--text caption othersSpan">(+{{ talentPools.length - 1 }} others)</span>
</template>
</v-select>
Any idea how to solve this?
As I said, loadCandidatesFromTalentPool(change) returns complete array of v-model (markedCandidates)..
EDIT:
I found this solution, it's more of a workaround actually, would be nice if there was dedicated event for this situation:
https://codepen.io/johnjleider/pen/OByoOq?editors=1011
Actually there is only one event related to changing values of v-autocomplete : #change (See https://vuetifyjs.com/en/components/autocompletes#events). The watch approach is useful if you want to monitor only individual changes. However, if you plan to do this with more selectors, it could be better if you create a custom reusable component with a new attached event (in this example, for the last change).
Vue.component('customselector',{
props:[
"value",
"items"
],
data: function() {
return {
content: this.value,
oldVal : this.value
}
},
methods: {
handleInput (e) {
this.$emit('input', this.content)
},
changed (val) {
oldVal=this.oldVal
//detect differences
const diff = [
...val.filter(x => !oldVal.includes(x)),
...oldVal.filter(x => !val.includes(x))
]
this.oldVal = val
var deleted=[]
var inserted=[]
// detect inserted/deleted
for(var i=0;i<diff.length;i++){
if (val.indexOf(diff[i])){
deleted.push(diff[i])
}else{
inserted.push(diff[i])
}
}
this.$emit("change",val)
this.$emit("lastchange",diff,inserted,deleted);
}
},
extends: 'v-autocomplete',
template: '<v-autocomplete #input="handleInput" #change="changed" :items="items" box chips color="blue lighten-2" label="Select" item-text="name" item-value="name" multiple return-object><slot></slot></v-autocomplete>',
})
Then you can use your component as simple as:
<customselector #lastchange="lastChange" >...</customselector>
methods:{
lastChange: function(changed, inserted, deleted){
this.lastChanged = changed
}
}
The changed only shows items which are actually changed. I've added the inserted and deleted arrays to return new items added or removed from the selection.
Source example: https://codepen.io/fraigo/pen/qQRvve/?editors=1011
Replace
:item-text="'name'" :item-value="'name'"
by
item-text="name" item-value="name"

vuejs Computed data detecting if model has been updated

I'm trying to detect if a Model Data has not been changed within a Vue Computed data.
I have two sets of Variables that need to be checked,
before Computed:filteredItems should return a new list or current list.
Below are two data i'm checking
text ( the text input )
selectedInput ( currently selected item )
Current Behavior:
I've changed, selectedInput to null, this updates Computed:filteredList to be triggered. which is expected.
The first Condition is to make sure that this update returns current list if text === selectedInput.text, work as expected
However, I need a second condition to detect if text has not been changed.
<input v-model="text" />
<ul>
<li v-for="item in filteredItems" #click="text=item.text"></li>
</ul>
{
data():{
text: 1,
items: [],
tempList: [],
selectedItem: {text: 1}
},
computed: {
filteredItems(){
// when selectedItem.text === current text input, do not run
if (this.selectedItem.text === text) return this.tempList;
// how do i detect if selectedItem.text has not been changed
if (this.selectedItem.text.hasNotChange??) return this.tempList;
}
}
}
Data Flow: 1update the text > 2filter list > 3click on listItem, update (1) text
[input(text): update on type ] >
[li(filteredItem): filter list on type by value (text) and (selectedInput.text) ] >
[li(item)#click: update (1), and also another value(selectedInput.text) input(text) to equal (item.text) ]
This cycle works until I have action somewhere else that updates selectedInput.text
is there something i can do with a setter/getter for the Text model.
Create a variable, changed. Watch selectedItem.text, and set changed to true. In a watcher on text, set changed to false.
I got this to work using a temp variable
data(){
return: {
text: "",
temp: {
text
}
}
}
computed(){
filteredList(){
var temporaryList,originalList,filteredList
if ((this.text === $store.state.selectedText )||
(this.text === this.temp.text ) ) {
return temporaryList || originalList
}
// update
this.temp.text = this.text
return filteredList
}
}
thought it would be a bad practice to update variables within a Computed method.

Making a sort of "tags" list in Angular

I've been trying to make in Angular, a list where you could select multiple items, and that these items would appear in a field near the select, and that you could delete them at will; like the "tags" field when you ask a question here.
So far, i did this :
<md-input-container class="md-block">
<p>{{classesSelected}}</p>
<md-select ng-model="classesSelected" multiples>
<md-option ng-value="classe.name" ng-repeat="classe in vm.classes">
{{classe.name}}
</md-option>
</md-select>
</md-input-container>
It gives me this :
here, you can see the list working like i'd want it to
and here the field with the data selected
i'd like that selecting an element would remove it from the list (or make it impossible to pick it again) and create a "block" in the field, with its name a little cross or something to remove it if i want to.
Picking another element from the list would ADD it to the field, and not reset it.
I have no idea how to do it, like i see the logic but i don't know how to realize it in Angular. Can someone help me out ?
Your solution might be to use two lists :
One list with the item the user can select
One list with the items the user selected
I'm not sure about what you want,
but here is a jsfiddle
function MyCtrl() {
this.options = ['Maternelle', 'CP', 'CE1', 'CM1'];//Possible to select
this.selected = [];//Chosen by user
}
MyCtrl.prototype.change = function(value) {//Called when the user select an option
if (value && value.length) {
this.selected.push(value);
this.options = this.options.filter(x => x != value);
}
}
MyCtrl.prototype.removeSelection = function(value) {//Called when the user click on the little cross
if (value && value.length) {
this.options.push(value);
this.selected = this.selected.filter(x => x != value);
}
}

knockout: remove one element from multiselect control when other is selected

I'm using knockout and I have a list of item, let say:
Tomato,
Potato,
Broccoli,
Bean
all those item are allowed to user to select from multiselect form-control.
<select class="form-control" multiple
data-bind="selectPicker: Types,
optionsText: 'Name',
optionsValue: 'VegetableTypeId',
selectPickerOptions: { optionsArray: AvailableVegetableTypes }">
</select>
Except one scenario - when the user selects tomato, potato should unselect.
I was trying to use subscription on selected items array:
this.Types.subscribe(changes => {
var VegetableTypes = this.AvailableVegetablesTypes();
var company = VegetableTypes.First(element => element.VegetableTypeId == changes[0].value);
if (changes[0].status == "added") {
if (Vegetable.IsTomato) {
this.Types.remove(element =>
VegetableTypes.First(baseElement =>
baseElement.VegetableTypesTypeId == element && baseElement.IsPotato));
} else if (Vegetable.IsPotato) {
this.Types.remove(element =>
VegetableTypes.First(baseElement =>
baseElement.VegetableTypesTypeId == element && baseElement.IsTomato));
}
}
}, null, "arrayChange");
Problem is that I'm using ObservableArray.Remove, so it's again call my function before current run is finish. This should not be a problem, because after remove first change is "deletion" type, so whole logic should not be executed.
But after this, when I select tomato/potato again, nothing is fired. In the end I actually have both tomato and potato selected.
Then, when I deselect one of these two and select it again, everything works fine, and then the whole situation repeats.
Do you have any ideas?
I didn't understand why you are using selectPicker bindings instead of the normal options and selectedOptions bindings available in Knockout.
However, I built a simple demo which implements the desired behaviour. You can find it here:
http://jsbin.com/fofojaqohi/1/edit?html,js,console,output
Note that, whenever you select Tomato after Potato, Potato will become unselected.
You were on the right track: you need to subscribe to the array of selected items and check if there are any invalid selections. I hope this helps.
For reference, here is the code:
HTML:
<select class="form-control" multiple="true"
data-bind="options: availableVegetableTypes, selectedOptions: selected">
</select>
JS:
var availableVegetableTypes = ['Tomato',
'Potato',
'Broccoli',
'Bean'];
var selected = ko.observableArray();
var unselectMap = {
'Tomato': 'Potato'
};
selected.subscribe(function(selectedOptions){
var keysToUnselect = [];
console.log("Selected", selectedOptions);
selectedOptions.forEach(function(selectedOption){
if (unselectMap[selectedOption] != null) {
// This key exists in the unselect map
// Let's check if the value is in the array
if (_.contains(selectedOptions, unselectMap[selectedOption])) {
// The invalid key exists. Let's mark it for removal.
keysToUnselect.push(unselectMap[selectedOption]);
}
}
});
if (keysToUnselect.length > 0) {
console.log("Unselect", keysToUnselect);
var reject = function(v){
return _.contains(keysToUnselect, v);
};
filteredSelectedOptions = _.reject(selectedOptions, reject);
console.log("Filtered", filteredSelectedOptions);
selected(filteredSelectedOptions);
}
});
ko.applyBindings({
availableVegetableTypes:availableVegetableTypes,
selected: selected
});

label inside combobox and conditional multiselect

I am building a pretty combobox with checkboxes and conditional entries. Everything works out alright, except for two features that I cannot figure out how to implement.
1) I would like to move the label inside the combobox, make it shift the values to the right, and appear in a slightly gray color.
2) I would like the value to ignore certain entries (group headers) selected. Those entries are there for functionality only - to select/unselect groups of other entries.
The entire project is in the zip file. You don't need a server, it's a client base app. Just download the archive, unpack, and launch app.html in your browser.
http://filesave.me/file/30586/project-zip.html
And here's a snapshot of what I would like to achieve.
Regarding your second issue, the best way I see is to override combobox onListSelectionChange to filter the values you don't want:
onListSelectionChange: function(list, selectedRecords) {
//Add the following line
selectedRecords = Ext.Array.filter(selectedRecords, function(rec){
return rec.data.parent!=0;
});
//Original code unchanged from here
var me = this,
isMulti = me.multiSelect,
hasRecords = selectedRecords.length > 0;
// Only react to selection if it is not called from setValue, and if our list is
// expanded (ignores changes to the selection model triggered elsewhere)
if (!me.ignoreSelection && me.isExpanded) {
if (!isMulti) {
Ext.defer(me.collapse, 1, me);
}
/*
* Only set the value here if we're in multi selection mode or we have
* a selection. Otherwise setValue will be called with an empty value
* which will cause the change event to fire twice.
*/
if (isMulti || hasRecords) {
me.setValue(selectedRecords, false);
}
if (hasRecords) {
me.fireEvent('select', me, selectedRecords);
}
me.inputEl.focus();
}
},
And change your onBoundlistItemClick to only select and deselect items in the boundlist not to setValue of the combo:
onBoundlistItemClick: function(dataview, record, item, index, e, eOpts) {
var chk = item.className.toString().indexOf('x-boundlist-selected') == -1;
if ( ! record.data.parent) {
var d = dataview.dataSource.data.items;
for (var i in d) {
var s = d[i].data;
if (s.parent == record.data.id) {
if (chk) { // select
dataview.getSelectionModel().select(d[i],true);
} else { // deselect
dataview.getSelectionModel().deselect(d[i]);
}
}
}
}
},
Regarding your first issue, it is easy to add the label using the displayTpl config option. But this will only add the text you need, without any style (grey color, etc). The combo is using a text input, which does not accept html tags. If you don't need the user to type text, than you may want to change the combo basic behavior and use another element instead of the text input.

Categories