I have a dialog as you can see here:
<template>
<ux-dialog>
<ux-dialog-body>
<h2 t="luminaires.list.enter-serialnumber">Bitte geben Sie eine neue Seriennummer ein</h2>
<input value.bind="serialNumber" />
</ux-dialog-body>
<ux-dialog-footer>
<button click.trigger="controller.cancel()" t="luminaires.list.cancel">Abbrechen</button>
<button click.trigger="controller.ok(serialNumber)" t="luminaires.list.ok">Ok</button>
</ux-dialog-footer>
</ux-dialog>
</template>
and related view-model:
import {DialogController} from "aurelia-dialog";
import {Controller} from "aurelia-templating";
export class SerialnumberDialog {
private static inject = [DialogController];
private serialNumber: string;
private controller: any;
constructor(controller: Controller) {
this.controller = controller;
}
}
I want to change the color of the following sentence sometimes.
<h2 t="luminaires.list.enter-serialnumber">Bitte geben Sie eine neue Seriennummer ein</h2>
For example when some body give a repetitious serial number, I want to change the colour to red. I can open the dialog through the following code:
this.dialogService.open({ viewModel: SerialnumberDialog, lock: false })
.whenClosed((response) => {......
I want to use Aurelia concept for this purpose. Could you please tell me the solution?
I would use the css.bind method on the <h2> element. I would create a method on your view model to be able to decide whether you want the text to be red or not, and then store the styles in a css variable.
import {DialogController} from "aurelia-dialog";
import {Controller} from "aurelia-templating";
export class SerialnumberDialog {
private static inject = [DialogController];
private serialNumber: string;
private controller: any;
constructor(controller: Controller) {
this.controller = controller;
this.myCss = {
color: 'black'
};
}
activate(){
if(//serial number is repetitious){
this.myCss.color = red;
}
}
}
Now you have a myCss object which can be bound to your view to alter the colour of your text.
<h2 t="luminaires.list.enter-serialnumber" css.bind="myCss">
Bitte geben Sie eine neue Seriennummer ein
</h2>
Dwayne Charrington does a great article on css binding on his ILikeKillNerds blog https://ilikekillnerds.com/2016/02/binding-with-style-in-aurelia/ if you want any more information.
Related
I am creating a block with InnerBlocks component.
If no content added to the InnerBlocks (and even with content in fact) it is very difficult to popup the block toolbar
I would like to add an iconbutton on top corner that will show the block floating toolbar
How can I tell the .block-editor-block-contextual-toolbar to show?
I don't see any method of the .wp-block in the inspector that would do that and the documentation of Block Controls: Block Toolbar and Settings Sidebar https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar/ is quite basic
Many thanks
You can use useSelect() to determine if there are any blocks present in the InnerBlocks component:
import { useSelect } from '#wordpress/data';
const hasInnerBlocks = useSelect((select) => (
select('core/block-editor').getBlock(clientId).innerBlocks.length > 0
), [clientId]);
Then you can use hasInnerBlocks to conditionally render whatever you'd like within the edit function:
{ !!hasInnerBlocks && (
<BlockControls group="block">
<ToolbarGroup
// Toolbar group settings here
/>
</BlockControls>
) }
Try to use same code structure among the edit and save methods. The RIchText need to be waraped inside div.
<div>
<RichText.Content
className={ `sticky-note-${ props.attributes.alignment }` }
style={ {
fontSize: props.attributes.fontSize,backgroundColor: props.attributes.color,
} }
tagName="p"
value={ props.attributes.content }/>
</div>
Example
I created this example to illustrate your situation.
import { InnerBlocks, BlockControls } from '#wordpress/block-editor';
// ...
edit: () => {
const blockProps = {
// your own props
};
return (
<div { ...blockProps }>
<BlockControls>
// your controls
</BlockControls>
<InnerBlocks />
</div>
);
}
Problem
For the BlockControls to decide whether or not it should appear, it needs to get some context from its parent which your own props don't have.
Solution:
Use the block props instead for the parent of BlockControls.
Steps:
Import useBlockProps from #wordpress/block-editor:
import { InnerBlocks, BlockControls, useBlockProps } from '#wordpress/block-editor';
Pass your own props as an argument to useBlockProps():
const blockProps = useBlockProps({
// your own props
});
Result
import { InnerBlocks, BlockControls, useBlockProps } from '#wordpress/block-editor';
// ...
edit: () => {
const blockProps = useBlockProps({
// your own props
});
return (
<div { ...blockProps }>
<BlockControls>
// your controls
</BlockControls>
<InnerBlocks />
</div>
);
}
Links
I hope that helped.
My answer is based on Wordpress's official Block Editor Handbook:
https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar/#block-toolbar
https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/
https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#block-wrapper-props
This seems like it should be pretty straight forward... within a stepper, you're collecting info, and you want to make sure an email is an email. But it seems like the shared 'form' tag causes some issues where the error checker gets messed up and doesn't work?
Further clarification... the issue seems to actually be in the following tag element...
formControlName="emailCtrl"
When I remove this line, and remove it's sibling line from the .ts (emailCtrl: ['', Validators.required],) the error check starts working. However, that means that the stepper can't verify that this step is required.
How can I make sure the stepper validates an entry and at the same time make sure that the ErrorStateMatcher works?
Here is my combined HTML...
<mat-step [stepControl]="infoFormGroup">
<form [formGroup]="infoFormGroup">
<ng-template matStepLabel>Profile Information</ng-template>
<div>
<!-- <form class="emailForm"> -->
<mat-form-field class="full-width">
<input matInput placeholder="Username" [formControl]="emailFormControl"
formControlName="emailCtrl"
[errorStateMatcher]="infoMatcher">
<mat-hint>Must be a valid email address</mat-hint>
<mat-error *ngIf="emailFormControl.hasError('email') && !emailFormControl.hasError('required')">
Please enter a valid email address for a username
</mat-error>
<mat-error *ngIf="emailFormControl.hasError('required')">
A username is <strong>required</strong>
</mat-error>
</mat-form-field>
<!-- </form> -->
</div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</form>
</mat-step>
As you can see, I have commented out the nested 'form' for the email slot. In testing, I have tried it commented and not commented out. Either way, the error checking doesn't work right.
Here are some of the pertinent .ts snippets...
import { FormControl, FormGroupDirective, NgForm, Validators } from '#angular/forms';
import { FormBuilder, FormGroup } from '#angular/forms';
import { ErrorStateMatcher } from '#angular/material/core';
export class Pg2ErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}
...
export class Pg2Dialog {
...
emailFormControl = new FormControl('', [
Validators.required,
Validators.email,
]);
infoMatcher = new Pg2ErrorStateMatcher();
...
this.infoFormGroup = this._formBuilder.group({
emailCtrl: ['', Validators.required],
});
I believe I figured this out. the ErrorStateMatcher requires a named form control. In this case, it's emailFormControl. This is declared as the following...
emailFormControl = new FormControl('', [
Validators.required,
Validators.email,
]);
Also, the stepper requires a named form group, that in itself declares a new form control. In this case, it was emailCtrl. It was declared as the following...
this.infoFormGroup = this._formBuilder.group({
emailCtrl: ['', Validators.required],
});
To have the stepper form control utilize the ErrorStateMatcher form control, simply drop the square brackets inside the .group assignment and assign emailFormControl to the emailCtrl. Like this...
this.infoFormGroup = this._formBuilder.group({
emailCtrl: this.emailFormControl
});
I tested this in a different code section with a similar problem and it worked in both places!
I implemented a custom control which is basically a styled file uploader specifically for images, which allows the preview of images. The problem is that I experience a weird behavior: the setter of the property to which the regular HTML <input type="file"> is bound gets called twice in FF 53, IE 11, Edge 38, but not in Chrome 57. I have no idea why this happens, perhaps some of you have experienced similar behavior and know a solution.
The markup is the following:
<template>
<!-- markup for preview functionality, irrelevant here -->
<input type="file" class="sr-only" id="${id}" aria-describedby="${id}-help" multiple accept="image/*" files.bind="imageGroup" />
<label class="btn btn-default btn-sm" aria-hidden="true" for="${id}" role="button">
<i class="fa fa-plus" aria-hidden="true"></i> Add files
</label>
</template>
The backing TypeScript code:
import { bindable, bindingMode, autoinject } from 'aurelia-framework';
import { DialogService } from 'aurelia-dialog';
import { Confirm } from '../confirm';
#autoinject
export class ImageUploader {
#bindable({ defaultBindingMode: bindingMode.twoWay }) imageArray: Array<File> = null;
#bindable id: string = null;
#bindable maxImageCount: number = 10;
#bindable maxFileSizeKiloBytes: number = 2048;
constructor(private dialogService: DialogService) { }
idChanged(newValue: string) {
if (!newValue) {
throw Error('The id parameter needs a value in order to make the file uploader work.');
}
}
set imageGroup(fileList: FileList) {
console.log('imageGroup');
if (!fileList || !fileList.length) return;
if (!this.imageArray) return;
for (let i = 0; i < fileList.length; ++i) {
let file = fileList.item(i);
if (this.imageArray.length >= this.maxImageCount) {
// TODO: Alert: maximum number of images reached
} else {
if (file && file.type.startsWith('image/')) {
// Size is in bytes. Div by 1024 to get kilobytes.
if (file.size / (1024) > this.maxFileSizeKiloBytes) {
// TODO: Alert: Image file too large.
} else {
this.imageArray.push(file);
}
} else {
// TODO: Alert: unsupported media type.
}
}
}
}
}
As you can see, I use the "hide the default file uploader and style a bound label instead" trick to style the uploader itself - I only say this to point out that I've checked if maybe this is the cause, but it's not, the same behavior can be experienced if I show the default file uploader and use that.
You can also see that I've bound the <input type="file"> to a setter-only property. I've done that because this property is basically just a proxy which populates another array (which is not in the control, this is what is bound to the control) which is necessary because I need to allow the user to upload files in multiple turns so that (s)he can select files from different folders.
The rest of the code is irrelevant, because the console.log line runs twice whenever I select some files, so the error is located somewhere here - I am just unable to figure out exactly what is causing this to happen.
Any help is appreciated.
I confirmed that using setter on that case causes to call your setter two times in FF, aurelia created another setter for every property that needs to observe. However you can achieve same behavior using observable with minimal changes and also provided encapsulation in this case. See those gist.run link. More info here.
UPDATE
For Firefox this is still open as a bug. But a workaround using change.delegate as elefantino and abemeister said works on FF, see gist here.
I'm creating a custom element that dynamically generates a user input form. So far it's working quite well, but I'm having problems setting up Aurelia-Validation on it and need some help.
my-form.html
<div class="form-group" repeat.for="control of controls">
<label>${control.label}</label>
<div>
<compose containerless
view-model="resources/elements/${control.type}/${control.type}"
model.bind="{'control': control, 'model': model, 'readonly': readonly}"
class="form-control"></compose>
</div>
</div>
I have several different control.type's available and working (my-textbox, my-dropdown, my-datepicker) -- each of them is a custom element. For instance:
Example control (my-textbox.html)
<template>
<input type="text"
class="form-control"
value.bind="model[control.bind] & validate">
</template>
Parent view:
<my-form controls.bind="controls" model.bind="model" if.bind="controls"></my-form>
Parent view-model:
controls = [
{label: 'Username', type: 'my-textbox', bind: 'user_username'},
{label: 'Status', type: 'my-dropdown', bind: 'user_status', enum: 'ActiveInactive', default: '(Choose)'},
{label: 'Last_login', type: 'my-datepicker', bind: 'user_lastlogin', vc: 'date'}];
ValidationRules
.ensure('user_username').required().minLength(1).maxLength(10)
.ensure('user_status').required()
.ensure('user_lastlogin').required()
.on(this.model);
I'm getting the error Error: A ValidationController has not been registered. at ValidateBindingBehaviorBase.bind.... However, I only want one validator for the whole dynamically-built form, so I don't want to import validation in each of the controls. What do I do?
It actually works to import the Validation Controller and related resources in the parent form, even though the & validate is attached to the child controls.
Parent view-model
import {inject} from 'aurelia-framework';
import {ValidationControllerFactory, ValidationRules} from 'aurelia-validation';
import {BootstrapFormRenderer} from 'common/bootstrap-form-renderer';
#inject(Core, ValidationControllerFactory)
export class MyForm {
constructor(validationControllerFactory) {
this.validationCtrl = validationControllerFactory.createForCurrentScope();
this.validationCtrl.addRenderer(new BootstrapFormRenderer());
}
setupValidator() {
let rules = [];
this.controls.map(control => {
if (control.validation) {
rules.push(ValidationRules.ensure(control.bind).required().rules[0]);
}
this.validationCtrl.addObject(this.modelEdit, rules);
}
}
Short question: How can I validate a parent form when the validation is part of child custom elements?
Long version:
I built a reusable custom element which includes validation which is working like I expect it to do:
validated-input.html:
<template>
<div class="form-group" validate.bind="validation">
<label></label>
<input type="text" value.bind="wert" class="form-control" />
</div>
</template>
validated-input.js:
import { bindable, inject } from 'aurelia-framework';
import { Validation } from 'aurelia-validation';
#inject(Validation)
export class ValidatedInputCustomElement {
#bindable wert;
constructor(validation) {
this.validation = validation.on(this)
.ensure('wert')
.isNotEmpty()
.isGreaterThan(0);
}
}
I will have some forms that will use this custom element more than once in the same view (can be up to 8 or 12 times or even more). A very simplified example could look like this:
<template>
<require from="validated-input"></require>
<form submit.delegate="submit()">
<validated-input wert.two-way="val1"></validated-input>
<validated-input wert.two-way="val2"></validated-input>
<validated-input wert.two-way="val3"></validated-input>
<button type="submit" class="btn btn-default">save</button>
</form>
</template>
In the corresponding viewmodel file I would like to ensure that the data can only be submitted if everything is valid. I would like to do something like
this.validation.validate()
.then(() => ...)
.catch(() => ...);
but I don't understand yet how (or if) I can pull the overall validation into the parent view.
What I came up with up to now is referencing the viewmodel of my validated-input like this:
<validated-input wert.two-way="val1" view-model.ref="vi1"></validated-input>
and then to check it in the parent like this:
this.vi1.validation.validate()
.then(() => ...)
.catch(() => ...);
but this would make me need to call it 8/12/... times.
And I will probably have some additional validation included in the form.
Here is a plunkr with an example:
https://plnkr.co/edit/v3h47GAJw62mlhz8DeLf?p=info
You can define an array of validation objects (as Fabio Luz wrote) at the form level and then register the custom element validations in this array. The validation will be started on the form submit.
The form code looks like:
validationArray = [];
validate() {
var validationResultsArray = [];
// start the validation here
this.validationArray.forEach(v => validationResultsArray.push(v.validate()));
Promise.all(validationResultsArray)
.then(() => this.resulttext = "validated")
.catch(() => this.resulttext = "not validated");
}
validated-input.js gets a new function to register the validation
bind(context) {
context.validationArray.push(this.validation);
}
The plunker example is here https://plnkr.co/edit/X5IpbwCBwDeNxxpn55GZ?p=preview
but this would make me need to call it 8/12/... times.
And I will probably have some additional validation included in the form.
These lines are very important to me. In my opinion (considering that you do not want to call 8/12 times, and you also need an additional validation), you should validate the entire form, instead of each element. In that case, you could inject the validation in the root component (or the component that owns the form), like this:
import { Validation } from 'aurelia-validation';
import { bindable, inject } from 'aurelia-framework';
#inject(Validation)
export class App {
val1 = 0;
val2 = 1;
val3 = 2;
resulttext = "";
constructor(validation) {
this.validation = validation.on(this)
.ensure('val1')
.isNotEmpty()
.isGreaterThan(0)
.ensure('val2')
.isNotEmpty()
.isGreaterThan(0)
.ensure('val3')
.isNotEmpty()
.isGreaterThan(0);
//some additional validation here
}
validate() {
this.validation.validate()
.then(() => this.resulttext = "valid")
.catch(() => this.resulttext = "not valid");
}
}
View:
<template>
<require from="validated-input"></require>
<form submit.delegate="validate()" validation.bind="validation">
<validated-input wert.two-way="val1"></validated-input>
<validated-input wert.two-way="val2"></validated-input>
<validated-input wert.two-way="val3"></validated-input>
<button type="submit" class="btn btn-default">validate</button>
</form>
<div>${resulttext}</div>
</template>
Now, you can re-use the validated-input component in other places. And of course, you probably have to rename it, because the name validated-input does not make sense in this case.
Here is the plunker example https://plnkr.co/edit/gd9S2y?p=preview
Another approach would be pushing all the validation objects into an array and then calling a function to validate all validations objects, but that sounds strange to me.
Hope it helps!