Aurelia + Select2 custom element not propagating selected changed - binding

I have created a custom element in Aurelia.
import {bindable, inject, customElement, bindingMode} from 'aurelia-framework';
import 'select2';
import * as $ from 'jquery';
import {BindingSignaler} from "aurelia-templating-resources";
#customElement('select2')
#inject(Element, BindingSignaler)
export class Select2CustomMultiselect {
#bindable name = null; // name/id of custom select
#bindable selected = null; // default selected values
#bindable ({defaultBindingMode: bindingMode.oneWay, attribute:"options"}) source:Array<{id:number, name:any}>= []; // array of options with id/name properties
#bindable placeholder = "";
#bindable allow_clear = true;
private $select2: $;
constructor(private element, private signaler:BindingSignaler) {
}
attached() {
let $select = $(this.element).find('select');
this.$select2 = $select.select2({theme: 'bootstrap', placeholder: this.placeholder});
// on any change, propagate it to underlying select to trigger two-way bind
this.$select2.on('change', (event) => {
if (event.originalEvent) { return; }
const select2Value = this.$select2.val();
if(select2Value == this.selected) { return; }
// dispatch to raw select within the custom element
var notice = new Event('change', {bubbles: true});
event.target.dispatchEvent(notice);
});
this.$select2.val(this.selected);//.trigger('change');
}
selectedChanged(newValue,oldValue){
console.log(newValue);
}
detached() {
$(this.element).find('select').select2('destroy');
}
}
And it's template:
<template>
<select value.two-way="selected" name.one-way="name" id.one-way="name" class="form-control" data-allow-clear.one-way="allow_clear" size="1">
<option></option>
<option repeat.for="src of source" model.bind="src.id">${src.name & t}</option>
</select>
</template>
I use the control like this:
<select2 name="payingBy" selected.two-way="model.countryId & validate" options.bind="countries" placeholder="${'Select' & t}" allow_clear="true"></select2>
And model is:
countries:Array<{id:number, name:string}> = [{id:1, name:"USA"}, {id:2, name:Canada'}];
model.countryId: number;
Now, everything works fine if I change the select and on initial binding.
But if i change the model.countryId from ie. 1 to 2, the change is not reflected in the select control, the control still displays "USA" as like 1 is selected.
Because 'selected' property is bind two-way I would expect it to update the select when it change. But it does not. Why?
What Am I doing wrong?
Please help

Ok, I implemented it like in this post:Custom Select2 Aurelia component
And it works perfectly.

That is because you are using the data version which expects an object, but you have set your select to work with the id value only. So you should use the val to pass the id.
selectedChanged(newValue, oldValue) {
console.log(newValue);
if (this.select2) {
this.select2.select2({
val: newValue, // << changed this from data: newValue
theme: 'bootstrap',
placeholder: this.placeholder
});
}

Related

how to use NgIF condition with formcontrol instead of ngModel

I want to compare my values using my formcontrol than using ngModel, when values is entered in my input box , I want to display my cancel image, so I given userTextValue as true inside subscribe, my query now is how to reset the value when cancel is clicked . I want the input box to be empty , now cancel button is hidden but still values available, I am using pipe to filter values.
<input matInput class="form-control input" placeholder="Enter name or Id" id="user"
[formControl]="userControl"[matAutocomplete]="auto>
<img src="assets/icons/cancel.svg" *ngIf="userTextvalue" class="cancel-icon"
aria-label="Clear" (click)="clearUserValues()">
ts:
constructor() {
this.userControl.valueChanges.subscribe((user) => {
this.filterResult = user;
this.userTextvalue = true;
});
}
clearUserValues() {
this.filterResult = "";
this.userTextvalue = false;
}
pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filterUser'
})
export class FilterUserPipe implements PipeTransform {
transform(items: any[], searchText: string): any[] {
if (searchText && searchText.length > 1) {
items = searchText ? this.filterUsers(items, searchText) : items.slice();
} else {
items = [];
}
return items;
}
filterUsers(items: any[], value: string): any[] {
const filterValue = value.toLowerCase();
return items.filter(item => item.name.toLowerCase().indexOf(filterValue) === 0);
}
}
First of All you have to get knowledge about what is difference between template driven and reactive form approach. When any change happens in formcontrol, it return new object of form (included its formcontrols) and that's what make it synchronous approach.
Let me wrap up this in short scenario.
When any change happens in formcontrol input or html tag
it could be tracked by subscribed it.
for example
// getting specific FormControl changed value.
// form: FormGroup
this.form.get('name of your formControl').subscribe(value => {// here (value) is changed value of that particular formControl. });
2nd Approach
For example you have formcontrol on any clickable input button or select input.
then you can emit an method on event click and on that method you can subscribe changed value and compare or save it to where you want.
sample code ::::
<mat-select formControlName="transferType">
<mat-option [value]="type" *ngFor="let type of transferTypes" (click)="onChanges()">{{type}}</mat-option>
</mat-select>
i am calling "onChanges()" method when i selects an option then i am subscribing this formControl and getting changed value and compare selected value by comparing operation.
same as you can get changed value and then set it to any boolean type variable then you can set any div with *ngIf="" statement in your html template/
if any confusion let me know.
Thanks..
angular7 #reactiveforms #formcontrol
As per your question i'm not completely sure why you want to use the filter since you have not shown the code. However, if you want to just reset your control, change this.filterResult = "" to this.userControl.setValue('') in clearUserValues() method.
Also, since you are subscribing to the valueChanges, it would be better if you do it in ngOnInit().
Check stackblitz here
Your TS code will be like this.
ngOnInit() {
this.onchanges();}
onchanges() {
this.form.get('userControl').valueChanges.subscribe( data => {
// here you can set your boolean value for condition to show cancel button
and push the value/or object which you take from input textbox in an your array for further comparison.
});}
clearUserValues() {
// here you will be set your boolean for hiding cancel button
and then you will be set empty state to your formControl input by doing like this.
this.form.get('userControl').patchValue(//here you will be pass empty string '' or what ever you want set it to.);}

Angular 7 - Copying a List returned from SQL into an Array

I would like to convert results from an SQL method to an array so that I can use the filter option on the array in Angular 7.
I am still getting my feet wet in Angular (7) and I'm trying to set up a nested dropdown/select list where the first select list is for "Departments" and the selected value will return a result set for my "DeptTypes" dropdown/select list.
I am currently returning the data by sending the selected value (id) call my "changeData(id: number)" event in the ...component.ts. It successfully returns data, but there is sometimes a problem with the dropdown not always populating. It appears that it is a performance or speed issue due to always making the call to the SQL backend. So, I would like to return the data as an array so that I can use the .filter command, but there seem to be no option for that. So, what would be the best solution to my lil' problem? I was thinking about pushing the returned list into an [] array variable that would then allow me to use "push" but I cannot figure out the coding to do that. If there is a better way, by all means, enlighten me. Thanks.
// the calls in my component.ts
opportunityList() { // this is the for the first <select ...>
this.dashboardService.getOpportunities()
.subscribe((opportunities: Opportunities) => {
this.opportunities = opportunities;
},
error => {
// this.notificationService.printErrorMessage(error);
});
}
typeList(id: number) { // nested <select ...> that needs to populate
this.dashboardService.getTypesByOpportunityId(id)
.subscribe((types: Types) => {
this.types = types;
},
error => {
// this.notificationService.printErrorMessage(error);
});
}
changedata($event) { // called after selecting a value in the 1st <select ...>
// to remove previous selected items from second dropdown
//this.finaldata.splice(0);
// filter items and pass into finaldata
//this.finaldata = this.type2.filter(x => x.areaId == $event.target.value);
this.typeList($event.target.value);
this.finaldata = this.types;
}
// the HTML
<label for="areaId">Departments</label>
<select id="areaId" (change)="changedata($event)" [(ngModel)]="opportunityList" class="form-control">
<option *ngFor="let opp of opportunities" [value]="opp.areaId">{{ opp.areaName }}</option>
</select><br />
<label for="typeId">Department Area Types</label>
<select id="typeId" class="form-control">
<option *ngFor="let typeobj of finaldata" [value]="typeobj.typeId">{{ typeobj.typeName}}</option>
</select>
my ...service.ts
getTypesByDepartmentId(id: number): Observable<Types> {
const headers = new Headers();
headers.append('content-Type', 'application/json');
const authToken = localStorage.getItem('auth_token');
headers.append('Authorization', `Bearer ${authToken}`);
return this.http.get(this.baseUrl + '/dashboard/GetTypesByDepartmentId/' + id, { headers })
.map(response => response.json())
.catch(this.handleError);
}
the controller action
#region api/dashboard/GetTypesByDepartmentId
[HttpGet("{id}")]
public async Task <IActionResult> GetTypesByDepartmentId([FromRoute]int id)
{
// retrieve all revenue types
var model = await (from t in _appDbContext.Type where t.AreaId == id
select new
{
t.TypeId,
t.TypeName
}).ToArrayAsync();
return Ok(model);
}
#endregion
The code above, thru the service.ts returns the results, it is not always populating the "typeId". It's a "hit or miss." Sometimes the data is there and sometimes it's just blank.
I would like to go with returning all of the department area types and using an array and the "filter" command. for example:
this.finaldata = this.typeList.filter(x => x.areaId == event.target.value);
in the component.ts itself or a more proper way to handle this issue since "filter" does not work on this call.
Looks like I get to answer my own question. I can actually filter my returned observable ... the issue was that I was defining a variable that used an interface, reflecting my model. If I remove that and make it "any", I can then push it to an array:
types: any;
type2: Types[] = [];
public finaldata: any[] = [];
typeList() {
this.dashboardService.getTypes()
.subscribe((types: Types) => {
this.types = types;
this.type2.push({ areaId: this.types.areaId, typeId: this.types.typeId, typeName: this.types.typeName, areaName: this.types.areaName });
},
error => {
// this.notificationService.printErrorMessage(error);
});
}
changedata($event) {
// to remove previous selected items from second dropdown
this.finaldata.splice(0);
// filter items and pass into finaldata
this.finaldata = this.types.filter(x => x.areaId == $event.target.value);
}
So, I just call my typeList() in my "ngInit()" and it is taken care of.

Dart PaperDropdownMenu getting selected item

I'm kind of struggling getting the selected item id or even the text displayed in a dropdown menu list. Here's my code:
HTML:
<paper-dropdown-menu label="Currency" on-core-select="{{selectCurrency}}">
<paper-dropdown class="dropdown" halign='right'>
<core-menu class="menu" selected="{{selectedCurrency}}">
<template repeat="{{c in currencies}}">
<paper-item>{{c}}</paper-item>
</template>
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
Dart:
void selectCurrency(CustomEvent e, var detail, PaperDropdownMenu m) {
var id = m.getAttribute("selected");
//id = mCurrencyDropdown.selected;
JsObject detail = new JsObject.fromBrowserObject(e)['detail'];
if(detail['isSelected']) {
PaperItem selected = detail['item'] as PaperItem;
print( 'source | $selected' );
}
print(detail);
}
I can see several properties from which I might get the information I want but I can't access them as the don't seem to be public:
https://drive.google.com/file/d/0B9-4jVIpB0XuTXJ5eVBMZllyanM/view
Any idea? Thank you!
Thanks to Günter I have found the answer:
I have an observable like this:
#observable int selectedCurrency = 20;
Apparently you can simply add a method to your class matching the members name like this and it's called each time the observable is changed:
selectedCurrencyChanged(var oldValue, var newValue) {
print(newValue);
}
Official Documentation
Where is the field selectedCurrency you have bound to ?
I guess you can drop the on-core-select event handler entirely and instead add a method
selectedCurrencyChanged(newValue) {
// event handler code here
}

Knockout jQueryUI Autocomplete how to use input value if nothing selected from autocomplete list

I am using the custom binding provided in How to create an auto-complete combobox?
I want to allow the user to either select a value from the list of suggestions or enter a value that is not in the list of suggestions. How can I get the value of the input into my observable field?
For example, if the user types 'smi' the autocomplete list will show Smith and other surnames beginning with 'smi', however, if they do not select an option from the list, I just want to set the value of my observable field to be 'smi'. At present, the only way the observable propety is set is when the user selects an item from the list of suggestions.
I have the following code (HTML):
<input type="text" data-bind="
value: surname,
jqAuto: { autoFocus: true },
jqAutoSource: surnames,
jqAutoQuery: surnameAutocomplete,
jqAutoValue: surname"
/>
JavaScript view model (simplified):
var vm = {
surnames: ko.observableArray(),
surname: ko.observable(),
surnameAutocomplete: function (searchTerm, result) {
repository.surnameAutocomplete(searchTerm, result);
};
Solution:
I amended the custom binding handler in two places:
init: function - added the following
// New setting to allow / disallow a user to enter values that are in the autocomplete list.
forceSelection = allBindings.jqAutoForceSelection;
options change function - amended to the following
//on a change, make sure that it is a valid value or clear out the model value
options.change = function (event, ui) {
var currentValue = $(element).val();
// Start: new code, check new setting on whether to force user to make a selection
if (!forceSelection) {
writeValueToModel(currentValue);
return;
}
// End: new code
var matchingItem = ko.utils.arrayFirst(unwrap(source), function (item) {
return unwrap(inputValueProp ? item[inputValueProp] : item) === currentValue;
});
if (!matchingItem) {
writeValueToModel(null);
}
}
I also found that the first item in the autocomplete list was being automatically selected, but then noticed by setting autofocus: false solved my issue, e.g.,
<input type="text" data-bind="
jqAuto: { autoFocus: false }, /* This fixes the auto select issue */
jqAutoSource: surnames,
jqAutoQuery: surnameAutocomplete,
jqAutoValue: surname,
jqAutoForceSelection: false"
/>
If you look closely at the binding handler you're using, you will notice this section:
//on a change, make sure that it is a valid value or clear out the model value
options.change = function(event, ui) {
var currentValue = $(element).val();
var matchingItem = ko.utils.arrayFirst(unwrap(source), function(item) {
return unwrap(item[inputValueProp]) === currentValue;
});
if (!matchingItem) {
writeValueToModel(null);
}
What this section of the binding handler essentially does is check if the text the user entered into the text field matches something in the autocomplete dropdown, and if it doesn't, it clears the model value (which it sounds like what you want to change).
You can either try deleting this section or extend it to suit your purposes.

Change title when opening dialog from knockout binding

I have based my code on this example
http://jsfiddle.net/rniemeyer/WpnTU/
When you select an item I want the title of the dialog to have a observable's value
I managed to to it by creating another custom binding
ko.bindingHandlers.dialogOptions = {
update: function(element, valueAccessor) {
var options = ko.utils.unwrapObservable(valueAccessor());
if (options ) {
$(element).dialog(options);
}
}
}
Added a new observable to viewmodel and set it when the item is selected
this.selectProduct = function(product) {
self.dialogOptions({ title: product.name() });
self.selectedProduct(product);
}
Working example: http://jsfiddle.net/WpnTU/76/
It works but I do not like it, it adds a new observable which is very coupled with the GUI, it would be much nicer if I could use the already exiting selectProduct observable and point out the name property in the GUI something like { title: selectProduct.name }
Here is a sample that moves the .dialog calls into the update function and unwraps the options, so that it will be triggered any time that something changes.
//custom binding to initialize a jQuery UI dialog
ko.bindingHandlers.jqDialog = {
init: function(element) {
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).dialog("destroy");
});
},
update: function(element, valueAccessor) {
var options = ko.toJS(valueAccessor());
if (options) {
$(element).dialog(options);
}
}
};
I added a computed observable to your sample just to handle the selectedProduct being null (could be done in-line).
http://jsfiddle.net/rniemeyer/Gt5Hw/

Resources