b-form-select get selected option as value - bootstrap-vue

I'm having some trouble understanding b-form-select from bootstrap-vue.
I have a list of object lets say
factories = [{ id: 1, name: "A" }, { id: 2, name: "B" }]`
And my select as
<b-form-select
v-model="factory"
:options="factories"
value-field="id"
text-field="name"
/>
But how would I do to get the full selected object rather than just the id without having to declare options manually ?
This works but it feels 'hacky'.
<b-form-select v-model="factory">
<option v-for="f in factories" :value="f" :key="f.id">{{f.name}}</option>
</b-form-select>
If not possible, any reasons why ?

This doesn't work because <b-form-select> requires the options array to take the form of:
[{value: factory, text: factory.name}]
Put another way, <b-form-select> doesn't know what to do with [{id: 1, name: 'A'}]
To make your first form work, you'll need to transform the array 'factories' into an array the can use:
const factories = [{ id: 1, name: "A" }, { id: 2, name: "B" }]
var factoriesSelectList = []
factories.forEach((factory, index, mechanicsArray) => {
var selectListOption = {
value: factory,
text: factory.name
}
factoriesSelectList.push(selectListOption)
})
Then, in your template:
<b-form-select
v-model="factory"
:options="factoriesSelectList"
/>
Note: This probably isn't much different than doing it in the template as in your second form. I've not looked, but I'm betting the resulting javascript is similar.

Related

Proper custom component with complex data in it

I have following interface:
export interface Product {
name: string;
provider: {
name: string;
logo: string;
};
pricePerUnit: {
quantity: number;
currency: string;
};
}
And my rowData looks like this:
rowData = [
{
name: 'Fish',
provider: {
name: 'Amazon',
logo: 'url to amazon logo',
},
pricePerUnit: {
quantity: 5,
currency: 'USD',
},
},
]
So, as you can see i have at least 2 complex object here, and by design I should display provider as img + name and price as quantity + currency symbol.
I`m using custom angular components for that with styling.
Actual problem
In order to provide these object to my custom components, I set field property in colDefs as follow (example for price):
{
headerName: 'Price',
field: 'pricePerUnit',
cellRenderer: PriceCellRendererComponent,
},
And here is the catch, because I specified in field property complex object, I no longer able to visualize data using integrated charts, because for them to work I should specify in my field propery path to number itself, like so:
{
field: 'pricePerUnit.quantity',
}
But now I`ve broke my custom component because params.value now holds just a number and not my complex object. Same goes to provider.
And it`s also broke grouping, sorting, filtering.
html template for one of my custom component (provider) looks like so:
<div class="wrapper provider">
<tui-avatar [avatarUrl]="params.value.logo" class="provider__logo"></tui-avatar>
<div class="provider__name">{{params.value.name}}</div>
</div>
So the question is:
How to properly setup custom components, so they would work in grouping, sorting, filtering and also integrated charts would use just simple primitive like number to correctly display data?

How to get v-select items inside items?

I have a dropdown with v-select
<v-select
label="Select"
v-bind:items="companies"
v-model="e11"
item-text="employee.name"
item-value="name"
max-height="auto"
>
</v-select>
Data from API like
new Vue({
el: '#app',
data () {
return {
e11: [],
companies: [
{
companyName: 'Google',
employee: [{
name: 'Sandra Adams',
name: 'Ali Connors',
name: 'Trevor Hansen',
name: 'Tucker Smith',
}]
},
{
companyName: 'Facebook',
employee: [{
name: 'Sandra Adams',
name: 'Ali Connors',
name: 'Trevor Hansen',
name: 'Tucker Smith',
}]
},
{
companyName: 'Twitter',
employee: [{
name: 'Sandra Adams',
name: 'Ali Connors',
name: 'Trevor Hansen',
name: 'Tucker Smith',
}]
},
]
}
}
})
In dropdown item getting [object object] to fixed fixed it? I want to get dropdown list with grouped by company name.
Here is what I want to do
Here is the codepen link https://codepen.io/yurenlimbu/pen/bGVKGEa?editors=1011
I had a crack at it, as ive also had some issues in the past with vuetify dropdown, i managed to get it to work, you may need to update your current data objects to make the name key unique in the list.
Added computed property to map companies and employees and inject the employees company so we have one object with the data we need
mappedNames: function() {
return this.companies.map(function(person) {
person.employee.forEach(e => e.company = person.companyName)
return person.employee
})
}
Helper function to concat the arrays from the map
item-value prop update to access entire object, and linked to e11 v-model
Here is the codepen:
https://codepen.io/mcwalshh/pen/Vwvdvve
EDIT:
I've included the slot for item and selected item, so you can change how the data is displayed inside the select:
<template v-slot:selection="data">
{{ data.item.name }} - {{ data.item.company }}
</template>
ref: https://vuetifyjs.com/en/components/selects/
Here is the updated codepen:
https://codepen.io/mcwalshh/pen/rNOKqvd
EDIT #2:
Not really sure how to go about adding titles, i suggest reading more vuetify documentation, or perhaps in my example, use the helper in the text-name instead of the items to access companyName. But following from my previous posts, i think the potential way of doing it, is to inject/unshift an object into the employees with:
{ name: person.employee.companyName, type: "title" }
For now in my example i add it straight to the object, then use template v-if's to determine what is a title:
codepen: https://codepen.io/mcwalshh/pen/NWGzeoK
Now just a matter of targeting parent to match your gif, but im in no way saying this is the best approach, like i said before i had issues with vuetify in the past, so i hope this can help get you by until you work out the best method

Buffer grouped observables

I have the following class and array
class Hero {
id: number;
name: string;
}
const HEROES: Hero[] = [
{ id: 11, name: 'Narco' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Bombasto' },
{ id: 15, name: 'Bombasto' },
{ id: 16, name: 'Dynama' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dynama' },
{ id: 19, name: 'Dynama' },
{ id: 20, name: 'Dynama' }
];
I want to create an observable that treats the HEROES array as the source, groups the array by name and emits the result as a single array, i.e., I should end up with three arrays, one for Narco, one for Bombasto and one for Dynama.
I can create my source observable as follows
var heroSource = Observable.create((observer:any) => {
HEROES.forEach((hero: Hero) => {
observer.next(hero);
})
});
I can then group heros by using groupBy, i.e.,
var groupHeroSource = heroSource
.groupBy((hero: Hero) => {return hero.name});
This effectively gives me as many observables as there are different names in my HEROES array (three in this case). However, these will be emitted as a sequence and ideally I'd like to buffer them until heroSource is complete. How would I use the buffer operator in rxjs on my grouped observables so that emit a single collection?
First of all you can create your initial observable much simpler:
var heroSource = Observable.from(HEROES);
Next, your groupBy mapper/selector can be abbreviated to:
var groupHeroSource = heroSource.groupBy((hero: Hero): string => hero.name);
To solve the original problem I needed to buffer the streams, since they are ready a buffer with time of 0 would do the work (I guess there should be a more elegant solution out there), use take(1) to take only the first result (and avoid a repeating buffer) and then merge all:
var finalGroup = groupHeroSource.map((ob) => ob.bufferWithTime(0).take(1)).mergeAll();
Note that since that since your array is actually static, putting it through a stream and then mapping it might not be the simplest solution, you can simply reduce it:
var grouped = HEROES.reduce((acc: any, hero: Hero) => {
acc[hero.name] = acc[hero.name] || [];
acc[hero.name].push(hero);
return acc;
}, {});
Since Object.values is not standard, you'll have to iterate the keys to get an array, yet, it might be a better fit for your need

Using search with Bloodhound

I am trying to find out how Bloodhound works (without typeahead).
var engine = new Bloodhound({
local: [{ id: 1, name: 'dog' }, { id: 2, name: 'pig' }],
identify: function(obj) { return obj.id; },
queryTokenizer: Bloodhound.tokenizers.whitespace,
datumTokenizer: Bloodhound.tokenizers.whitespace
});
engine.search('do', function(datums) {
console.log(datums); // results: []
});
In this very basic example, why does my search not return my first item? What I am doing wrong?
Out of the box, Bloodhound tokenizers work for an array of a basic type. You have "complex" data (an object with 2 properties), so you must tell Bloodhoud what to tokenize using the obj tokenizer and passing property names:
datumTokenizer: Bloodhound.tokenizers.obj.whitespace("id", "name"),

angularstrap typeahead with json object array is not working

I am using angularstrap typeahead directive. Its working fine with single object json values but its not working when replacing the json with my json object array.
Demo Json:
typeahead= ["Alabama","Alaska","Arizona","Arkansas","California","Colorado","Connecticut","Delaware","Florida","Georgia"];
<input type="text" ng-model="typeaheadValue" bs-typeahead="typeahead">
The above code is working fine.
My JSON object array:
typeahead = [
{id: 1, name: 'name1', email: 'email1#domain.com'},
{id: 2, name: 'name2', email: 'email2#domain.com'},
{id: 3, name: 'name3', email: 'email3#domain.com'}
];
$scope.typeaheadFn = function(query) {
return $.map($scope.typeahead, function(contacts) {
return contacts;
});
}
<input type="text" ng-model="typeaheadValue" bs-typeahead="typeaheadFn">
Please give me some solution for this.
You want to map your items to a list of strings, I believe.
Try:
$scope.typeaheadFn = function(query) {
return $.map($scope.typeahead, function(contact) {
return contact.name;
});
}
(I should add that I am currently stumped by something similar)
If you have, for example:
items = [
{id: 1, name: 'name1', email: 'email1#domain.com'},
{id: 2, name: 'name2', email: 'email2#domain.com'},
{id: 3, name: 'name3', email: 'email3#domain.com'}
];
You will need:
<input type="text" bs-typeahead ng-model="selectedItem" ng-options="item.name for item in items|orederBy:'name'|filter:{name:$viewValue}:optionalCompareFn"></input>
If you exclude filter from ng-options matching will be done on every property of item object, so if you want it to be done on one property add filter:{propName:$viewValue}. Also, if you exclude optionalCompareFn, default comparison from angular will be applied, but you can add your custom one (on your $scope), with signature (actual is property value of the item, stated in filter, not the whole object).
optionalCompareFn(expected,actual){ return /compare and return true or false/}
Attempt 1
I finally got this semi-working after a huge amount of frustration.
An easy way to get your desired text appearing is for each item to have a toString method.
You might have something like
typeaheadData = [
{id: 1, text: "abc", toString: function() { return "abc"; }},
{id: 2, text: "def", toString: function() { return "def"; }}
]
Then you will see the correct text in the options that popup, but the matching won't yet work properly (the items shown by the widget won't match the text the user enters in the box).
To get this working I used the new filter option that's been added in the current git version of angular-strap. Note that it's not even in the pre-built dist/angular-strap.js file in the repository, you will need to rebuild this file yourself to get the new feature. (As of commit ce4bb9de6e53cda77529bec24b76441aeaebcae6).
If your bs-typeahead widget looks like this:
<input bs-typeahead ng-options="item for item in items" filter="myFilter" ng-model="myModel" />
Then the filter myFilter is called whenever the user enters a key. It's called with two arguments, the first being the entire list you passed to the typeahead, and the second being the text entered. You can then loop over the list and return the items you want, probably by checking whether the text matches one or more of the properties of an item. So you might define the filter like this:
var myApp = angular.module('myApp', ['mgcrea.ngStrap'])
.filter('myFilter', function() {
return function(items, text) {
var a = [];
angular.forEach(items, function(item) {
// Match an item if the entered text matches its `text` property.
if (item.label.indexOf(text) >= 0) {
a.push(item);
}
});
return a;
};
});
Unfortunately this still isn't quite right; if you select an item by clicking on it, then the text parameter will be the actual object from the items list, not the text.
Attempt 2
I still found this too annoying so I made a fork of angular-strap (https://github.com/amagee/angular-strap) which lets you do this:
typeaheadData = [
{id: 1, text: "abc"},
{id: 2, text: "def"}
]
//...
$scope.myFormatter = function(id) {
if (id == null) { return; }
typeaheadData.forEach(function(d) {
if (d.id === id) {
return d.text;
}
});
};
<input bs-typeahead ng-options="item for item in items" ng-model="myModel"
key-field="id" text-field="text" formatter="myFormatter" />
With no need to fuss around with toString methods or filters. The text-field is what the user sees, and the key-field is what is written to the model.
(Nope! Still doesn't work if you update the model without going through the view).
The formatter option is needed so when you set the model value to just the key field, the widget can figure out the right text to display.