Binding complex object to a component - aurelia

Introduction
My goal is to create custom element in aurelia so that I can reuse it across the application.
In this context I've created component called operator-detail (operator-detail.html and operator-detail.js) which will holds information about operator and my plan is to reuse it in several places in application.
In this use case I have electornicRegistrationForm object which holds reference to operatorDetails and legalRepresentative. Both instances are injected into electornicRegistrationForm module and will be used as part of wizard allowing user to create a document which will be printed later on.
This electronicRegistraionForm is injected into operatorStep component.
operator-detail.html component I've included in operatorStep.html and confirm that component has been rendered correctly.
Problem
How to pass (bind) property operator from operatorStep.js to operator-detail.html component so that values from object operator are displayed (binded) in a two way binding manner.
In following example, value from this.operator.firstName 'First name from operator step' don't get displayed in operator-detail.html component.
Source code
electronicRegistrationForm.js:
import { OperatorDetail } from 'common/operator-detail';
import { LegalRepresentative } from 'common/legalRepresentative';
import { inject } from 'aurelia-framework';
#inject(OperatorDetail, LegalRepresentative)
export class ElectronicRegistrationForm {
constructor(operatorDetail, legalRepresentative) {
this.operator = operatorDetail;
this.legalRepresentative = legalRepresentative;
}
}
operator-detail.js
import {inject, NewInstance} from 'aurelia-framework';
import {ValidationRules, ValidationController} from 'aurelia-validation';
#inject(NewInstance.of(ValidationController))
export class OperatorDetail {
constructor(validationController) {
this.validationController = validationController;
this.firstName = '';
}
attached() {
ValidationRules
.ensure('firstName').displayName('Ime').required()
.on(this);
}
}
operator-detail.html
<template bindable="operatorvalue">
<div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="firstName" t='first_name'></label>
<input type="text" class="form-control" id="firstName" value.bind="operatorvalue.firstName ">
</div>
</div>
</div>
</template>
operatorStep.js
import { ElectronicRegistrationForm } from 'model/electronicRegistrationForm';
import { inject, NewInstance } from 'aurelia-framework';
import { RegistrationWizard } from 'registration/registrationWizard';
import { WizardStep } from 'registrationSteps/wizardStep';
import { ValidationController } from 'aurelia-validation';
import {bindable, bindingMode} from 'aurelia-framework';
#inject(ElectronicRegistrationForm, RegistrationWizard, NewInstance.of(ValidationController))
export class OperatorStep extends WizardStep {
#bindable({ defaultBindingMode: bindingMode.twoWay }) operator;
constructor(electronicRegistrationForm, regWiz, validationController) {
super(electronicRegistrationForm, regWiz, validationController);
this.operator = electronicRegistrationForm.operator;
this.operator.firstName='First name from operator step';
this.representative = electronicRegistrationForm.legalRepresentative;
}
}
operatorStep.html
<template>
<require from="common/operator-detail"></require>
<form validation-renderer="bootstrap-form">
<operator-detail operatorvalue.bind="operator"></operator-detail>
</form>
</template>

Declaring a bindable property on a template is for when you have a View without a ViewModel.
The bindable="operatorvalue" in your operator-detail.html doesn't work because you also have a ViewModel defined for this element. If you want to keep it this way then simply remove the bindable="operatorvalue" from the template and instead declare it in your ViewModel like so:
import {inject, NewInstance, bindable} from 'aurelia-framework';
import {ValidationRules, ValidationController} from 'aurelia-validation';
#inject(NewInstance.of(ValidationController))
export class OperatorDetail {
#bindable operatorvalue;
constructor(validationController) {
this.validationController = validationController;
this.firstName = '';
}
attached() {
ValidationRules
.ensure('firstName').displayName('Ime').required()
.on(this);
}
}
operator-detail.html would then become:
<template>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="firstName" t='first_name'></label>
<input type="text" class="form-control" id="firstName" value.bind="operatorvalue.firstName ">
</div>
</div>
</div>
</template>
Alternatively, you could make the bindable property declaration in the template work by simply deleting operator-detail.js and putting the validation stuff elsewhere. Then, rather than injecting OperatorDetail into the registration form you could new up the operator object like so:
import { LegalRepresentative } from 'common/legalRepresentative';
import { inject } from 'aurelia-framework';
#inject(LegalRepresentative)
export class ElectronicRegistrationForm {
constructor(legalRepresentative) {
this.operator = {};
this.legalRepresentative = legalRepresentative;
}
}
Both ways will work.
Working without ViewModels means putting the validation logic in electronicRegistrationForm.js for instance, which also means less code to write.
Working with ViewModels gives you more encapsulation, which is generally preferable if your view-specific logic is more complex than what you've shown here.
EDIT
In case you're using Google Chrome, I highly recommend using the aurelia context extension. You could then inspect the html and see that operator was defined in operator-step, but operatorvalue was undefined in operator-detail. This would tell you that the operatorvalue bindable declaration must be wrong, rather than something else.

Related

Angular undefined value of #input variable

I'm new to Angular and I have some issues , hope you'll help me.
so I'm trying to share a value of a variable from a ProjectComponent to an AcceuilComponent , the value of this variable is displaying correctly into my acceuil.component.html but when I try to use it into my acceuil.component.ts it's undefined !
project.component.html (the parent component)
<app-header-in></app-header-in>
<ng-sidebar-container>
<ng-sidebar [opened]="opened">
<p> Sidebar </p>
<button (click)="Sidebar()">
Close Sidebar
</button>
<ul class="menu">
<li class="hh"
*ngFor="let project of projects"
[class.selected]="project === selectedProject"
(click)="onSelect(project)">
{{project.nomprojet}}</li>
</ul>
</ng-sidebar>
<div ng-sidebar-content >
<br><br><br><br>
<button (click)="Sidebar()">
Open Sidebar
</button>
<app-acceuil [message]="idProject"></app-acceuil>
</div>
</ng-sidebar-container>
project.component.ts
import { Component, OnInit } from '#angular/core';
import { ApiService } from '../api.service';
import {ProjectService} from '../project.service';
import {PartService} from '../part.service';
#Component({
selector: 'app-project',
templateUrl: './project.component.html',
styleUrls: ['./project.component.css']
})
export class ProjectComponent implements OnInit {
opened=true;
projects:any;
idProject;
selectedProject;
constructor(private projectService:ProjectService) { }
ngOnInit(): void {
this.projectService.getProjects().subscribe((result)=>{console.log("result",result)
this.projects=result})
}
Sidebar(){
this.opened=!this.opened;
}
onSelect(pro): void {
this.idProject = pro.id;
}
}
acceuil.component.html (my child component)
<p>{{message}}</p>
<ul >
<li class="hh"
*ngFor="let part of parts">
{{part.nomparti}}
</li>
</ul>
acceuil.component.ts
import { Component, OnInit,Input } from '#angular/core';
import { ApiService } from '../api.service';
import {PartService} from '../part.service';
#Component({
selector: 'app-acceuil',
templateUrl: './acceuil.component.html',
styleUrls: ['./acceuil.component.css']
})
export class AcceuilComponent implements OnInit {
#Input() message;
parts:any;
constructor(private partService:PartService) {
}
ngOnInit(): void{
console.log("id",this.message);
this.partService.getPartsFromIdProject(this.message).subscribe((result)=>{console.log("result",result)
this.parts=result})
}
ngOnChanges() {
if(this.message) {
console.log(this.message)
}
}
}
I'm using the message to call a service and displaying data .
in the acceuil.component.html <p>{{message}}</p> is displaying correctly but console.log("id",this.message); in acceuil.component.ts displays undefined
As message is an input property, you need to get its value in ngOnchanges life cycle.
First time, when it is in ngOnChanges, input value will be undefined. So for the safe side, better to add a check for not undefiled condition like below
ngOnChanges(changes: SimpleChanges) {
if (changes.message) {
// Should be defined now
console.log(this.message);
}
}

bind to css visibility

Using Aurelia's css bind you have to use this syntax
<div css.bind="{visibility: someField ? 'visible':'hidden'}">
is there a more succinct way to do this?
Please note, using show.bind is not what i'm after as this is equivalent to display:none and I actually want visibility:hidden (so the element takes up its space but is not visible)
Something like this would be ideal
<div visibility.bind="someField">
A little simpler syntax would be:
<div css=“visibility: ${someField ? 'visible':'hidden'}”>
To make this more succinct, you can easily create a custom attribute:
import {inject} from 'aurelia-framework';
#inject(Element)
export class VisibilityCustomAttribute {
constructor(element) {
this.element = element;
}
valueChanged(newValue) {
this.element.style.visibility = newValue ? 'visible' : 'hidden';
}
}
And use it like this:
<template>
<require from='./visibility-custom-attribute'></require>
<div visibility.bind="someField">
</template>
See this GistRun for an example.

Aurelia custom element binding

I'm trying to build a custom element in Aurelia. At this point, this is what I have:
item.html
<template>
<span>${someProperty}</span>
</template>
item.ts
import {bindable} from 'aurelia-framework';
class Item {
#bindable someProperty: string;
}
parent.html
<template>
<require from="./item"></require>
<item repeat.for="item of items"></item>
</template>
parent.ts
class Parent {
items: Item[];
loadItems() {
// at this point, I'm sure that items is getting populated.
this.items = dataservice.loadItems();
}
}
I can't seem to find anything in the documentation that covers this scenario. What I'm getting, is that the span is empty. I'm not getting any errors in the console. Am I going about this the right way?
You need to bind to the item's someProperty. The following assumes that items[] is an array of strings.
<div repeat.for="item of items">
<item someProperty.bind="item"></item>
</div>
Sorry about the formatting, I'm on my phone.
You need to use the custom element and the bindable property. You also need to register the class as a custom element. Try this:
item.html
<template>
<span>${someProperty}</span>
</template>
item.js
import {bindable, customElement} from 'aurelia-framework';
#customElement('item')
class Item {
#bindable someProperty: string;
}
parent.html
<template>
<require from="./item"></require>
<item repeat.for="item of items" someProperty.bind="item"></item>
</template>
parent.ts
class Parent {
items: Item[] = [
'trees',
'swans',
'capes',
'a horse',
'triangles',
'witches',
'a different horse'
];
}
For more information, take a look at a few of my blogs on custom elements and custom attributes like this one: http://davismj.me/blog/semantic-custom-element/

Aurelia Custom Elements Inside of Custom Elements & Sharing Variables

How do I access & share variables between custom elements? I have the following files...
tip.html
<template>
<div class="tip-container">
<content select="tip-trigger"></content>
<content select="tip-content"></content>
</div>
</template>
tip.js
export class Tip {}
tip-trigger.html
<template>
<span class="tip-trigger" click.trigger="showTip()">
<content></content>
</span>
</template>
tip-trigger.js
export class TipTrigger {
showTip() {
console.debug(this);
}
}
tip-content.html
<template>
<span class="tip">
<content></content>
<span class="tip__close tip__close--default">×</span>
<span class="tip__color"></span>
</span>
</template>
tip-content.js
export class TipContent {}
In my Tip class I would like to have a variable name visible. When showTip is triggered visible would be set to true, which I would then use to add a class in tip-content.html. How can I share variables between these custom elements to do this?
The idea is to create an element to show tip pop-ups where any type of content can be the trigger and any type of content can be displayed when triggered. Basic example:
<tip>
<tip-trigger><button>?</button></tip-trigger>
<tip-content><div>Here is some helpful info...</div></tip-content>
</tip>
Here is a solution to your problem in Plunker.
Note that the tip-trigger and tip-content elements are just replaceable parts of the template. They don't needed to be components themselves (that confused me a lot in the "original" custom elements article).
app.html:
<template>
<require from="tip"></require>
<tip>
<tip-trigger><button>?</button></tip-trigger>
<tip-content><div>Here is some helpful info...</div></tip-content>
</tip>
</template>
tip.html:
<template>
<div class="tip-container">
<div>
<div click.trigger="showContent()">
<content select="tip-trigger"></content>
</div>
</div>
<div show.bind="contentVisible">
tip content:
<content select="tip-content"></content>
</div>
</div>
</template>
tip.js:
export class Tip {
showContent(){
this.contentVisible = !this.contentVisible;
}
}
Do you just need to turn Tip into a service-like class and import it?
export class Tip {
constructor() {
this.visible = false;
}
show() {
this.visible = true; // Or whatever to show the content
}
hide() {
this.visible = false;
}
}
Then:
import {inject} from 'aurelia-framework';
import {Tip} from './tip';
#inject(Tip)
export class TipTrigger {
constructor(tip) {
this.tip = tip;
}
showTip() {
this.tip.show();
// Or I suppose you could access 'visible' directly
// but I like the implementation details a method call offers.
}
}
*Disclaimer: This is untested.

Using Aurelia validation in model

This is a similar to this but we want to define validation logic in model level but the following doesn't show validation message.
user-model.js (not working)
import {transient, inject} from 'aurelia-framework';
import {ensure} from 'aurelia-validation';
#transient()
export class UserModel {
#ensure(function(it) { it.isNotEmpty().hasLengthBetween(3,10) })
firstName = "";
constructor() {
this.firstName = "";
}
}
user.js (not working)
import {inject} from 'aurelia-framework';
import {Validation} from 'aurelia-validation';
import {UserModel} from 'models/user-model';
#inject(Validation, UserModel)
export class User {
constructor(validation, userModel) {
this.userModel = userModel;
this.validation = validation.on(this);
}
}
user.html (not working)
<form role="form" validate.bind="validation">
<div class="form-group">
<label>First Name</label>
<input type="text" validate="model.firstName" value.bind="model.firstName" class="form-control" >
</div>
</form>
Notice that validate="model.firstName" is used in user.html, which kind of makes the validation work, meaning I see 'has-success' class gets added to form-group div when user input is valid, but it doesn't display message when it's NOT valid input.
But, if I take out the validation logic outside of the user-model.js and put it in user.js like below, it works just fine.
user-model.js (working)
import {transient, inject} from 'aurelia-framework';
#transient()
export class UserModel {
constructor() {
this.firstName = "";
}
}
user.js (working)
import {inject} from 'aurelia-framework';
import {Validation} from 'aurelia-validation';
import {UserModel} from 'models/user-model';
#inject(Validation, UserModel)
export class User {
constructor(validation, userModel) {
this.model = userModel;
this.validation = validation.on(this)
.ensure('model.firstName')
.isNotEmpty()
.hasLengthBetween(3,10);
}
}
user.html (working)
<form role="form" validate.bind="validation">
<div class="form-group">
<label>First Name</label>
<input type="text" value.bind="model.firstName" class="form-control" >
</div>
</form>
We are trying to define validation logic in user model itself so that when we need to use it in other UIs, we have the centralized location to validate it instead of copy & paste the logic everywhere. It's possible that I am doing something wrong, but if anyone knows how to accomplish this, any help is appreciated!
From the aurelia-validation docs,
As your viewmodels become more complex or if you start using binding
converters, the binding path you used to set up the validation rules
might be different than the binding path you used in your view, so
you'll need to give the validate custom attribute some extra clues as
to which elements should be matched against which validation rules.
Consider this more complex example...
Basically, the validation rule was created against the firstName property in your UserModel, but the binding for the input element has a different binding path: value.bind="userModel.firstName". Because of this, you need to add a validate="firstName" attribute onto the input element to help aurelia-validation know which HTML element to match on for the validation messages.
Here's how you can do it (with Plunkr)
user-model.js
import {transient} from 'aurelia-framework';
import {ensure} from 'aurelia-validation';
#transient()
export class UserModel{
#ensure(function(it) { it.isNotEmpty().hasLengthBetween(3,10) })
firstName = "";
constructor() {
this.firstName = "";
}
}
user.js
import {inject} from 'aurelia-framework';
import {Validation} from 'aurelia-validation';
import {UserModel} from 'user-model';
#inject(Validation, UserModel)
export class User {
constructor(validation, userModel) {
this.userModel = userModel;
this.validation = validation.on(this.userModel);
}
}
user.html
<template>
<form role="form" validate.bind="validation">
<div class="form-group">
<label>First Name</label>
<input type="text" value.bind="userModel.firstName" validate="firstName" class="form-control">
</div>
</form>
</template>