MobX - Select single item in array, unselect all others? - mobx

I have the following observable array of search engines.
#observable favoriteSearchEngine = [
{ 'provider' : 'google', 'selected': true },
{ 'provider' : 'yahoo', 'selected': false },
{ 'provider' : 'bing', 'selected': false },
];
The user should only be able to select one at a time from the UI. So if they choose yahoo for example, yahoo would get selected: true and any other provider would get selected: false
This action handles the click:
#action onClickFavoriteSearchEngine = (provider) => {
alert(provider); // yahoo shows here
// How to do this step, only selected provider true and falsify all others in the array?
}

The solution given by #mweststrate works great, but since you are using an action (which also is a transaction), you could just unselect the previously selected, and select the new one if you would prefer:
#action onClickFavoriteSearchEngine = (provider) => {
alert(provider); // yahoo shows here
favoriteSearchEngine.forEach(e => e.selected = false);
favoriteSearchEngine.find(e => e.provider === provider).selected = true;
}

I would introduce a single observable representing selection, and derive the selected state from that:
#observable selection = null
#observable favoriteSearchEngine = [
{ 'provider' : 'google', 'selected': function() {
return selection === this
}
]
If you now assign another engine to the selection a few times, you will see that the selected state of the engines will update accordingly
(N.B. don't use arrow functions if declaring a plain object + derivation like this, to avoid issues with this)

Related

Computed not reactive?

I wrote this code to return a list of skills. If the user already has a specific skill, the list-item should be updated to active = false.
This is my initial code:
setup () {
const user = ref ({
id: null,
skills: []
});
const available_skills = ref ([
{value: 'css', label: 'CSS', active: true},
{value: 'html', label: 'HTML', active: true},
{value: 'php', label: 'PHP', active: true},
{value: 'python', label: 'Python', active: true},
{value: 'sql', label: 'SQL', active: true},
]);
const computed_skills = computed (() => {
let result = available_skills.value.map ((skill) => {
if (user.value.skills.map ((sk) => {
return sk.name;
}).includes (skill.label)) {
skill.active = false;
}
return skill;
});
return result;
})
return {
user, computed_skills
}
},
This works fine on the initial rendering. But if I remove a skill from the user doing
user.skills.splice(index, 1) the computed_skills are not being updated.
Why is that the case?
In JavaScript user or an object is a refence to the object which is the pointer itself will not change upon changing the underling properties hence the computed is not triggered
kid of like computed property for an array and if that array get pushed with new values, the pointer of the array does not change but the underling reference only changes.
Work around:
try and reassign user by shadowing the variable
The computed prop is actually being recomputed when you update user.skills, but the mapping of available_skills produces the same result, so there's no apparent change.
Assuming user.skills contains the full skill set from available_skills, the first computation sets all skill.active to false. When the user clicks the skill to remove it, the re-computation doesn't set skill.active again (there's no else clause).
let result = available_skills.value.map((skill) => {
if (
user.value.skills
.map((sk) => {
return sk.name;
})
.includes(skill.label)
) {
skill.active = false;
}
// ❌ no else to set `skill.active`
return skill;
});
However, your computed prop has a side effect of mutating the original data (i.e., in skill.active = false), which should be avoided. The mapping above should clone the original skill item, and insert a new active property:
const skills = user.value.skills.map(sk => sk.name);
let result = available_skills.value.map((skill) => {
return {
...skill,
active: skills.includes(skill.label)
}
});
demo
slice just returns a copy of the changed array, it doesn't change the original instance..hence computed property is not reactive
Try using below code
user.skills = user.skills.splice(index, 1);

Make react-select option unsearchable

I'm using react-select and have one option with a blank name and the value of not set and another with the name and value of notification. This means my options array looks something like this (though with many more options):
const options = [
{
label: '',
value: 'not set'
},
{
label: 'Notification',
value: 'notification'
}
];
Because of this, when users are looking for the notification option, they often just type not and then are shown the blank option.
I do want this option to show in the dropdown menu itself if someone is just thumbing through it (with arrow keys) but is there a way I can prevent certain options from showing in the search? Maybe a key I can include in the option?
I recommend you to use the props filterOption to achieve your goal. See more how to use it here.
You can either decide to filter by label only instead of value or add a key for each searchable option like this:
const options = [
{
label: '',
value: 'not set',
searchable: false
},
{
label: 'Notification',
value: 'notification',
searchable: true
}
];
// your custom filterOption function
filterOption = ({ label, value, data }, string) => {
if (string && data.searchable) {
return label.includes(string) || value.toString().includes(string);
} else {
return true;
}
};

How do get focus to return to input on selection with enter

Using Vue Multiselect to create an unordered list, how would I get the focus to return to the input when a user hits enter to select an item? That is:
1. user searches item
2. user hits enter
3. item is added to list (as currently happens)
4. focus is returned to input with dropdown closed so the user can type in a new entry.
Would be amazingly grateful for any help :)
I’ve tried changing close-on-select to false which returns the focus correctly but leaves the dropdown open, which is not ideal as it hides the content below.
https://jsfiddle.net/hugodesigns/rtogxe4s/?utm_source=website&utm_medium=embed&utm_campaign=rtogxe4s
new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
data: {
options: [
],
optionsProxy: [],
selectedResources: [],
showLoadingSpinner: false
},
methods: {
customLabel (option) {
return `${option.name} - ${option.version}`
},
updateSelected(value) {
value.forEach((resource) => {
// Adds selected resources to array
this.selectedResources.push(resource)
})
// Clears selected array
// This prevents the tags from being displayed
this.optionsProxy = []
},
cdnRequest(value) {
this.$http.get(`https://api.cdnjs.com/libraries?search=${value}&fields=version,description`).then((response) => {
// get body data
this.options = []
response.body.results.forEach((object) => {
this.options.push(object)
});
this.showLoadingSpinner = false
}, (response) => {
// error callback
})
},
searchQuery(value) {
this.showLoadingSpinner = true
// GET
this.cdnRequest(value)
},
removeDependency(index) {
this.selectedResources.splice(index, 1)
}
},
created() {
const value = ''
this.cdnRequest(value)
}
}).$mount('#app')
Current result is that an item is added to the list and the multiselect loses focus, so must be clicked again to enter more input.
Expected behaviour is that an item is added to the list and focus returns to the multiselect, ready to accept input, with dropdown closed.
It seems like dropdown opening on focus is a behavior was flagged as an issue for version 2. If you upgrade the package the dropdown will not automatically open on focus.
https://github.com/shentao/vue-multiselect/issues/740
You should be able to get the element to go into focus by adding a ref to the multiselect and doing the following:
this.$refs.multiselectref.$el.focus();

Using map to reduce in Gun

I am new to Gun. I have existing code that very effectively reduces an array of objects based on a pattern. I am thinking I should tweak this to run in the context of Gun's .map and return undefined for non-matches. I think I will also have to provide two arguments, one of which is the where clause and the other the properties I want shown on returned objects. I also presume that if I use .on future matches will automagically get spit out! Am I on the right path?
const match = (object,key,value) => {
const type = typeof(value);
if(value && type==="object") {
return Object.keys(value).every(childkey =>
match(object[key],childkey,value[childkey]));
if(type==="function") return value(object[key]);
return object[key]===value;
}
const reduce = (objects,where) => {
const keys = Object.keys(where);
return objects.reduce((accumulator,current) => {
if(keys.every(key => match(current,key,where[key]))) {
accumulator.push(current);
}
return accumulator;
},[]);
}
let rows = reduce([{name: "Joe",address:{city: "Seattle"},age:25},
{name: "Mary",address:{city: "Seattle"},age:16},
{name: "Joe",address:{city: "New York"},age:20}],
{name: () => true,
address: {city: "Seattle"},
age: (age) => age > 10});
// results in
[{name: "Joe",address:{city: "Seattle"},age:25},
{name: "Mary",address:{city: "Seattle"},age:16}]
Further exploration of this resulted in the code below, which is stylistically different, but conforms to the immediate responsive nature of Gun. However, it is unclear how to deal with nested objects. The code below only works for primitives.
const match = (object,key,value) => {
const type = typeof(value);
if(!object || typeof(object)!=="object") return false;
if(value && type==="object") {
const child = gun.get(object[key]["#"]);
for(let key in value) {
const value = {};
child.get(key).val(v => value[key] = v,{wait:0});
if(!match(value,key,value[key])) return;
}
}
if(type==="function") return value(object[key]);
return object[key]===value;
}
const gun = Gun(["http://localhost:8080/gun"]),
users = [{name: "Joe",address:{city: "Seattle"},age:25},
{address:{city: "Seattle"},age:25},
{name: "Mary",address:{city: "Seattle"},age:16},
{name: "Joe",address:{city: "New York"},age:20}];
//gun.get("users").map().put(null);
for(let user of users) {
const object = gun.get(user.name).put(user);
gun.get("users").set(object);
}
gun.get("users").map(user => {
const pattern = {name: (value) => value!=null, age: (age) => age > 20}; //, address: {city: "Seattle"}
for(let key in pattern) {
if(!match(user,key,pattern[key])) return;
}
return user;
}).on(data => console.log(data));
Yes. GUN's .map method does more than what it seems.
Say we have var users = gun.get('users'). We can do:
users.map() with no callback acts like a forEach because the default callback is to return the data as-is.
users.map(user => user.age * 2) with a callback, it lets you transform the data like you would expect from a map, except where:
users.map(function(){ return }) if you return undefined, it will filter out that record.
WARNING: As of the current time, .map(transform) function is currently experimental and my have bugs with it. Please try it and report any you find.
Now we can combine it with some other methods, to get some cool behavior:
users.map().on(cb) will get current and future users as they are added to the table, and gets notified for updates on each of those users.
users.map().val(cb) will get current and future users as they are added to the table, but only gets each one once.
users.val().map().on(cb) gets only the current users (not future), but gets the updates to those users.
users.val().map().val(cb) gets only the current users (not future), and only gets them once.
So yes, you are on the right track. For instance, I have a test in gun core that does this:
list.map(user => user.age === 27? user.name + "thezombie" : u).on(function(data){
// verify
});
list.set({name: 'alice', age: 27});
list.set({name: 'bob', age: 27});
list.set({name: 'carl', age: 29});
list.set({name: 'dave', age: 25});
This creates a live map that filters the results and locally (view only) transforms the data.
In the future, this is how the SQL and MongoDB Mango query extensions will work for gun.
Note: GUN only loads the property you request on an object/node, so it is bandwidth efficient. If we do users.map().get('age') it will only load the age value on every user, nothing else.
So internally, you can do some efficient checks, and if all your conditionals match, only /then/ load the entire object. Additionally, there are two other options: (1) you can use an in-memory version of gun to create server-side request-response patterns, so you can have server-side filtering/querying that is efficient. (2) if you become an adapter developer and learn the simple wire spec and then write your own custom query language extensions!
Anything else? Hit me up! More than happy to answer.
Edit: My reply in the comments, comments apparently can't have code. Here is pseudo-code of how to "build up" more complex queries, which will be similar to how SQL/Mango query extensions will work:
mutli-value & nested value matching can be "built up" from this as the base, but yes, you are right, until we have SQL/Mango query examples, there isn't a simple/immediate "out of the box" example. This is pseudo code, but should get the idea across:
```
Gun.chain.match = function(query, cb){
var gun = this;
var fields = Object.keys(query);
var check = {};
fields.forEach(function(field){
check[field] = true;
gun.get(field).val(function(val){
if(val !== query[field]){ return }
check[field] = false;
//all checks done?
cb(results)
});
});
return gun;
}
```
Solution, the trick is to use map and not val:
Gun.chain.match = function(pattern,cb) {
let node = this,
passed = true,
keys = Object.keys(pattern);
keys.every(key => {
const test = pattern[key],
type = typeof(test);
if(test && type==="object") {
node.get(key).match(test);
} else if(type==="function") {
node.get(key).map(value => {
if(test(value[key])) {
return value;
} else {
passed = false;
}
});
} else {
node.get(key).map(value => {
if(value[key]===test) {
return value;
} else {
passed = false;
}
});
}
return passed;
});
if(passed && cb) this.val(value => cb(value))
return this;
}
const gun = new Gun();
gun.get("Joe").put({name:"Joe",address:{city:"Seattle"},age:20});
gun.get("Joe").match({age: value => value > 15,address:{ city: "Seattle"}},value => console.log("cb1",value));

Store filter in sencha touch

I have store having structure :
Ext.create('Ext.data.Store', {
fields: [
'title'
],
data: [{
title: 'ABC'
}, {
title: 'ABC2'
}, {
title: 'ABC3'
}, {
title: 'ABC4'
}, {
title: 'ABC5'
}, {
title: 'ABC6'
}]
});
So when I load this store List get populated with all 6 records.
I just wanted to Filter this store on button click I just wanted to get some selected record out of this 6 record Can It be possible.
Provide me Some Idea or Working code.
To filter the store based on title
Ext.getStore('storeId').filter("title", "ABC3");
To clear filter
Ext.getStore('storeId').clearFilter();
See store filter doc
Update
Ext.getStore('storeId').filterBy(function(record){
var title = record.get('title');
if(title == "ABC" || title == "ABC1" || title == "ABC2")
return record;
});
My approach is to set a filter on the store when I tap on the button. In my case it was a selectfield and on the change event I filter compared to the current value in the selectfield
onChangeStatusSelectfield: function (newValue, oldValue) {
var store = Ext.getStore('CustomVacationRequest');
console.log('Accepted Filter');
newValue = this.getStatusSelectfield().getValue();
console.log(store, newValue);
store.clearFilter();
if (store != null);
store.filter(function (record) {
if (newValue == record.data.status) { //your data from the store compared to
//the value from the selectfield
return true;
}
Ext.getCmp("VacationRequestsManagerList").refresh() //refresh your list
});
},
This is just my part of the controller. Handle events and buttons and stores at your own choice&need. Good luck!