How to set sub-properties in read-only Polymer 1.x properties - javascript

How do I set a read-only sub-property in Polymer without completely replacing its value?
Polymer({
is: 'my-element',
properties: {
foo: {
type: Object,
value: function() { return {bar:1, baz:2}; },
readOnly: true,
notify: true
}
},
observers: [
'_onBarChanged(foo.bar)', // this should fire
'_onBazChanged(foo.baz)' // this not
],
// how do I set this.foo.bar without completely replacing this.foo
ready: function() {
// the only way I found is completely replacing it's value
let temp = Object.assign({}, this.foo, {bar: 3});
this._setFoo(temp);
}
});
It feels like I'm missing something.

When updating an object's subproperty, you should use this.set('foo.baz', value) instead of this.foo.baz = value so that the change-notification is emitted.
HTMLImports.whenReady(() => {
Polymer({
is: 'x-foo',
properties: {
foo: {
type: Object,
readOnly: true,
value: () => ({bar: 2, baz: 4})
}
},
observers: [
'_barChanged(foo.bar)',
'_bazChanged(foo.baz)',
],
_barChanged: function(bar) {
console.log('bar', bar);
},
_bazChanged: function(baz) {
console.log('baz', baz);
},
_changeBar: function() {
this.set('foo.bar', this.foo.bar === 5 ? 3 : 5);
},
_changeBaz: function() {
this.set('foo.baz', this.foo.baz === 3 ? 5 : 3);
}
});
});
<head>
<base href="https://polygit.org/polymer+1.7.0/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
</head>
<body>
<x-foo></x-foo>
<dom-module id="x-foo">
<template>
<div>bar: [[foo.bar]]</div>
<div>baz: [[foo.baz]]</div>
<button on-tap="_changeBar">Change bar</button>
<button on-tap="_changeBaz">Change baz</button>
</template>
</dom-module>
</body>
codepen

Related

Vue Test Utils + Third-party component: cannot detect input event

I'm passing through a problem to detect the input event emission while using a third-party component only when testing. The component behaves as it's supposed to when testing on the browser. Using the Vue extension I also can see that it's emitting the event correctly.
I'm using Choices as my custom select.
Vue.component('choices', {
props: ['options', 'value'],
template: '#choices-template',
mounted: function() {
this.choicesInstance = new Choices(this.$refs.select);
this.$refs.select.addEventListener('addItem', this.handleSelectChange);
this.setChoices();
},
methods: {
handleSelectChange(e) {
this.$emit('input', e.target.value);
},
setChoices() {
this.choicesInstance.setChoices(this.options, 'id', 'text', true);
}
},
watch: {
value: function(value) {
console.log('input triggered: ' + value);
},
options: function(options) {
// update options
this.setChoices();
}
},
destroyed: function() {
this.choicesInstance.destroy();
}
})
var vm = new Vue({
el: '#el',
template: '#demo-template',
data: {
selected: 2,
options: [{
id: 1,
text: 'Hello'
},
{
id: 2,
text: 'World'
}
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css" />
<div id="el"></div>
<!-- using string template here to work around HTML <option> placement restriction -->
<script type="text/x-template" id="demo-template">
<div>
<p>Selected: {{ selected }}</p>
<choices :options="options" v-model="selected">
<option disabled value="0">Select one</option>
</choices>
</div>
</script>
<script type="text/x-template" id="choices-template">
<select ref="select">
<slot></slot>
</select>
</script>
My test file is like this (I'm using Jest):
import CustomSelect from '#/CustomSelect';
import { mount } from '#vue/test-utils';
let wrapper,
options = [
{
key: 'Foo',
value: 'foo',
},
{
key: 'Bar',
value: 'bar',
},
{
key: 'Baz',
value: 'baz',
},
];
beforeEach(() => {
wrapper = mount(CustomSelect, {
propsData: {
options,
},
});
});
it('Emits an `input` event when selection changes', () => {
wrapper.vm.choicesInstance.setChoiceByValue([options[1].value]);
expect(wrapper.emitted().input).not.toBeFalsy();
});
wrapper.emitted() is always an empty object for this test;
If I expose choicesInstance from my component to global context and manually call choicesInstance.setChoiceByValue('1') it correctly emits the input event.

Vue js issue with jquery get

Hi I'm trying to get data via ajax and using vue wrapper component. Here is my code.
<html>
<head>
<title>title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
html, body {
font: 13px/18px sans-serif;
}
select {
min-width: 300px;
}
</style>
</head>
<body>
<div id="el"></div>
<!-- using string template here to work around HTML <option> placement restriction -->
<script type="text/x-template" id="demo-template">
<div>
<p>Selected: {{ input.selected }}</p>
<select2 :options="options" v-model="input.selected">
<option disabled value="0">Select one</option>
</select2>
</div>
</script>
<script type="text/x-template" id="select2-template">
<select>
<slot></slot>
</select>
</script>
<script src="http://themestarz.net/html/craigs/assets/js/jquery-3.3.1.min.js"></script>
<script src="https://unpkg.com/vue#2.5.17/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
Vue.component('select2', {
props: ['options', 'value'],
template: '#select2-template',
mounted: function () {
var vm = this;
$(this.$el)
// init select2
.select2({data: this.options})
.val(this.value)
.trigger('change')
// emit event on change.
.on('change', function () {
vm.$emit('input', this.value)
});
},
watch: {
value: function (value) {
// update value
$(this.$el)
.val(value)
.trigger('change')
},
options: function (options) {
// update options
$(this.$el).empty().select2({data: options})
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
})
var vm = new Vue({
el: '#el',
template: '#demo-template',
data: {
input: {
selected: "all"
},
options: []
},
created: function () {
this.mymethod();
},
methods: {
mymethod: function () {
var vm = this;
$.get("https://api.coindesk.com/v1/bpi/currentprice.json", function (data) {
vm.options = [
{id: 'all', text: 'All'},
{id: 1, text: 'Hello'},
{id: 2, text: 'World'},
{id: 3, text: 'Bye'}
];
vm.input.selected = 2;
});
}
}
});
</script>
</body>
</html>
After items loaded to drop down I need to change the selected item like this
vm.input.selected = 2;
But unfortunately this is not happening after ajax request. If I added the array before ajax it happens as expected but I need data from an ajax request. And I have rduced the complexity of code for better visibility.
Here is a jsfiddle for issue. I think the issue is with vue component.
I did a few tests and it seems like you're essentially changing the select2's value before its options, and since option 2 doesn't exist, the change fizzles.
Like I mentioned in my comment, changing the order of options and value in the component's watch fixes this, probably because that way the options are changed right before the new value is set.
Working example:
Vue.component('select2', {
props: ['options', 'value'],
template: '#select2-template',
mounted: function() {
var vm = this;
$(this.$el)
// init select2
.select2({
data: this.options
})
.val(this.value)
.trigger('change')
// emit event on change.
.on('change', function() {
vm.$emit('input', this.value)
});
},
watch: {
options: function(options) {
// update options
$(this.$el).empty().select2({
data: options
})
},
value: function(value) {
// update value
$(this.$el)
.val(value)
.trigger('change')
}
},
destroyed: function() {
$(this.$el).off().select2('destroy')
}
})
var vm = new Vue({
el: '#el',
template: '#demo-template',
data: {
input: {
selected: "all"
},
options: []
},
created: function() {
this.mymethod();
},
methods: {
mymethod: function() {
var vm = this;
$.get("https://api.coindesk.com/v1/bpi/currentprice.json", function(data) {
vm.options = [{
id: 'all',
text: 'All'
},
{
id: 1,
text: 'Hello'
},
{
id: 2,
text: 'World'
},
{
id: 3,
text: 'Bye'
}
];
vm.input.selected = 2;
// setTimeout(() => { vm.input.selected = 2; }, 0);
});
}
}
});
html,
body {
font: 13px/18px sans-serif;
}
select {
min-width: 300px;
}
<script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script type="text/javascript" src="https://unpkg.com/vue#2.5.17/dist/vue.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="el"></div>
<!-- using string template here to work around HTML <option> placement restriction -->
<script type="text/x-template" id="demo-template">
<div>
<p>Selected: {{ input.selected }}</p>
<select2 :options="options" v-model="input.selected">
<option disabled value="0">Select one</option>
</select2>
</div>
</script>
<script type="text/x-template" id="select2-template">
<select>
<slot></slot>
</select>
</script>

Polymer: Vers simple data binding doesn't work in the second element

I am working on this issue for 6 hours now and I seem to be unable to see it.
So here is the snippet from the index.html:
<flat-data-array availableModes="{{modes}}" id="dataArray"></flat-data-array>
<flat-strip-view availableModes="{{modes}}" id="flatViewer"></flat-strip-view>
the dataArray (which works always fine):
<link rel="import" href="../../bower_components/polymer/polymer.html">
<dom-module id="flat-data-array">
<script>
(function() {
'use strict';
Polymer({
is: 'flat-data-array',
properties: {
strips: {
type: Array,
notify: true,
observe: '_stripsChanged'
},
availableModes: {
type: Number,
notify: true,
observe: '_modesChanged'
},
socket: {
type: Object
}
}
,
_stripsChanged: function(newVal, oldVal) {
this.fire('flat-strip-array-changed',{ newValue: newVal, oldValalue: oldVal});
},
_modesChanged: function(newVal, oldVal) {
this.fire('flat-strip-mode-changed',{ newValue: newVal, oldValalue: oldVal});
alert("Changed");
},
ready: function() {
this.socket = io.connect('http://192.168.0.101:3000');
socket.on('flatCommand', function(data) {
console.log(data);
});
this.availableModes=15;
/*[
{
modeID: 65,
letter: "A",
name: "Singler Color"
}
];*/
}
});
})();
</script>
</dom-module>
and now the problem:
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../elements/flat-list/flat-list.html">
<dom-module id="flat-strip-view">
<template>
<style>
:host {
display: block;
}
</style>
<flat-list
strips="{{strips}}"
id="flatList"
>
</flat-list>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'flat-strip-view',
properties: {
strips: {
type: Array,
notify: true,
observer: '_flatStripChanged'
},
availableModes: {
type: Number,
notify: false,
observer: '_flatModeChanged'
}
}
,
_flatModeChanged: function(newVal, oldVal) {
this.fire('flat-strip-view-mode-changed',{ newValue: newVal, oldValalue: oldVal});
alert("Event");
},
_flatStripChanged(newVal, oldVal) {
this.fire('flat-strip-view-array-changed',{ newValue: newVal, oldValalue: oldVal});
}
});
})();
</script>
</dom-module>
due to the definition in the index.html I'd expect the availableModes to be the same in both elements. But if i type:
documtent.getElementById('dataArray').availableModes
I get 15 (perfectly ok), but when I type
document.getElementById('flatViewer').availableModes it says undefined
Oddly enough, had another definition in the same manner before (infact I only removed it to track down the problem) and that worked and passed the values down to the last element in the cain. I just can't see any difference.
<aiur-data-array strips="{{mystrips}}" availableModes="{{modes}}" id="dataArray"></aiur-data-array>
<aiur-strip-view availableModes="{{modes}}" strips="{{mystrips}}" id="aiurViewer"></aiur-strip-view>
That worked for "strips" in any direction with any element...
Change the attribute availableModes to available-modes.
When mapping attribute names to property names:
Attribute names are converted to lowercase property names. For example, the attribute firstName maps to firstname.
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.
Souce: https://www.polymer-project.org/1.0/docs/devguide/properties.html#property-name-mapping

Polymer 1.0 Global Variables

In Polymer 0.5 the advice on globals was as outlined in this question/answer:
Polymer global variables
However in Polymer 1.0 this doesn't seem to work. Change notifications are not automatically generated on the underlying model, they are generated on the <dom-module> instead which means that change notifications will be generated on only one of the <app-globals>.
What is the recommended way of implementing this pattern in Polymer 1.0?
Polymer element <iron-meta> is also an option. For me this was the easiest solution.
I've extended Etherealones' solution to work as a Behavior, and to extend Polymers "set" and "notifyPath" methods to trigger the updates automatically. This is as close as i could get to a true databinding across components/elements:
globals-behavior.html:
<script>
var instances = [];
var dataGlobal = {};
var GlobalsBehaviour = {
properties: {
globals: {
type: Object,
notify: true,
value: dataGlobal
}
},
ready: function() {
var _setOrig = this.set;
var _notifyPathOrig = this.notifyPath;
this.set = function() {
_setOrig.apply(this, arguments);
if (arguments[0].split(".")[0] === "globals") {
this.invokeInstances(_notifyPathOrig, arguments);
}
};
this.notifyPath = function(path, value) {
_notifyPathOrig.apply(this, arguments);
if (arguments[0].split(".")[0] === "globals") {
this.invokeInstances(_notifyPathOrig, arguments);
}
};
},
invokeInstances: function(fn, args) {
var i;
for (i = 0; i < instances.length; i++) {
instance = instances[i];
if (instance !== this) {
fn.apply(instance, args);
}
}
},
attached: function() {
instances.push(this);
},
detached: function() {
var i;
i = instances.indexOf(this);
if (i >= 0) {
instances.splice(i, 1);
}
}
};
</script>
And in all polymer elements that should have access to the globals variable:
<script>
Polymer({
is: 'globals-enabled-element',
behaviors: [GlobalsBehaviour]
});
</script>
Examples:
I have posted a full example as a Gist on Github
Here's a snippet to see it in action:
<!DOCTYPE html>
<html>
<head>
<title>Globals Behavior Example</title>
<link rel="import" href="//rawgit.com/Polymer/polymer/master/polymer.html">
<dom-module id="globals-enabled-element">
<template>
<input type="text" value="{{globals.my_variable::input}}">
</template>
<script>
var instances = [];
var dataGlobal = {};
var GlobalsBehaviour = {
properties: {
globals: {
type: Object,
notify: true,
value: dataGlobal
}
},
ready: function() {
var _setOrig = this.set;
var _notifyPathOrig = this.notifyPath;
this.set = function() {
_setOrig.apply(this, arguments);
if (arguments[0].split(".")[0] === "globals") {
this.invokeInstances(_notifyPathOrig, arguments);
}
};
this.notifyPath = function(path, value) {
_notifyPathOrig.apply(this, arguments);
if (arguments[0].split(".")[0] === "globals") {
this.invokeInstances(_notifyPathOrig, arguments);
}
};
},
invokeInstances: function(fn, args) {
var i;
for (i = 0; i < instances.length; i++) {
instance = instances[i];
if (instance !== this) {
fn.apply(instance, args);
}
}
},
attached: function() {
instances.push(this);
},
detached: function() {
var i;
i = instances.indexOf(this);
if (i >= 0) {
instances.splice(i, 1);
}
}
};
</script>
<script>
Polymer({
is: 'globals-enabled-element',
behaviors: [GlobalsBehaviour]
});
</script>
</dom-module>
</head>
<body>
<template is="dom-bind">
<p>This is our first polymer element:</p>
<globals-enabled-element id="element1"></globals-enabled-element>
<p>And this is another one:</p>
<globals-enabled-element id="element2"></globals-enabled-element>
</template>
</body>
</html>
I have implemented a pattern like iron-signals uses for this purpose. So the basic principle is that you manually notify other instances when an update occurs.
Consider this:
<dom-module id="x-global">
<script>
(function() {
var instances = [];
var dataGlobal = {};
Polymer({
is: 'x-global',
properties: {
data: {
type: Object,
value: dataGlobal,
},
},
attached: function() {
instances.push(this);
},
detached: function() {
var i = instances.indexOf(this);
if (i >= 0) {
instances.splice(i, 1);
}
},
set_: function(path, value) {
this.set(path, value);
instances.forEach(function(instance) {
if (instance !== this) { // if it is not this one
instance.notifyPath(path, value);
}
}.bind(this));
},
notifyPath_: function(path, value) {
instances.forEach(function(instance) {
instance.notifyPath(path, value);
});
},
fire_: function(name, d) {
instances.forEach(function(instance) {
instance.fire(name, d);
});
},
});
})();
</script>
</dom-module>
You will simple call the version that have an underscore suffix like fire_ when you are firing an event. You can even create a Polymer Behavior of some sort with this pattern I guess.
Beware that preceding underscore properties are already used by Polymer so don't go ahead and convert these to _fire.
P.S.:
I didn't look around to solve how to reflect the notification of this.push(array, value); as I don't need it. I don't know if it's possible this way. Should go find Polymer.Base.push.
Sjmiles, one of Polymer's creators just posted the following snippet to the Polymer slack room as an example of shared data:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="shared-data element and repeats">
<base href="http://milestech.net/components/">
<script href="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
</head>
<body>
<demo-test></demo-test>
<script>
(function() {
var private_data = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
Polymer({
is: 'private-shared-data',
properties: {
data: {
type: Object,
notify: true,
value: function() {
return private_data;
}
}
}
});
})();
Polymer({
is: 'xh-api-device',
properties: {
data: {
type: Array,
notify: true
},
_share: {
value: document.createElement('private-shared-data')
}
},
observers: [
'dataChanged(data.*)'
],
ready: function() {
this.data = this._share.data;
this.listen(this._share, 'data-changed', 'sharedDataChanged');
},
dataChanged: function(info) {
this._share.fire('data-changed', info, {bubbles: false});
},
sharedDataChanged: function(e) {
this.fire(e.type, e.detail);
},
add: function(name) {
this.push('data', {name: name});
}
});
</script>
<dom-module id="demo-test">
<template>
<h2>One</h2>
<xh-api-device id="devices" data="{{data}}"></xh-api-device>
<template is="dom-repeat" items="{{data}}">
<div>name: <span>{{item.name}}</span></div>
</template>
<h2>Two</h2>
<xh-api-device data="{{data2}}"></xh-api-device>
<template is="dom-repeat" items="{{data2}}">
<div>name: <span>{{item.name}}</span></div>
</template>
<br>
<br>
<button on-click="populate">Populate</button>
</template>
<script>
Polymer({
populate: function() {
this.$.devices.add((Math.random()*100).toFixed(2));
// this works too
//this.push('data', {name: (Math.random()*100).toFixed(2)});
}
});
</script>
</dom-module>
</body>
</html>
I've actually moved my app to using simple data binding, so I'm not sure of the validity of this approach, but maybe it would be useful to someone.
I have tried to improve on Alexei Volkov's answer, but I wanted to define the global variables separately. Instead of the getters/setters I used the observer property and saved the key together with the instances.
The usage is:
<app-data key="fName" data="{{firstName}}" ></app-data>
whereas the keyproperty defines the name of the global variable.
So for example you can use:
<!-- Output element -->
<dom-module id="output-element" >
<template>
<app-data key="fName" data="{{data1}}" ></app-data>
<app-data key="lName" data="{{data2}}" ></app-data>
<h4>Output-Element</h4>
<div>First Name: <span>{{data1}}</span></div>
<div>Last Name: <span>{{data2}}</span></div>
</template>
</dom-module>
<script>Polymer({is:'output-element'});</script>
Definition of the <app-data>dom module:
<dom-module id="app-data"></dom-module>
<script>
(function () {
var instances = [];
var vars = Object.create(Polymer.Base);
Polymer({
is: 'app-data',
properties: {
data: {
type: Object,
value: '',
notify: true,
readonly: false,
observer: '_data_changed'
},
key: String
},
created: function () {
key = this.getAttribute('key');
if (!key){
console.log(this);
throw('app-data element requires key');
}
instances.push({key:key, instance:this});
},
detached: function () {
key = this.getAttribute('key');
var i = instances.indexOf({key:key, instance:this});
if (i >= 0) {
instances.splice(i, 1);
}
},
_data_changed: function (newvalue, oldvalue) {
key = this.getAttribute('key');
if (!key){
throw('_data_changed: app-data element requires key');
}
vars.set(key, newvalue);
// notify the instances with the correct key
for (var i = 0; i < instances.length; i++)
{
if(instances[i].key == key)
{
instances[i].instance.notifyPath('data', newvalue);
}
}
}
});
})();
</script>
Fully working demo is here: http://jsbin.com/femaceyusa/1/edit?html,output
I've combined all suggestions above into the following global polymer object
<dom-module id="app-data">
</dom-module>
<script>
(function () {
var instances = [];
var vars = Object.create(Polymer.Base);
var commondata = {
get loader() {
return vars.get("loader");
},
set loader(v) {
return setGlob("loader", v);
}
};
function setGlob(path, v) {
if (vars.get(path) != v) {
vars.set(path, v);
for (var i = 0; i < instances.length; i++) {
instances[i].notifyPath("data." + path, v);
}
}
return v;
}
Polymer({
is: 'app-data',
properties: {
data: {
type: Object,
value: commondata,
notify: true,
readonly: true
}
},
created: function () {
instances.push(this);
},
detached: function () {
var i = instances.indexOf(this);
if (i >= 0) {
instances.splice(i, 1);
}
}
});
})();
</script>
and use it elsewere like
<dom-module id="app-navigation">
<style>
</style>
<template>
<app-data id="data01" data="{{data1}}" ></app-data>
<app-data id="data02" data="{{data2}}"></app-data>
<span>{{data1.loader}}</span>
<span>{{data2.loader}}</span>
</template>
</dom-module>
<script>
(function () {
Polymer({
is: 'app-navigation',
properties: {
},
ready: function () {
this.data1.loader=51;
}
});
})();
</script>
Changing either data1.loader or data2.loader affects other instances. You should to extend commondata object to add more global properties like it shown with loader property.
It is much easier to achieve the same effect of global variables if you wrapped your application in a template. Watch the explanation in this video (I linked to the exact minute and second where the concept is explained).
Using ootwch's solution, I ran into a race condition situation with lazy-loaded components.
As posted, lazy-loaded components are not initialized with the value from the shared data.
In case anyone else runs into the same problem, I think I fixed it by adding a ready callback like this:
ready: function() {
const key = this.getAttribute('key')
if (!key) {
throw new Error('cm-app-global element requires key')
}
const val = vars.get(key)
if (!!val) {
this.set('data', val)
}
},
Hope this saves someone some pain.

Shared behavior state across custom elements

I have these two custom Polymer Elements (Polymer 1.0.3):
Displays text to be translated.
Displays button to trigger loading of translation.
I also have a Behavior that holds the translations (json object) and contains all the functions that make translation possible.
This is what I expect to happen:
Click the button in Element 2
Translations load into the Behavior
Language selection is set in the Behavior
Text in Element 1 is updated with the translated equivalent
Steps 1 - 3 happen, but 4 doesn't. The text is never updated. I can get it to work if Elements 1 & 2 are combined as the same element, but not if they're separate (any they need to be separate).
If you're wondering about the "kick" property, it's something I learned from Polymer 0.5. It got things working when the two elements were combined, so I'm thinking it'll be be necessary when the elements are separate.
Any idea how I can make this happen? I'm open to alternative paradigms.
Code
This is roughly how my code is laid out. I also made a plunker with a single-page test case.
index.html
<!doctype html>
<html>
<head>
<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<link rel="import" href="behavior.html">
<link rel="import" href="element1.html">
<link rel="import" href="element2.html">
</head>
<body>
<my-element></my-element>
<another-element></another-element>
</body>
</html>
Element 1
<dom-module id="my-element">
<template>
<p>{{localize(label, kick)}}</p>
</template>
</dom-module>
<script>
Polymer({
is: 'my-element',
behaviors: [
behavior
],
properties: {
label: {
type: String,
value: 'original'
}
}
});
</script>
Element 2
<dom-module id="another-element">
<template>
<button on-click="buttonClicked">load</button>
</template>
</dom-module>
<script>
Polymer({
is: 'another-element',
behaviors: [
behavior
],
buttonClicked: function() {
this.registerTranslation('en', {
original: 'changed'
})
this.selectLanguage('en');
}
});
</script>
Behavior
<script>
var behavior = {
properties: {
kick: {
type: Number,
value: 0
},
language: {
type: String,
value: 'fun'
},
translations: {
type: Object,
value: function() {
return {};
}
}
},
localize: function(key, i) {
if (this.translations[this.language] && this.translations[this.language][key]) {
return this.translations[this.language][key];
}
return key;
},
registerTranslation: function(translationKey, translationSet) {
this.translations[translationKey] = translationSet;
},
selectLanguage: function(newLanguage) {
this.language = newLanguage;
this.set('kick', this.kick + 1);
}
};
</script>
First, although the notion is to have behavior be a conduit for shared data between instances, as written, each instance will have it's own copy of the translations object and the kick property.
Second, even if that data was privatized so it could be shared, the kick binding made via localize(label, kick) is in a different scope from the expression that modifies kick (i.e. this.set('kick', this.kick + 1); [{sic} this could simply be this.kick++;]).
To notify N instances of a change in shared data, one must keep track of those instances. A good way to do this is by attaching event listeners. Another way is simply keeping a list.
Here is an example implementation of your design:
<script>
(function() {
var instances = [];
var translationDb = {};
var language = '';
window.behavior = {
properties: {
l10n: {
value: 0
}
},
attached: function() {
instances.push(this);
},
detached: function() {
this.arrayDelete(instances, this);
},
_broadcast: function() {
instances.forEach(function(i) {
i.l10n++;
});
},
localize: function(key, i) {
if (translationDb[language] && translationDb[language][key]) {
return translationDb[language][key];
}
return key;
},
registerTranslation: function(translationKey, translationSet) {
translationDb[translationKey] = translationSet;
},
selectLanguage: function(newLanguage) {
language = newLanguage;
this._broadcast();
}
};
})();
</script>
<dom-module id="my-element">
<template>
<p>{{localize(label, l10n)}}</p>
</template>
<script>
Polymer({
behaviors: [
behavior
],
properties: {
label: {
type: String,
value: 'original'
}
}
});
</script>
</dom-module>
<dom-module id="another-element">
<template>
<button on-tap="buttonClicked">load</button>
</template>
<script>
Polymer({
behaviors: [
behavior
],
buttonClicked: function() {
this.registerTranslation('en', {
original: 'changed'
});
this.selectLanguage('en');
}
});
</script>
</dom-module>

Categories

Resources