aurelia-validation with custom element - custom-element

I created an input form with aurelia-validation plugin which worked perfectly.
Now I created a custom element to replace the input type textbox.
I'm trying to validate the new custom element value, by setting the value attribute with the "validate" keyword - but the custom element input value isn't validated.
It seems like the Validation Controller is not binded to the custom element.
The bindings array of the Validation Controller doesn't contains the custom element.
Maybe this is related to the actions that should trigger the validation (blur\focus out), so I added dispatching of blur event, but it still doesn't work. As mentioned - it worked perfectly when it was a regular element.
Here is the relevant code (un-needed code was removed):
custom element template:
<template>
<label>
${title}<input name.bind="fieldName"
title.bind="title" focusout.trigger="focusoutAction()" />
</label>
</template>
custom element relevant viewmodel code:
#bindable onFocusout;
...
bind(bindingContext) {
var input = this.element.getElementsByTagName("input")[0];
input.type = this.customType || "text";
input.placeholder = this.placeHolder || "";
//input.value.bind = bindingContext.registration.firstName & validate;
}
...
focusoutAction() {
var customEvent = new CustomEvent("blur");
this.element.dispatchEvent(customEvent);
this.onFocusout();
}
Relevant container(parent) view code:
<form-input name="firstName" id="firstName" title="First Name" bind-
value="registration.firstName & validate" field-name="firstName" on-
focusout.call="validateInput()" />
And the relevant viewmodel code:
ValidationRules
.ensure(r => r.firstName).displayName('first
name').required().withMessage(`\${$displayName} cannot be blank`)
.satisfiesRule('FirstNameValidation')
.ensure(r => r.lastName).displayName('last
name').required().withMessage(`\${$displayName} cannot be blank`)
.satisfiesRule('LastNameValidation')
validateInput() { this.getValidationError(event.target.name); }
getValidationError(propertyName) {
let error = this.getValidationFirstError(propertyName);
....
}
getValidationFirstError(propertyName)
{
if (this.controllerToValidate.errors !== null &&
this.controllerToValidate.errors.length > 0) //This is 0 !!!!!
}

Related

How can I add a vue component in kendo grid custom editor?

I have a column of kendo grid like:
<kendo-grid-column
:field="'qrcode'"
:title="'qrcode'"
:width="200"
:editor="qrcodeEditor"
></kendo-grid-column>
According to Setting Custom Editors, I can rewrite the editor to textarea, checkbox, or dropdown list, like
textareaEditor: function(container, options) {
$('<textarea data-bind="value: ' +
options.field +
'" cols="20" rows="4"></textarea>'
).appendTo(container);
},
My problem is what if the editor is a vue component, like <qrcode-capture />?
I have found the solution:
methods:{
qrcodeEditor: function(container, options) {
// An <input> element is required for binding data
let input = $(`<input data-bind="value:${options.field}" class="k-textbox width-50"/>`);
input.appendTo(container);
// use Vue.extend to make a component constructor, and new a component
let qrcodeCapture = new (Vue.extend(QrcodeCapture))();
qrcodeCapture.$on("decode", decodedString => {
input.val(decodedString).trigger("change");
// Trigger "change" element to tell kendo that you have change the data
});
qrcodeCapture.$mount();
container.append(qrcodeCapture.$el);
},
}

Render form after method completion in VueJS

I am facing a problem with my page with VueJS. It's a page for different translations of the website. It has a dropdown on the top for the language selection that once switched will update the fields with the current language.
The problem starts when it loads, because my form is like this:
<form id="trForm">
...
<input type="text" name="header_title" class="form-control" v-model="translations.header.header_title" />
...
</form>
It's trying to access these attributes before the method returns any data, but somehow it will still show the data once it is complete, but it becomes troublesome when I try to switch the language, it won't because of this problem and also, if I do the following:
<form id="trForm">
...
<input type="text" name="header_title" v-if="translations.header" class="form-control" v-model="translations.header.header_title" />
...
</form>
on each field, those that aren't populated will display no field at all for a new input value. I tried something like translations.features || '', but no success.
I also tried to put on the parent block a condition that if the loading is false will display the form, but since the page is loaded first than the method is executed, it will always be false for the first microsecond.
methods: {
fetchTranslations(e) {
let vm = this;
vm.loaded = false;
$.get('/ajax/admin/translations', { 'locale': e }).done((data) => {
if (data.success) {
vm.translations = JSON.parse(data.translations.translation);
vm.loaded = true;
} else {
toastr.error('Something went wrong');
}
});
},
Please, what do I do? It'd be good to show the form after there is data.
Introduce a new variable, e.g. loaded that defaults to false
Use this variable as a v-if condition on the form
In the callback of your data fetch, set loaded to true.

Observe Change in Vue js model when input updated by javascript function

I am using Date Range Picker to select to dates now when I select the dates, I update the inputs with dates value respectively.
The inputs I have binded with v-model and created a function in watch attribute of component to observe the change in model.
But when the inputs are updated with the javascript function no change can be observed in the model but the value of my input fields are updated.
// My Input Fields
<input type="text" name="updateStartDate" v-model="updateDateRange.start">
<input type="text" name="updateEndDate" v-model="updateDateRange.end">
//My javascript Function
$('input[rel=dateRangePickerX]').daterangepicker({
'autoApply': true,
'drops': 'up',
'startDate': moment().add(90, 'days').calendar(),
'endDate': moment().add(97, 'days').calendar(),
locale: { cancelLabel: 'Clear' }
},
function (start, end, label) {
$('input[name="updateStartDate"]').val(start.format('MM/DD/YYYY'));
$('input[name="updateEndDate"]').val(end.format('MM/DD/YYYY'));
});
// My watch attribute in Component
watch : {
'updateDateRange.end' : function (val) {
console.log('In Watch Function');
console.log(this.dateRanges);
if(val != '' && this.updateDateRange.start != '' && this.updateDateRangeIndex != ''){
console.log(val);
console.log(this.updateDateRange.start);
console.log(this.updateDateRangeIndex);
this.dateRanges[this.updateDateRangeIndex] = this.updateDateRange;
this.updateDateRangeIndex = '';
this.updateDateRange.start = '';
this.updateDateRange.end = '';
console.log(this.dateRanges);
}
}
}
I don't like to mix jQuery and Vue because jQuery messes up the DOM. Even more, I find it completely unnecessary.
Simple only with native Vue you can do it like this:
<input type="text" name="updateStartDate" v-model="startDate" #input="onInput()">
<input type="text" name="updateStartDate" v-model="endDate" #input="onInput()">
methods: {
onInput(e): function () {
// this will be called on change of value
}
}
Further to set the value and update the DOM simply update startDate and/or endDate variables and DOM will update accordingly.
You need to work with your model and not fiddle with the bound DOM element. You have bound the elements to viewmodel items:
<input type="text" name="updateStartDate" v-model="updateDateRange.start">
<input type="text" name="updateEndDate" v-model="updateDateRange.end">
then you use jQuery to set the field values
$('input[name="updateStartDate"]').val(start.format('MM/DD/YYYY'));
$('input[name="updateEndDate"]').val(end.format('MM/DD/YYYY'));
but you should be setting the bound values instead:
updateDateRange.start = start.format('MM/DD/YYYY');
updateDateRange.end = end.format('MM/DD/YYYY');

Aurelia: validating form with reusable validatable custom element

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!

Dragging dnd items generates "this.manager.nodes[i] is null"

I use Dojo 1.3.1, essentially under FF3.5 for now.
I have a dnd source which is also a target. I programmatically add some nodes inside, by cloning template items. The aim for the user is then to use dnd to order the items. It is ok for one or two actions, then I got the "this.manager.nodes[i] is null" error in Firebug, then no more dnd action is taken into account.
My HTML (jsp), partial:
<div id="templates" style="display:none">
<div class="dojoDndItem action" id="${act.name}Template">
<fieldset>
<legend class="dojoDndHandle" >${act.name}</legend>
<input id="${act.name}.${parm.code}." type="text" style="${parm.style}"
dojoTypeMod="dijit.form.ValidationTextBox"
/><br>
</fieldset></div>
</div>
My Javascript for adding/removing dnd items nodes, partial :
function addActionFromTemplate(/* String */actionToCreate, /* Object */data) {
// value of actionToCreate is template id
var node = dojo.byId(actionToCreate + "Template");
if (node) {
var actNode = node.cloneNode(true);
// make template id unique
actNode.id = dojo.dnd.getUniqueId();
// rename inputs (add the action nb at the end of id)
// and position dojo type (avoid double parsing)
dojo.query("input[type=text], select", actNode).forEach( function(input) {
input.id = input.id + actionsCount;
dojo.attr(input, "name", input.id);
dojo.attr(input, "dojoType", dojo.attr(input, "dojoTypeMod"));
dojo.removeAttr(input, "dojoTypeMod");
});
// insert the action at script's tail
actionList.insertNodes(true, [ actNode ]);
dojo.parser.parse(actNode);
// prepare for next add
actionsCount++;
}
}
function deleteAction(node) {
var cont = getContainerClass(node, "action");
// remove the fieldset action
cont.parentNode.removeChild(cont);
}
Thanks for help ...
OK, it seems that, finally, simply using :
actionList.insertNodes(false, [ actNode ]);
instead of
actionList.insertNodes(true, [ actNode ]);
fixed the pb .