Can't understand Polymer 2.0 custom element properties? - polymer

class DemoElement extends Polymer.Element {
static get is() { return "demo-element" }
static get properties() {
return {
prop1 : {
type:String,
notify: true,
reflectToAttriubute: true,
}
}
}
}
I couldn't understand the notify and reflectToAttribute settings of a property here. Can anyone please explain with some simple applicable examples?

Notify:
From: https://www.polymer-project.org/2.0/docs/devguide/properties
notify Type: boolean
If true, the property is available for two-way data binding. In addition, an event, property-name-changed is fired whenever the property changes. See Property change notification events (notify) for more information.
From: https://www.polymer-project.org/2.0/docs/devguide/data-system
A notifying property supports upward data flow. By default, properties are non-notifying, and don't support upward data flow.
Means that any changes that the user makes to this property, will be propagated 'upwards' through the dom tree, or 'target to host'
e.g. https://www.polymer-project.org/2.0/docs/devguide/data-system
<script>
class XTarget extends Polymer.Element {
static get is() {return 'x-target';}
static get properties() {
return {
someProp: {
type: String,
notify: true
}
}
}
}
customElements.define(XTarget.is, XTarget);
</script>
...
<dom-module id="x-host">
<template>
<!-- changes to "value" propagate downward to "someProp" on target -->
<!-- changes to "someProp" propagate upward to "value" on host -->
<x-target some-prop="{{value}}"></x-target>
</template>
<script>
class XHost extends Polymer.Element {
static get is() {return 'x-host';}
}
customElements.define(XHost.is, XHost);
</script>
.
ReflectToAttribute
reflectToAttribute Type: boolean
Set to true to cause the corresponding attribute to be set on the host node when the property value changes. If the property value is Boolean, the attribute is created as a standard HTML boolean attribute (set if true, not set if false). For other property types, the attribute value is a string representation of the property value. Equivalent to reflect in Polymer 0.5. See Reflecting properties to attributes for more information.
so in short, reflectToAttribute exists as a performance boost. unless specified as true, polymer avoids manipulating the dom attributes. if specified as true the property will update the dom elements attribute.
https://github.com/PolymerElements/iron-checked-element-behavior/blob/master/iron-checked-element-behavior.html
iron-checked-element-behavior is propbably the most canonical example of reflecting a property back to an attribute.
Checkboxes in html, in order to meet the specs should have a checked property that appears when checked.
checked: {
type: Boolean,
value: false,
reflectToAttribute: true,
notify: true,
observer: '_checkedChanged'
},
By specifying that checked is reflected to attributes, looking at the demo, https://www.webcomponents.org/element/PolymerElements/iron-checked-element-behavior/demo/demo/index.html
and inspecting using the browser, we can see the state of simple checkbox is updated to checked, when the simple-checkboxes are clicked.
https://github.com/PolymerElements/iron-checked-element-behavior/blob/master/demo/simple-checkbox.html:L32
//L32
<input type="checkbox" id="checkbox" on-tap="_onCheckTap">
//...
//this.checked is inherited from L43
behaviors: [Polymer.IronCheckedElementBehavior],
//...
//L53
_onCheckTap: function() {
this.checked = this.$.checkbox.checked;
},

Related

Specifying a property as `type: Array` doesn't seem to parse the string property value correctly

Update: Filed a bug at https://github.com/Polymer/lit-element/issues/411
I'm having trouble implementing the default and custom converter for properties given the information in the guide. With the current version (0.6.5), it says that you can assign the type Array to a property and it will automatically be parsed as JSON from the string value of the attribute, but that doesn't seem to be the case as described in the code sample.
Here's an illustration of the problem. In the console, it should report an array with three elements, yet it returns an array of one element, the string that contains the attribute value. The result of the render method also shows just one <p> element containing the single string.
<script type="module">
import { LitElement, html } from 'https://unpkg.com/#polymer/lit-element#0.6.5/lit-element.js?module';
class Histogram extends LitElement {
static get properties() {
return {
values: { type: Array }
};
}
constructor() {
super();
this.values = [];
}
render() {
console.log(Array.isArray(this.values), this.values);
return html`
<div>
The elements:
${this.values.map(item => html`<p>item: ${item}</p>`)}
</div>
`;
}
}
customElements.define('x-histogram', Histogram);
</script>
<x-histogram values="[1,2,3]"/>
I've also tried modifying the example by providing a converter, but that doesn't seem to get invoked, either. What am I doing wrong?
I had a look. The 0.6.5 updating-element.js file doesn't even have converting for array. And looking at the npm package of 0.6.5 it doesn't mention an array either.
But the master branch does map the array type, and it has it mentioned in the readme. It should work for you if you pull it and use it directly.

Use AdditionalFields to compare to field in a different class

Introduction
In MVC Core I have a base ViewModel and two ViewModels included in the base model as properties, like so:
public class BaseViewModel
{
public FirstViewModel First { get; set; }
public SecondViewModel Second { get; set; }
}
In FirstViewModel I added a custom validation attribute on one of the properties, inheriting from RemoteAttribute. My goal is to use this attribute comparing the value to a property in SecondViewModel. I've set this up using the AdditionalFields property of the RemoteAttribute.
I think my problem lies in the way the HTML attributes are added to the control in the razor view:
data-val-remote-additionalfields="*.PropOfModelFirst,*.PropOfModelSecond"
When the clientside validation is calling the controller action, the *. is replaced by the framework by First., which is wrong, because the second value is not part of the First-class.
I tried prepending the classname to the second property, resulting in
data-val-remote-additionalfields="*.PropOfModelFirst,*.Second.PropOfModelSecond"
but as can be expected this is changed to First.Second.PropOfModelSecond.
Question
Can the AdditionalFields property be used to compare against values from another ViewModel?
You cannot use AdditionalFields to compare against values from another ViewModel. The reason is that the rules are added to jquery.validate.js by the jquery.validate.unobtrusive.js plugin (which reads the data-val-* attributes generated by the HtmlHelper methods). Specifically it is the adapters.add("remote", ["url", "type", "additionalfields"], function (options) { method that is pre-pending First to the property names.
One option would be to use a single 'flat' view model containing all properties.
If that is not desirable, then you can just write your own ajax code to call your server method that performs the validation. This actually has some added performance benefits as well. By default, after initial validation triggered by the .blur() event, validation is performed on every .keyup() event, meaning that you are potentially making a lot of ajax and database calls if the user initially entered an invalid value.
Remove the [Remote] attribute, and add the following script (I'll assume the properties are First.ABC and Second.XYZ)
$('#First_ABC').change(function() {
var url = '#Url.Action(...)'; // add your action name
var input = $(this);
var message = $('[data-valmsg-for="First.ABC"]'); // or give the element and id attribute
$.post(url, { abc: input.val(), xyz: $('#Second_XYZ').val() }, function(response) {
var isValid = response === true || response === "true";
if (isValid) {
input.addClass('valid').removeClass('input-validation-error');
message.empty().addClass('field-validation-valid').removeClass('field-validation-error');
} else {
input.addClass('input-validation-error').removeClass('valid');
message.text(response).addClass('field-validation-error').removeClass('field-validation-valid');
}
})
});
where the controller method would be
[HttpPost]
public ActionResult Validate(string abc, string xyz)
{
bool isValid = .... // code to validate
if (isValid)
{
return Json(true, JsonRequestBehaviour.AllowGet);
}
else
{
return Json("your error message", JsonRequestBehaviour.AllowGet)
}
}

How to bind an array to the component template in Polymer 2?

I'm trying to directly bind an array to a Polymer 2 component template. It works at first but then it doesn't detect changes. I'm using this.push method in order to allow Polymer to detect the changes, but it's not working. Binding to a dom-repeat template it's OK.
Here you can find a Plunker: http://plnkr.co/edit/MWO7i7m3GB5b7Eqri1yX?p=preview
Is it possible to do what I'm trying? What am I doing wrong?
Thank you for your help
No it's not possible to bind to an array like this.
[[json(data)]] if data is an array, it won't work.
The same way for the observers :
static get observers(){
return [
'_dataChanged(data)'
]
}
This won't work.
On a direct binding, the only way for it to work is to change the complete array value with another one.
class ParentElement extends Polymer.Element {
static get is() { return "parent-element"; }
static get properties(){
return {
data: {
type: Array,
notify: true,
value: function(){
return [{name: 'first'}]
}
}
}
}
constructor() {
super();
}
json(data){
return JSON.stringify(data);
}
addChild(){
this.data = [{name: 'first'},{name: 'second'}];
}
}
customElements.define(ParentElement.is, ParentElement);
In this case above, you can see that the data array is completely replaced and it will work because it's the data object that changed.
In your case you change the content of the array, then the binding won't work.
If you want to see something, in you plunker you can change the HTML part with :
[[json(data.*)]]
Then you will see the polymer binding object that changed.
But instead of binding an array like you did, the best is to use an observer and do our action in the function :
static get observers(){
return [
'_dataChanged(data.splices)'
]
}
_dataChanged(value){
//work to do here
}
For more details you can check the polymer doc here

Using the #bindable attribute on child class in Aurelia

For the current project at work, we are creating quite a few custom controls that all share the same properties and that are bindable.
#bindable required: boolean = false;
#bindable minLength: number = 0;
#bindable maxLength: number = 0;
Since we'd like to avoid code duplication, the idea is to keep these bindable properties in a separate class, called 'Validation' in this case.
import {Validation} from "./validation";
export class MyClass {
private validation: Validation = new Validation();
// also tried:
// #bindable validation: Validation = new Validation();
}
The question is how to get the HTML to bind to the properties in the Validation class. Doing this validation.required.bind="someProperty.required" doesn't update the required property on the validation instance. We attempted to use DI, but that didn't seem to cut it either.
import {inject} from "aurelia-framework";
import {Validation} from "./validation";
#inject(Validation)
export class MyClass {
constructor(private validation: Validation) {
this.validation = validation;
}
}
Any hints would be greatly appreciated.
EDIT:
It seems that Aurelia interprets 'validation.required' as a command rather than an expression.
WARN [templating-binding] Unknown binding command. Object {defaultBindingMode: null, attrName: "validation", attrValue: "true", command: "required", expression: null}
As a work-around until inheritance with bindable properties gets supported in Aurelia, I am binding to a class that has some of the shared properties. The bindable ones will get duplicated across the controls for now.
import {bindable, bindingMode} from "aurelia-framework";
import {IControlBase, ControlBase} from "./controlbase";
export class MyClass {
#bindable controlbase: IControlBase = new ControlBase();
#bindable label: string = "";
#bindable editing: boolean = false;
#bindable({ defaultBindingMode: bindingMode.twoWay })
value: string;
}

Bidirectional binding for flex ComboBox?

I have a collection that I want to bind as data input for a ComboBox:
private static var LOGOS:ArrayCollection = new ArrayCollection([
{index:0, label=logo1},
{index:1, label=logo2}
]);
<s:ComboBox selectedItem="#{model.labelIndex}" labelField="#label" dataProvider="{LOGOS}" />
Now, when selecting an item, the binding should send the associated index property of the objext to the model and update labelIndex.
Of course it does not work as above, because labelIndex is of different datatype than the ArrayCollection.
[Bindable]
private var model:MyModel;
[Bindable]
class MyModel {
public var:Number labelIndex;
}
Question: how can I map the array element to the model and vice versa?
What you are looking for will require some scripting, binding isn't smart enough to figure out how to handle this on its own.
You can use the BindingUtils class to define the bindings, and use the chain argument of the bindProperty method to modify how values are being looked up.
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/binding/utils/BindingUtils.html
For the combo.selectedItem to model.labelIndex binding, you can specify the chain as an array, where the elements define the path where to look for the value:
BindingUtils.bindProperty(model, 'labelIndex', combo, ['selectedItem', 'index']);
This will bind to the selectedItem property, and pass the value of the items index property.
The other way around is a little more tricky and will require using a getter function which grabs the object from the datasource, based on the labelIndex value:
BindingUtils.bindProperty(combo, 'selectedItem', model, {
name: 'labelIndex',
getter: function(host:MyModel):Object
{
return LOGOS.source.filter(function(item:Object, index:int, array:Array):Boolean
{
return item.index === host.labelIndex;
})[0];
}
});
This will bind to the labelIndex property, and the getter function will be invoked when the property changes. The function will filter the datasource based on the models changed labelIndex property value, and return the source object with the matching index property value, which will finally be set for the combobox selectedItem property.
Your combobox definition will of course need an id in order to be targetable via script
<s:ComboBox id="combo" dataProvider="{LOGOS}" />
Note that there's no need forthe the # in the labelField property, this is only used with XML datasources where you need to target an attribute. However, actually you don't need to specify this at all, since label is the default value of the labelField property.