Binding an array in polymer and detecting changes in a child component - arrays

I have a component in Polymer that has an array as one of its properties.
Then I have a child component to which I'm passing this array. What I'm trying to do is to detect a change in that array from the child component.
Here is the parent component:
<dom-module id="some-test">
<template>
<paper-input label="Let's update param" on-input="updateParam"></paper-input>
<some-child param={{param}}></some-child>
</template>
<script>
Polymer({
is: 'some-test',
properties: {
param: {type: Array, value: []}
},
updateParam: function(e) {
this.param["test"] = e.currentTarget.value;
console.log("Param has been updated");
}
});
</script>
</dom-module>
And here is the child component:
<dom-module id="some-child">
<template>
<div>the child component</div>
</template>
<script>
Polymer({
is: 'some-child',
properties: {
param: {type: Array, observer: "doSomething"},
},
doSomething: function() {
console.log("ah, we've detected a change in param");
},
});
</script>
</dom-module>
So, when I type something in the input field (in the first component), I get a consolge log of "Param has been updated". All good.
But then I would except param to be passed to the child component, which would detect the change and log "ah, we've detected a change in param".
However this doesn't seem to happen. If I use a string instead of an array, then it works as intended. But if it's an array and I update one of its values, the child component doesn't pick up the change.
This leads me to believe I am missing something as to how to bind an array. Any clues?

You can use complex observers in child components:
<dom-module id="some-child">
<template>
<div>the child component</div>
</template>
<script>
Polymer({
is: 'some-child',
properties: {
param: {type: Array, observer: "doSomething"},
},
//Here add complex observers
observers: ['doSomething(param.*)', 'doSomething(param.splices)'],
doSomething: function() {
console.log("ah, we've detected a change in param");
},
});
</script>
</dom-module>

Ah, I finally got it working! It took a combination of the various answers. Here is the final code that works:
Parent component:
<dom-module id="some-test">
<template>
<paper-input label="Let's update param" on-input="updateParam"></paper-input>
<some-child param={{param}}></some-child>
</template>
<script>
Polymer({
is: 'some-test',
properties: {
param: {type: Array, value: []}
},
updateParam: function(e) {
this.set("param.test", e.currentTarget.value); <!-- this is the first trick -->
console.log("Param has been updated");
}
});
</script>
</dom-module>
And here is the child component:
<dom-module id="some-child">
<template>
<div>the child component</div>
</template>
<script>
Polymer({
is: 'some-child',
properties: {
param: {type: Array},
},
observers: [
"doSomething(param.*)"
]
doSomething: function() {
console.log("ah, we've detected a change in param");
},
});
</script>
</dom-module>
So, the two things that I needed to do were:
1) Use "this.set()" to update the value of the object in the parent component
2) Use a complex observer in the child component

Related

Polymer app-route return empty data attributes

I'm learning Polymer and i got some issues with app-route and app-location. I tried a simple example like this:
<dom-module id="test-component">
<template>
<style scoped>
</style>
<app-location route="{{route}}" use-hash-as-path></app-location>
<app-route route="{{route}}" pattern="/test/:advisor_id/:user_id"
data="{{data}}" tail="{{subroute}}">
</app-route>
route : [[route.path]]<br>
data.advisor_id : [[data.advisor_id]]<br>
data.user_id : [[data.user_id]]
</template>
<script>
Polymer({
is: "test-component",
properties: {
route: String,
data: Object
},
ready: function() {
console.log(this.route);
}
});
</script>
</dom-module>
The main page only loads components and have test-component tags on the body
With the url localhost/test/advisor_id/14152, i see the component but data.advisor_id and data.user_id are empty. I tested route.path and it is empty too.
It seems like i forgot something but don't understand what it is.
Thanks in advance for your time
remove the 'use-hash-as-path' from your app-location, since it expects a 'http://..../#!/path' syntax for location

Passing a binding to transcluded scope in component

In AngularJS 1.5, I want to pass a binding from a component into the (multi-slot) transcluded scope - for a reference in the template (in either one specific or all of them - no either way is fine).
This is for creating a generic custom-select list
// Component
.component('mySelect', {
bind: {
collection: '<'
},
transclude:{
header: 'mySelectHeader',
item: 'mySelectItem'
},
templateUrl: 'my-select-template',
controller: function(){
.....
}
});
...
// Component template
<script id="my-select-template" type="text/ng-template">
<ol>
<li ng-transclude="header"> </li>
<li ng-transclude="item"
ng-click="$ctrl.select($item)"
ng-repeat"$item in $ctrl.collection">
</li>
</ol>
</script>
...
// Example usage
<my-select collection="[{id: 1, name: "John"}, {id: 2, name: "Erik"}, ... ]>
<my-select-head></my-select-head>
<!-- Reference to $item from ng-repeate="" in component -->
<my-select-item>{{$item.id}}: {{$item.name}}</my-select-item>
</my-select>
Is this possible from a .component()? with custom-directives for the transclusion ?
In your parent component my-select keep a variable like "selectedItem"
In your child component my-select-item, require your parent component like below
require: {
mySelect: '^mySelect'
}
And in your my-select-item component's controller, to access your parent component
$onInit = () => {
this.mySelectedItem= this.mySelect.selectedItem; // to use it inside my-select-item component.
};
select($item) {
this.mySelect.selectedItem = $item; // to change the selectedItem value stored in parent component
}
So that the selected item is now accessible from
<my-select-item>{{selectedItem.id}}: {{selectedItem.name}}</my-select-item>
I ran into this problem as well, and building upon salih's answer, I came up with a solution (disclaimer--see bottom: I don't think this is necessarily the best approach to your problem). it involves creating a stubbed out component for use in the mySelect component, as follows:
.component('item', {
require: { mySelect: '^mySelect' },
bind: { item: '<' }
})
then, tweaking your template:
<script id="my-select-template" type="text/ng-template">
<ol>
<li ng-transclude="header"> </li>
<li ng-click="$ctrl.select($item)"
ng-repeat"$item in $ctrl.collection">
<item item="$item" ng-transclude="item"></item>
</li>
</ol>
</script>
this will mean there's always an item component with the value bound to it. now, you can use it as a require in a custom component:
.component('myItemComponent', {
require: {
itemCtrl: '^item',
}
template: '<span>{{$ctrl.item.id}}: {{$ctrl.item.name}}</span>',
controller: function() {
var ctrl = this;
ctrl.$onInit = function() {
ctrl.item = ctrl.itemCtrl.item;
}
}
});
and to use it:
<my-select collection="[{id: 1, name: "John"}, {id: 2, name: "Erik"}, ... ]>
<my-select-head></my-select-head>
<my-select-item>
<my-item-component></my-item-component>
</my-select-item>
</my-select>
after I implemented this, I actually decided to change my strategy; this might work for you as well. instead of using a transclude, I passed in a formatting function, i.e.:
.component('mySelect', {
bind: {
collection: '<',
customFormat: '&?'
},
transclude:{
header: 'mySelectHeader'
},
templateUrl: 'my-select-template',
controller: function(){
var ctrl = this;
ctrl.format = function(item) {
if(ctrl.customFormat) {
return customFormat({item: item});
} else {
//default
return item;
}
}
.....
}
});
then in the template, just use:
<script id="my-select-template" type="text/ng-template">
<ol>
<li ng-transclude="header"> </li>
<li ng-click="$ctrl.select($item)"
ng-repeat"$item in $ctrl.collection"
ng-bind="::$ctrl.format($item)">
</li>
</ol>
</script>
let me know if you have any feedback or questions!

Polymer 1.0: Refresh/Re-Render Computed Value Without Property Binding

I'm trying to re-compute and render a value within a Polymer 1.0 template. However, I'm trying to do this without binding to any properties.
In case it matters, the use case is for a translation mechanism that uses a string key to find the translated value. When the 'translations' value changes, the translate() call needs to be re-computed.
The component definition is as follows :
<dom-module id="my-component">
<template>
<style></style>
<p><span>[[translate("SOME_STRING")]]</span></p>
</template>
<script>
var MyComponent = Polymer({
is: "my-component",
properties: {
translations: {
type: Object,
notify: true,
value: {
"SOME_STRING": "Some String"
}
}
},
translate: function (key) {
if (this.translations.hasOwnProperty(key)) {
return this.translations[key];
}
}
});
</script>
</dom-module>
I can get the refresh to work by adding the translations property to the translate() call as follows :
<p><span>[[translate("SOME_STRING", translations)]]</span></p>
However, what I would like to do is re-compute/refresh without having to put the translations property as a second parameter in every call (there's other reasons too).
Basically, when the translations object updates with different locale translations, I'd like the translate("SOME_STRING") to be re-computed.
Is this possible? Is there any way to re-render the template or even just re-render the entire component manually? How? If not, what is the simplest way to get the computed value or template re-rendered without a property in the binding?
How about that????
<dom-module id="my-component">
<template>
<style></style>
<p><span>[[str]]</span></p>
</template>
<script>
var MyComponent = Polymer({
is: "my-component",
properties: {
translations: {
type: Object,
notify: true,
value: {
"SOME_STRING": "Some String"
},
observer: '_Changed'
},
str: {
type: String,
value: "hello"
}
},
_Changed: function(){
this.set("str",this.translate(this.str));
},
translate: function (key) {
if (this.translations.hasOwnProperty(key)) {
return this.translations[key];
}
}
});
</script>

Polymer 1: How can I set up paper-checkbox label dynamically using a custom element

I want to set label/s of paper-checkbox elements through a custom element I have created.
This is how I am calling my custom element with the value set to a property called optionLabel which I want to display when checkbox renders on the screen.
<check-list optionLabel="My first checkbox"></check-list>
My custom element check-list looks like this:
<dom-module id="check-list">
<template>
<style>
:host {
display: block;
}
</style>
<paper-checkbox on-change="_checkChanged">{{optionLabel}}</paper-checkbox>
</template>
<script>
(function () {
'use strict';
Polymer({
is: 'check-list',
properties: {
optionLabel: {
type: String,
notify: true
}
},
_checkChanged: function (e) {
alert("State changed");
}
});
})();
</script>
</dom-module>
My goal is to reuse my custom element inside a dom-repeat layout and set values according to the requirement.
What is the correct way of doing this?
According to the documentation camelCase properties are "accessed" from outside the element like camel-case. The documentation states the following:
Attribute names with dashes are converted to camelCase property names
by capitalizing the character following each dash, then removing the
dashes. For example, the attribute first-name maps to firstName. The same mappings happen in reverse when converting property names to attribute names.
In other words, your code should have worked if you did the following instead:
<check-list option-label="My first checkbox"></check-list>
I got it to work! The variable (property) I was using previously was optionLabel, which did not work. Don't know what is the reason but when I changed it to optionlabel, i.e. all lowercase, it worked fine!
Not sure if above is the true solution to the problem I faced but it is working for me now :)
However, it will still be very helpful for many beginners like me if somebody please explain why optionLabel did not work.
So my code now changes to this
Custom element:
<dom-module id="check-list">
<template>
<style>
:host {
display: block;
}
</style>
<paper-checkbox on-change="_checkChanged">{{optionlabel}}</paper-checkbox>
</template>
<script>
(function () {
'use strict';
Polymer({
is: 'check-list',
properties: {
optionlabel: {
type: String,
notify: true
}
},
_checkChanged: function (e) {
if (e.target.checked) {
this.optionlabel = "Good to see you agree";
this.$.btnsubmit.disabled = false;
} else {
this.optionlabel = "Please agree to proceed";
this.$.btnsubmit.disabled = true;
}
}
});
})();
</script>
</dom-module>
And the call looks like:
<check-list optionlabel="My first checkbox"></check-list>

Passing down Polymer's HTML attributes to nested components

I have a custom element <x-marker> with a <paper-checkbox> inside of it.
I want an checked attribute on my element to reflect down on the <paper-checkbox> but my solution doesn't seem to be the way to do it.
HTML:
<x-marker is-checked="<%= someBoolean %>"></x-marker>
Element:
<dom-module id="x-marker">
<template>
<div>
<paper-checkbox id="checkbox"></paper-checkbox>
</div>
</template>
<script>
Polymer({
is: 'x-marker',
properties: {
isChecked: String
},
listeners: {
change: 'changeHandler'
},
attached: function() {
if (this.isChecked === 'true') {
this.$.checkbox.setAttribute('checked', this.isChecked);
}
},
changeHandler: function (event, detail, sender) {
//...
}
});
</script>
</dom-module>
In Polymer 0.5 you could use checked?="{{isChecked}}", but that doesn't seem to work in 1.0 anymore. Also hard coding <paper-checkbox checked="false"> still checks the checkbox as long as the attribute is present. The value doesn't seem to matter, that's why the attribute itself has to be bound, and not its value.
I can't seem to figure this out, including treating the property as a string === 'true' instead of a boolean, and bind it straight to the <paper-checkbox>
In 1.0 you just need to use checked="{{isChecked}}". You can implement it as follows:
HTML:
<x-marker checked="{{someBoolean}}"></x-marker>
ELEMENT:
<dom-module id="x-marker">
<template>
<div>
<paper-checkbox id="checkbox" checked="{{checked}}">Check me</paper-checkbox>
</div>
</template>
<script>
Polymer({
is: 'x-marker',
properties: {
checked: {
type: Boolean,
observer: '_checkChanged'
}
},
_checkChanged: function(newValue, oldValue) {
// Do something
}
});
</script>
</dom-module>

Resources