Aurelia-dialog using attach-focus on custom element - javascript

I'm trying to pass attach-focus="true" to one of the inner elements of a custom element so that the correct element will receive the focus when aurelia-dialog opens.
Custom element: enum-list.html
<template>
<label class="control-label">${label} DEBUG: ${attach-focus}</label>
<select class="form-control" value.bind="value" attach-focus.bind="attach-focus">
<option if.bind="data" repeat.for="code of data | keys" value="${code}">${data[code]}</option>
</select>
</template>
Custom element: enum-list.js
import { bindable, bindingMode } from 'aurelia-framework';
export class EnumListCustomElement {
#bindable label;
#bindable data;
#bindable attach-focus; // <-- Maybe the source of the error?
#bindable({ defaultBindingMode: bindingMode.twoWay }) value;
}
Dialog template: edit-locale.html:
<template>
<ai-dialog>
<ai-dialog-header class="modal-header modal-header-success">
<h4 class="modal-title">Edit Locale</h4>
</ai-dialog-header>
<ai-dialog-body>
<form>
<enum-list attach-focus="true" label="Language" data.bind="core.enums.SystemLanguage" value.bind="sch_lang"></enum-list>
<enum-list label="Currency" data.bind="core.enums.CurrencyCode" value.bind="sch_currency"></enum-list>
</form>
</ai-dialog-body>
<ai-dialog-footer>
<button type="button" click.trigger="dialogController.cancel()">Cancel</button>
<button type="button" click.delegate="dialogController.ok()">Save</button>
</ai-dialog-footer>
</ai-dialog>
</template>
Instantiation (from my VM js):
this.dialogService.open({ viewModel: EditLocale, model: this.record, lock: true }).then(response => {
The modal dialog loads fine if I remove the dashes from attach-focus in edit-locale.js and inside the custom element. But with the dash, I'm getting an error: Uncaught SyntaxError: Unexpected token import. I think the dash is interfering but I don't know how to fix it.
My preference is to fix it so that the instantiation of the custom control has the standard parameter attach-focus="true" (with the dash) so that it's consistent with normal elements like INPUT and SELECT.

You are right about the source of the error, you can't have a property-name containing a dash. Because it reads as property - name.
There is a convention in aurelia (link to docs, search for dash-case) to map attributes and elements name from dash notation to camelCase notation, so if in your model you will name your bindable property as #bindable attachFocus - you will be able to use it in you views as attach-focus.bind="true".
Also make sure that you <require> your custom elements/attributes in your views or make them globally available when configuring aurelia.

Related

Angular4 ngModel changes type of data from 'number' to 'string'

I have an Angular4 app that captures data from a form and stores in DynamoDB. It uses ngModel to support two-way data binding and on the display all looks good. Issue comes into play because of an input field typed as 'text' bound to Typescript 'number' field. Seems to be changing the type of the object value to 'string'. I would simply change the HTML Input type to 'number' except for the unnecessary and undesirable increment/decrement decorators on the form field (and hiding them seems to have limited support). So I was curious if there is another way to keep the data structure typed as desired... if this is a bug in ngModel... or if the input type just simply needs to be 'number'.
The structure in my sample.component.ts file looks like this:
export class Course {
Id: number;
Name: string;
}
...
courseInstance: Course;
saveCourse() {
JSON.stringify(this.courseInstance);
}
My sample.component.html file looks like this:
<div>
<label for="courseid">Course ID: </label>
<input type="text" class="form-control"[(ngModel)]="courseInstance.Id" name="courseid">
</div>
<div>
<label for="courseName">Course Name: </label>
<input type="text" class="form-control"[(ngModel)]="courseInstance.Name" name="courseName">
</div>
<div>
<button type="button" class="btn btn-info btn-lg" (click)="saveCourse()">Save</button>
</div>
Output of JSON.stringify(this.courseInstance) results in something like
{"Id":"100","Name":"Test course"}
Notice the value 100 represented as a string.
If I don't use the form, but simply create an instance such as
courseInstance: Course = {
Id: 100,
Name: 'Test course'
};
Then when outputing result of JSON.stringify(courseInstance); I get
{"Id":100,"Name":"Test course"}
If I try to use PutItem to store object in DynamoDB, the Id value fails type check when data comes from HTML form.
I would have thought that the typing in Typescript would have held precedence over the HTML 'text' input type.
Change the type text to the number to make the type number and don't let to write letters.
<input type="number" class="form-control"[(ngModel)]="courseInstance.Id" name="courseid">
The [(ngModel)] syntax can only set a data-bound property. If you need to do something more or something different, you can write the expanded form.
<div>
<label for="courseid">Course ID: </label>
<input type="text" #ref class="form-control" [ngModel]="courseInstance.Id" (ngModelChange)="onDataChange(ref.value)"name="courseid">
</div>
The ngModel data property sets the element's value property and the ngModelChange event property listens for changes to the element's value.use parseInt to get the desired result.
ngModelChange will be fired on every keystroke you need to debounce value else event will be emitted for every keystroke and To debounce values you can use a Subject with debounceTime() operator.A subject is both an observable and an observer. This means you can treat it as an observable and pass values to it as well. Apart from this use Template reference variable
In your component
import { Subject } from 'rxjs/Subject';
import {debounceTime } from 'rxjs/operators
debouncer= new Subject();
constructor() {
this.debouncer
.debounceTime(1000)
.subscribe((val) =>{
console.log(val);
this.courseInstance.id=ParseInt(val,10);
});
}
onDataChange(value) {
this.debouncer.next(value);
}
Angular currently doesn't support type checking in the property binding and event binding. As you can see here, there is an open issue in their github:
https://github.com/angular/angular/issues/16952
What I suggest you to do is to change your input to type number and add this css in your component:
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
EDIT: adding an application in the Stackblitz:
https://stackblitz.com/edit/angular4-ngmodel-changes-type-of-data-from-number-to-string

Binding a parameter to an ember component and handle it from the controller

I want to create the following component in ember (templates / components / echo-hlabel-tf.hbs)
<div id="echo-hlabel-tf">
<span id="tf-label">{{label}}</span>
<span id="tf-value">
<input type="text" value={{textValue}}
{{action "validateRegExp" textValue regExp post on="focusOut"}}
{{action "showText" textValue post on="mouseUp"}}
/>
</span>
</div>
Just a textfield and a label placed in the same row . I have created the functions in the controller of the component (components / echo-hlabel-tf.js):
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
validateRegExp(text,regExpStr){
let regExpObj = new RegExp(regExpStr)
if(regExpObj.test(text)){
console.log('entry matches')
}else{
console.log('entry is wrong');
}
},
showText(text){
console.log(text);
}
}
});
I want to use this component from another template (template / programmers.hbs) :
<ul>
{{people-list title="List of programmers" people=model}}
</ul>
<div>
{{echo-hlabel-tf
label="Textfield horizontal"
textValue="..."
regExp="^([a-z0-9]{5})$"
}}
</div>
The thing is that , even the actions are fired as the events trigger, the variable that represents the value of the input (textValue) always stands the same ("...") . seems that the binding between the outer template, and the components is created at the beginning, but if I put more letters in the textfield , the value of textValue remains the same and doesn't change . I would like to, as I'm adding letters to the textBox, print them with the console with the method showText, but I always print "..."
You are using pure html5 input that does not do two way binding by itself. That is even if you type something, it is not reflected to textValue attribute. You can use ember's builtin input component as in this twiddle. I believe this will solve your problem.

Angular 2 doesn't update input value modified by third-party libraries

I have a simple form with its input field associated to a directive:
<form id="scrollable-dropdown-menu">
<input class="input-field" name="value" [someDirective] type="text" [(ngModel)]="value" #query="ngModel" />
<button (click)="onPost(query.value)" type="submit">Search</button>
</form>
The directive changes the input field through the use of a third party library. In my particular case, this is an autocomplete/typeahead Jquery plugin. This plugin provides options to the user and after selecting an option, it changes the value of the input field.
However, Angular doesn't update its property query.value and therefore, passes the old value to the onPost method.
The directive looks something like this:
#Directive({
selector: '[someDirective]',
})
export class MyDirective {
constructor(private elRef: ElementRef, private renderer: Renderer) {
// this changes the value of the field if the user selects an option
$('.input-field').plugin();
}
}
I was suggested to use UpdateValue, but I can't see how to use it inside a directive. That led me to look at #ViewChild, but it doesn't seem to work in directives, although I could be mistaken.
I also tried to force an update by injecting ChangeDetectorRef, but I didn't see any difference. This is what I did:
my.directive.ts
import {ChangeDetectorRef} from '#angular/core';
#Directive({
selector: '[someDirective]',
})
export class MyDirective {
constructor(private elRef: ElementRef, private renderer: Renderer, private detectorRef: ChangeDetectorRef) {
$('.input-field').plugin();
$('.input-field').my-plugin(':selected', ()=>{
// do something...
this.detectorRef.detectChanges();
})
}
}
AfterSelection is triggered when the user selects an option from the autocomplete plugin. For the plugin, it looks a bit different because it binds to some event, but I think this illustrates the idea.
I would appreciate any suggestions.
UPDATE:
This is a plnkr to show the main issue using the typeahead.js library. If you write a letter in the input field, it will show you some options. Select one of them and the value of the input field will change accordingly. However, when you submit the form (clicking on the input field or pressing enter), an alert will show that the value that was passed to the onPost function was the old value of the field and not the autocompleted value you selected.
Provisional solution: Demo. Passing the form to the custom directive and using updateValue to manually change the value:
my.component.ts
<form *ngIf="active" #frm="ngForm">
<input name="value" [myDirective]="frm" type="text" [(ngModel)]="value" #query="ngModel" />
<button (click)="onPost(query.value)" type="submit">Search</button>
</form>
my.directive.ts
#Directive({
selector: '[myDirective]',
})
export class MyDirective {
#Input('myDirectivev') query;
...
$(this.elRef.nativeElement).my-plugin(':selected', (ev, suggestion) => {
this.query.controls['value'].updateValue(suggestion);
});
This works, but let me know if there is a standard approach to solve this kind of issue.
I was told about another solution on Gitter. Unfortunately, I can't remember who told me about it and Gitter's search functionality doesn't help me. Since this person hasn't posted his solution, I will share it for the benefit of people who might be interested.
The idea is basically to pass the whole field as an argument to the onPost(input) method and access the value property of the input field (input.value).
#Component({
selector: 'my-app',
template: `
<form id="scrollable-dropdown-menu" class="search-form">
<input name="value" class="form-control typeahead" typeahead type="text" data-provide="typeahead" [(ngModel)]="thing" #query />
<button (click)="onPost(query)" class="btn btn-default search-button" type="submit">Search</button>
</form>
`,
directives: [TypeaheadDirective],
providers: []
})
export class AppComponent {
onPost(input) {
alert(`Your current value is ${input.value}`);
}
}
Notice the [(ngModel)]="thing" and #query in the input field. I'm not entirely sure why this updates its value correctly and the native attempt doesn't, but this is far simpler than other alternatives. Here you can find a demo.
Instead of
this.detectorRef.detectChanges();
use
$('.input-field').trigger('input');
ControlValueAccessor used by ngModel listens to input events for normal input fields.
Günter Zöchbauer is right. You should dispatch 'input' event to update angular's model. But JQuery trigger didn't help. Only native event fixed this
#Directive({
selector: '[myDirective]',
})
export class MyDirective {
...
$(this.elRef.nativeElement).my-plugin(':selected', (ev, suggestion) => {
const inputEvent: any = document.createEvent('CustomEvent');
inputEvent.initEvent('input', true, true);
el.dispatchEvent(inputEvent);
});
}
I think Zone is not aware of third-party variable changes, hence third-party variable changes are not reflected in Angular
You could try to place your jQuery code inside an angular zone, by implementing something like this:
import Zone: import {NgZone} from 'angular2/core' or import {NgZone} from '#angular/core' , depending on the Angular 2 version you are using;
add zone: NgZone to the constructor arguments of MyDirective;
replace $('.input-field').plugin(); by
zone.run( () => {$('.input-field').plugin(); });
Here is a post about Change detection and Zone
Hope this will help you...

Submit a form made of multiple components

I've started Angular2 lately and I am facing a problem. I want to create a form to generate some multiple choices questions and this is my problem :
I have a FormComponent which display the layout of my form.
The AnswerComponent can be added to the FormComponent to provide multiple choices about the question.
I have been using the DynamicComponentLoader to programatically add thoses AnswerComponent inside of my FormComponent.
The thing is the submit button must belong to the FormComponent and I do not know how to alert my AnswerComponent to send their data to the FormComponent so it cans gather all the data and create my question.
If anyone has an idea that would be great !
Thank you !
Let's take a sample. I have a form that manages company details:
<form [ngFormModel]="companyForm">
<field label="Name" [state]="companyForm.controls.name">
<input [ngFormControl]="companyForm.controls.name" [(ngModel)]="company.name"/> {{name.valid}}
</field>
<field label="Tags">
<tags [(labels)]="company.labels"></tags>
</field>
<button type="submit" [disabled]="!companyForm.valid">Submit</button>
</form>
As you can see, I use two sub components:
The field one that aims to build the layout for a field block using Bootstrap3. It accepts a variable area to provide the form element (input, select, textarea). This component also leverages the associated control to display validation errors if any.
The tags one that manages the tags attribute that is a list of string. It allows to display, add and remove tags.
You can see that every form element leverages two way binding. This means that each form element is associated to a property of an object of the component. Here it's the company one that is a property of the component.
This means that when you want to submit the form, you can use this company object to build the payload of the corresponding HTTP request for example.
Let's deal a bit more with the associated with the company object. For inputs, it's obvious using the ngModel directive with this syntax: [(ngModel)]. With the tags sub component, it's perhaps not so obvious.
In fact you need define inputs and outputs to manage the labels with two ways bindings:
#Input labels:string[]
#Output labelsChanged: EventEmitter
When labels are updated, you need to call the emit method of labelsChanged.
Here is the complete code for the TagsComponent component:
#Component({
selector: 'tags',
template: `
<div *ngIf="labels">
<span *ngFor="#label of labels" style="font-size:14px"
class="label label-default" (click)="removeLabel(label)">
{{label}} <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</span>
<span> | </span>
<span style="display:inline-block;">
<input [(ngModel)]="labelToAdd" style="width: 50px; font-size: 14px;" class="custom"/>
<em class="glyphicon glyphicon-ok" aria-hidden="true" (click)="addLabel(labelToAdd)"></em>
</span>
</div>
`
})
export class TagsComponent implements OnInit {
#Input()
labels:string[];
#Output()
labelsChange: EventEmitter;
constructor(private elementRef:ElementRef) {
this.labelsChange = new EventEmitter();
}
removeLabel(label:string) {
var index = this.labels.indexOf(label, 0);
if (index != undefined) {
this.labels.splice(index, 1);
this.labelsChange.emit(this.labels);
}
}
addLabel(label:string) {
this.labels.push(this.labelToAdd);
this.labelsChange.emit(this.labels);
this.labelToAdd = '';
}
}
Hope it helps you,
Thierry

Aurelia Custom Elements data binding

I am using a custom-element with my Aurelia app. When I am binding data from my view-model with the custom-element, it is working fine. Even if I make some changes in the data in the custom-element control, the change is also reflected to the data in my view model, thanks to the two-way data binding.
However, if I make some changes in the data from the view model (using javascript), the data is not updated in the custom-element. I have replicated this problem for a simpler setting.
Simple View Model
export class Playground {
public testObj: any;
counter = 0;
constructor() {
this.testObj = {
prop1: "This is prop1 value"
, collection2: [{ id: "item21" }]
}
}
updateProp1() {
alert("before update: "+this.testObj.prop1);
this.testObj.prop1 = "Sayan " + this.counter++;
alert(this.testObj.prop1);
}
verifyChange() {
alert(this.testObj.prop1);
}
}
Simple View
<template>
<h1>
Playground
</h1>
<div >
<div repeat.for="item of testObj.collection2">
<div class="row">
<div class="col-sm-4">
<input type="text" class="form-control" placeholder="prop1"
value.bind="$parent.testObj['prop1']">
</div>
</div>
</div>
<button click.delegate="updateProp1()" class="btn btn-primary"> Update Prop1 </button>
<button click.delegate="verifyChange()" class="btn btn-primary"> Verify Change </button>
</div>
</template>
Now whenever I click Verify Change after changing the value in textbox, the changed value comes in the alert. But, if I click the Update Prop1 button, the prop1 value gets updated, but the change doesn't reflect in the view.
I am not sure exactly what I am missing.
Note: Instead of using $parent.testObj['prop1'], if $parent.testObj.prop1 is used, the databinding works as it should. However, my actual custom-element is of generic kind and the property name is not known before hand, hence it seems that I need to keep using $parent.testObj['prop1'] notation instead of dot notation: $parent.testObj.prop1.
At pointer/suggestion/feedback is appreciated.
Update: If anyone can point out the the difference between the dot notation and indexer notation w.r.t. aurelia data-binding (I have checked this already), that will be of great help.
This was an issue in earlier builds of the aurelia/binding module. Here's the related issue(s):
https://github.com/aurelia/binding/issues/75
https://github.com/aurelia/binding/pull/76
I tried your view/view-model in the latest version of aurelia and everything worked. Here's a screencast of what I saw: http://screencast.com/t/KqcuqXWjkU2
Make sure you have the latest version of the aurelia components installed- update steps here: http://blog.durandal.io/2015/05/01/aurelia-may-status-and-releases/

Categories

Resources