Public access to objects propery on aurelia validation - aurelia

I would do like to have public access the private property where objects are stored on the current ValidationController as when we issue addObject().
From this blog:
http://www.jujens.eu/posts/en/2017/Jan/24/aurelia-validation/
I am trying to validate not only a WELL KNOWN object but ALL objects registered in the ValidationController
Let me explain a little bit, I had an interface called
export interface IRuleValidator {
addRules(model:any): void;
}
and classes that implement such interface
export class AddressRuleValidator implements IRuleValidator {
addRules(address: Address) {
ValidationRules
.ensure((a: Address) => a.address)
.required()
.on(address);
}
}
export class EmailRuleValidator implements IRuleValidator {
addRules(email: Email) {
ValidationRules
.ensure((e: Email) => e.email)
.required()
.on(email);
}
}
export class PhoneRuleValidator implements IRuleValidator {
addRules(phone: Phone) {
ValidationRules
.ensure((p: Phone) => p.phone)
.required()
.on(phone);
}
}
#inject(AddressRuleValidator, PhoneRuleValidator, EmailRuleValidator)
export class PlayerRuleValidator implements IRuleValidator {
private readonly addressRuleValidator: IRuleValidator;
private readonly phoneRuleValidator: IRuleValidator;
private readonly emailRuleValidator: IRuleValidator;
constructor(addressRuleValidator: IRuleValidator, phoneRuleValidator: IRuleValidator, emailRuleValidator: IRuleValidator) {
this.addressRuleValidator = addressRuleValidator;
this.phoneRuleValidator = phoneRuleValidator;
this.emailRuleValidator = emailRuleValidator;
}
addRules(player: Player) {
ValidationRules
.ensure((p: Player) => p.firstName)
.required()
.on(player);
if (player.addresses && player.addresses.length > 0)
player.addresses.map(address => this.addressRuleValidator.addRules(address));
if (player.phones && player.phones.length > 0)
player.phones.map(phone => this.phoneRuleValidator.addRules(phone));
if (player.emails && player.emails.length > 0)
player.emails.map(email => this.emailRuleValidator.addRules(email));
}
}
#inject(PlayerRuleValidator)
export class ScoreRuleValidator implements IRuleValidator {
private readonly playerRuleValidator: IRuleValidator;
constructor(playerRuleValidator: IRuleValidator) {
this.playerRuleValidator = playerRuleValidator;
}
addRules(score: Score) {
ValidationRules
.ensure((s: Score) => s.factor)
.required()
.on(score);
if (score.player) { this.playerRuleValidator.addRules(score.player); }
}
}
Each class knows how to validate the object passed to it and delegates to other classes the validation of "child" objects.
i.e.: score has a player and a player has emails.
Score knows how to validate to itself and delegates to player his own validation and player do the same with emails, phones, buildin all "the chain" down.
Thus the entire process of building a "validation chain" starts calling addRules() on the root object of the graph.
Suppose that we have an score object: We resolve from "the container" a ruleValidator for Score and starts buildind the validation chain as follows.
#inject(ScoreRuleValidator)
export class ScoreList extends BaseViewModel {
public isOk: boolean;
public score: Score
................ code removed for brevity (validation controller code )
#inject(ScoreRuleValidator)
constructor(ruleValidator: IRuleValidator) {
................ code removed for brevity (validation score object creation)
ruleValidator.addRules(this.score) //this call will start all the validation chain registration
this.validationController.validateTrigger = validateTrigger.changeOrBlur;
this.validationController.subscribe(event => this.validateAll())
}
}
private validateAll() {
this.validator
.validateObject(this.model)
.then(results => this.isOk = results.every(result => result.valid));
//HERE GOES THE PROBLEM SINCE ONLY SCORE is known, and what about score.player, and score.player.addresss[], score.player.phones[], score.player.emails[] and so on in the graph
//I WILL NEED to traverse all the chain and since ValidationController has track of those object will be greet to have access to them
}
HERE GOES THE PROBLEM SINCE ONLY SCORE is known, and what about score.player, and score.player.addresss[], score.player.phones[], score.player.emails[] and so on in the graph?.
I WILL NEED to traverse all the chain and since ValidationController has track of those object will be great to have access to it.
Meanwile an option is refactor the interface a rewrite the validator classes as follows:
export interface IRuleValidator {
addRules(model:any, models:any[]): void;
}
and pass an empty array from the root of the chain collecting all those objects.. like so..
export class AddressRuleValidator implements IRuleValidator {
addRules(address: Address, models: any[]) {
ValidationRules
.ensure((a: Address) => a.type)
.required()
.on(address);
models.push(address);
}
and kick the process.. with an empty array []
const objects: any[] = [];
ruleValidator.addRules(this.score, [])
But since we alreay have this property private on the ValidationController, please make it public.. (I will take care of not touching it, just read it)
BR
(then... the final method for validateAll should be like this)
private async validateAll() {
for (let model of this.models) {
let results = await this.validator.validateObject(model);
if (results.some(result => !result.valid)) {
this.isOk = false;
return;
}
}
this.isOk = true;
}

A deep look to the callback is the answer.
validationController.subscribe(event => this.validateAll())
the event object passed to the callback is an array of ValidateResult[]
the ValidateResult type implements the following interface.
export declare class ValidateResult {
rule: any;
object: any;
propertyName: string | null;
valid: boolean;
message: string | null;
private static nextId;
/**
* A number that uniquely identifies the result instance.
*/
id: number;
/**
* #param rule The rule associated with the result. Validator implementation specific.
* #param object The object that was validated.
* #param propertyName The name of the property that was validated.
* #param error The error, if the result is a validation error.
*/
constructor(rule: any, object: any, propertyName: string | null, valid: boolean, message?: string | null);
toString(): string | null;
}
so the object/s validated is already there in the event object
we could simplyfy the code as follow to update a field to signal if the htlm for is ready.
this.validationController.subscribe(validateEvent => this.isFormValid = validateEvent.results.every(result => result.valid));

Related

mobx challenge: getters and setters into an observable array

I'm trying to write getters and setters into an observable array and it isn't working. The code below gives me the following error: Error: [MobX] No annotations were passed to makeObservable, but no decorator members have been found either
I've tried different combinations of decorators, but nothing seems to work. The behavior I want is whenever AppModel.text is updated, any UI rending the getter for text should update. Also whenever gonext() is called on the object, then any UI rending from AppModel.text should update and render data from the new 0 item on the array.
class DataThing
{
#observable text?: string = "foo";
}
class AppModel
{
get text() { return this.items[0].text}
set text(value: string | undefined) { this.items[0].text = value;}
items: DataThing[] = observable( new Array<DataThing>());
constructor() {
makeObservable(this);
this.items.push(new DataThing());
}
gonext() { this.items.unshift(new DataThing()); }
}
EDIT:
I ended up doing the following, but would still like to understand how to index into an array in an observable way.
class DataThing
{
#observable text?: string = "zorp";
constructor(){makeObservable(this);}
}
class AppModel
{
#observable _current?:DataThing;
get current() {return this._current;}
items: DataThing[] = observable( new Array<DataThing>());
constructor() {
makeObservable(this);
this.gonext();
}
gonext() {
this.items.unshift(new DataThing());
this._current = this.items[0];
}
}

AutoMapper - Map destination object's property that does not exist in source object

I'm casting class Process to ProcessDTO. The ProcessDTO object have a property named ProcessSteps that does not exist in Process. I want the ProcessSteps-property to be casted to ProcessStepsDto. I'm using a global configuration for AutoMapper.
I have tried using
CreateMap<Process, ProcessDto>()
.ForMember(dest=>dest.Steps, opt => opt.MapFrom(s => Mapper.Map<ProcessStepDto>(s)));
But this is wrong..
public class Process
{
}
public class ProcessDto
{
//This property does not exists in source object and get's created on get. I want this to be cast to "ProcessStepDto[]"
public ProcessStep[] Steps
{
get
{
ProcessStepRepository repository = new ProcessStepRepository();
return repository.Select(x => x.ProcessId == this.Id && x.Active).OrderBy(x=>x.Position).ToArray();
}
}
}
public class ProcessStep
{
...
}
public class ProcessStepDto
{
...
}
UPDATE
After i use AutoMapper to mapp my object Process to ProcessDto i also want the property of Stepsto be mapped to ProcessStepsDto. Currently it stays as ProcessStep.
If you want to return ProcessDto with ProcessStepDto[], the ProcessDto should define the property with type ProcessStepDto[] instead of ProcessStep[].
public class ProcessDto
{
public ProcessStepDto[] Steps
{
get
{
ProcessStepRepository repository = new ProcessStepRepository();
return repository.Select(x => x.ProcessId == this.Id && x.Active).Select(s => new ProcessStepDto { PropertyInProcessStepDto = s.PropertyInProcessStep }).OrderBy(x=>x.Position).ToArray();
}
}
}

Aurelia-Validation - Clarity on object validation w/addObject()

I'm attempting to manually validate an object which is updated via a customEvent.
//home.html
<type-ahead change.delegate="updateValue($event.detail, true/false)"></type-ahead>
I have two of the above elements, which I want to assign to the properties of origin and destination based on a boolean value.
I also want to validate both of these properties against the same custom rule, which I've defined in setupValidation on my viewmodel below.
Both of these are objects, that need some complex validation (I've simplified it for demonstration purposes), so I've used .ensureObject() and manually add both of these objects to the validation controller.
I would expect that I would only have to add the objects once (during my initial setupValidation(), but I've found that I have to remove, and then re-add the object to the validation controller whenever it changes.
If you look at updateValue(...), you'll see that I'm expecting this.destination to validate to the updated object, but I'm seeing my results still be null on validation. However, this.origin does update and the validation succeeds (as I'm manually updating the controller).
I would expect not to have to manually update the controller. Is this expected?
//home.ts
#autoinject
export class Home {
origin = null;
destination = null;
private controller: ValidationController;
public canSubmit: boolean = false;
public error: string;
private rules;
constructor(controllerFactory: ValidationControllerFactory) {
this.controller = controllerFactory.createForCurrentScope();
this.controller.validateTrigger = validateTrigger.manual;
}
bind() {
this.setupValidation();
}
private validate() {
this.controller.validate()
.then(results => {
this.canSubmit = results.valid;
});
}
private setupValidation() {
ValidationRules.customRule(
'rule1',
(value) => {
if(value.property && value.property.length === 3)
return true;
else
return false;
},
`\${$displayName} must be 3 characters long`
);
this.rules = ValidationRules
.ensureObject()
.required()
.satisfiesRule('rule1')
.rules;
this.controller.addObject(this.origin, this.rules);
this.controller.addObject(this.destination, this.rules);
}
updateValue(newValue, isOrigin) {
if(isOrigin) {
this.controller.removeObject(this.origin);
this.origin = newValue;
this.controller.addObject(this.origin, this.rules);
}
else
this.destination = newValue;
this.validate();
}
}
Thank you, let me know if there are any additional details needed.

Create different objects based on multiple parameters

I have a REST API. I need to create presentation (DTO) object, but the construction of this object depends on request - it differs in 15%.
I wonder what pattern should I use.
My case:
//presentation-DTO
class Item {
private $name;
private $price;
private $tags;
private $liked; //is Liked by logged user
...
public function __construct(Item $item, bool $liked, ...)
{
$this->name = $item->getName();
$this->price = $item->getPrice();
$this->tags = $item->getTags();
$this->liked = $liked;
...
}
}
When user is not logged in - I don't need $liked
When showing list of items - I don't need $tags
And there are more attributes that works as above.
My first idea was to use Builder principle.
$itemBuilder = new ItemBuilder();
$itemBuilder->setItem($item);
...
if($user) {
$itemBuilder->setUserLiked($userLiked);
...
}
return $itemBuilder->build();
It solves my problem with too many parameters in constructor.
But still, I also don't need all parameters to be constructed - eg. I don't need tags (on lists). As I use lazy load, I don't want my dto constructor to call them.
So I thought, maybe Factory.. but then my problem with too many (and optional) parameters is returning.
How will you solve this?
Sorry I don't have required points to make a comment hence an answer.
What are you trying to do with the Item class. Your class is Item and first parameter is also of type Item. I cannot visualizes how its going to work.
I will prefer to keep business login to set proper properties in a separate class:
/**
* A class for business logic to set the proper properties
*/
class ItemProperties {
private $item;
public $isLogin = false;
public $showList = false;
.....
public function __construct(Item &$item) {
// set all properties;
}
public function getProperties() {
$retVal = [];
if($this->isLogin == true) {
$retVal['liked'] = true;
}
if($this->showList == true) {
$retVal['tags'] = $this->item->getTags();
}
if(....) {
$retVal['...'] = $this->item->.....();
}
return $retVal;
}
}
/**
* DTO
*/
class Item {
public function __construct(ItemProperties $itemProps) {
$this->setItemProps($itemProps);
}
// If you prefer lazy loading here...maybe make it public
// and remove call from constructor.
private function setItemProps(&$itemProps) {
$properties = $itemProps->getProperties();
foreach($properties AS $propName => $propValue) {
$this->$propName = $propValue;
}
}
}
// Usage:
$itemProps = new ItemProperties($Item);
// set other properties if you need to...
$itemProps->isLogin = false;
$item = new Item($itemProps);

can not read get property of undefined angular 2 error

hi I am trying to get city name from google api but getting that error below is my code
appcomponent class
import {Component, OnInit} from 'angular2/core';
import {marketComponent} from './market.component';
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {introComponent} from './intro.component';
import {geoService} from './service.geo';
import {JSONP_PROVIDERS} from 'angular2/http';
declare var google: any;
#Component({
selector: 'my-app',
templateUrl: 'app/app.component.html',
directives: [ROUTER_DIRECTIVES],
providers: [JSONP_PROVIDERS, geoService]
})
#RouteConfig([
{ path: '/intro', name: 'Intro', component: introComponent, useAsDefault: true },
{ path: '/market', name: 'Market', component: marketComponent },
])
export class AppComponent {
constructor(private _http: geoService) { }
public maps;
public cat_error: Boolean = false;
public xml_Latitude :string;
public xml_Lang: string;
ngOnInit() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPosition);
} else {
alert("Geolocation is not supported by this browser.");
}
var input: any = document.getElementById('google_places_ac');
var autocomplete = new google.maps.places.Autocomplete(input, {});
google.maps.event.addListener(autocomplete, 'place_changed', function () {
var place = autocomplete.getPlace();
console.log(place)
});
}
showPosition(position) {
this.xml_Latitude = position.coords.latitude;
this.xml_Lang = position.coords.longitude;
this._http.getPlaces(this.xml_Latitude, this.xml_Lang).subscribe(
data => { this.maps = data },
err => { this.cat_error = true }
);
var result = this.maps.results;
var city = result[0].address_components[4].long_name + "," + result[0].address_components[6].long_name;
alert(city);
}
}
and geoservice file
import {Injectable} from 'angular2/core';
import { Response, Jsonp} from 'angular2/http';
import 'rxjs/add/operator/map';
#Injectable()
export class geoService {
constructor(private http: Jsonp) { }
public xml_Latitude: string;
public xml_Lang: string;
public getPlaces(xml_Latitude, xml_Lang) {
return this.http.get(`http://maps.googleapis.com/maps/api/geocode/json?latlng=
'${this.xml_Latitude}','${this.xml_Lang}'&sensor=true`)
.map((res: Response) => res.json())
.catch(this.handleError);
}
private handleError(error: Response) {
console.error(error);
return error.json().error || 'Server error';
}
}
error also says getplaces is not a function, I think I am missing something but don't know what....
In addition to the callback ordering problem identified by Thierry, you have a lost this context on this line:
navigator.geolocation.getCurrentPosition(this.showPosition);
The Problem
You have the classic JavaScript problem known as the incorrect this context.
The this keyword in JavaScript behaves differently than in does in other languages like C# and Java.
How this works
The this keyword, in a function, is determined as follows:
* If the function was created through a call to .bind, the this value is the argument provided to bind
* If the function was invoked through a method call, e.g. expr.func(args), then this is expr
* Otherwise
* If the code is in strict mode, this is undefined
* Otherwise, this is window (in a browser)
Let's look at how this works in practice:
class Foo {
value = 10;
doSomething() {
// Prints 'undefined', not '10'
console.log(this.value);
}
}
let f = new Foo();
window.setTimeout(f.doSomething, 100);
This code will print undefined (or, in strict mode, throw an exception).
This is because we ended up in the last branch of the decision tree above.
The doSomething function was invoked, the function wasn't a result of a bind call, and it wasn't invoked in a method syntax position.
We can't see the code for setTimeout to see what its invocation looks like, but we don't need to.
Something to realize is that all doSomething methods point to the same function object.
In other words:
let f1 = new Foo();
let f2 = new Foo();
// 'true'
console.log(f1.doSomething === f2.doSomething);
We know that setTimeout can only see the function we passed it, so when it invokes that function,
there's no way for it to know which this to provide.
The this context has been lost due to our referencing the method without invoking it.
The Red Flag
Once you know about this problems, they're easy to spot:
class Foo {
value = 10;
method1() {
doSomething(this.method2); // DANGER, method reference without invocation
}
method2() {
console.log(this.value);
}
}
The Solution
You have a few options here, each with its own trade-offs.
The best option depends on how often the method in question is invoked from differing call sites.
Arrow Function in Class Definition
Instead of using the normal method syntax, use an arrow function to initialize a per-instance member.
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
// OK
console.log(this.status);
}
}
let d = new DemonstrateScopingProblems();
window.setTimeout(d.run); // OK
Good/bad: This creates an additional closure per method per instance of your class. If this method is usually only used in regular method calls, this is overkill. However, if it's used a lot in callback positions, it's more efficient for the class instance to capture the this context instead of each call site creating a new closure upon invoke.
Good: Impossible for external callers to forget to handle this context
Good: Typesafe in TypeScript
Good: No extra work if the function has parameters
Bad: Derived classes can't call base class methods written this way using super.
Bad: The exact semantics of which methods are "pre-bound" and which aren't create an additional non-typesafe contract between your class and its consumers.
Function Expression at Reference Site
Shown here with some dummy parameters for explanatory reasons:
class DemonstrateScopingProblems {
private status = "blah";
public something() {
console.log(this.status);
}
public run(x: any, y: any) {
// OK
console.log(this.status + ': ' + x + ',' + y);
}
}
let d = new DemonstrateScopingProblems();
// With parameters
someCallback((n, m) => d.run(n, m));
// Without parameters
window.setTimeout(() => d.something(), 100);
Good/bad: Opposite memory/performance trade-off compared to the first method
Good: In TypeScript, this has 100% type safety
Good: Works in ECMAScript 3
Good: You only have to type the instance name once
Bad: You'll have to type the parameters twice
Bad: Doesn't easily work with variadic parameters
I think that you should move the result block into the subscribe callback associated the getPlaces method call:
showPosition(position) {
this.xml_Latitude = position.coords.latitude;
this.xml_Lang = position.coords.longitude;
this._http.getPlaces(this.xml_Latitude, this.xml_Lang).subscribe(
data => {
this.maps = data;
var result = this.maps.results; // <----------
var city = result[0].address_components[4].long_name + "," + result[0].address_components[6].long_name;
alert(city);
},
err => { this.cat_error = true }
);
}
It's because this.maps is undefined before the callback is called. And you try tyo get the result attribute before (this.maps.results).
Edit
I also see a problem at the line navigator.geolocation.getCurrentPosition. You could refactor your code this way:
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => { // <----
this.showPosition(position);
});
} else {
alert("Geolocation is not supported by this browser.");
}