How to display information from a parent object in Polymer? - javascript

I am writing a Polymer element that collects information from an API and which it should distribute to child elements based on the result's object keys.
The my-parent element executes the ajax call. The response if fetched in the response() function.
My question is this: how can I store the information received in a way, that I can distribute and display it to the child element?
App.html
<my-parent collector="1">
<h1>The Results</h1>
<h3><my-child name="title"><!-- should output FOO --></my-child></h3>
<h3><my-child name="description"><!-- should output BAR --></my-child></h3>
</my-parent>
my-parent.html
<dom-module id="my-parent">
<template>
<style>
:host {
display: block;
}
</style>
<content></content>
<iron-ajax auto url="//someurl/posts/[[collector]]" handle-as="json" last-response="{{response}}" on-response="onResponse" id="xhr"></iron-ajax>
</template>
<script>
Polymer({
is: 'my-parent',
properties: {
collector: {
type: String,
notify: true
},
response: {
type: String
}
},
onResponse: function(response){
/* WHAT TO DO HERE? */
}
})
</script>
</dom-module>
API result from //someurl/posts/1
{
"title": "FOO",
"description": "BAR"
}
my-child.html
<dom-module id="my-child">
<template>
<style>
:host {
display: block;
}
</style>
{{itemes}}
</template>
<script>
Polymer({
is: 'my-child',
properties: {
itemes: {
type: String,
value: function(){
return "what to do here?";
}
}
},
key: {
type: String,
notify: true
}
})
</script>
</dom-module>

Since <my-child> is actually a light DOM child of <my-parent> and not part of <my-parent>'s local DOM (i.e Shadow DOM), you'll have to use Polymer's DOM API.
In my-parent.html, remove the on-response="onResponse" attribute from <iron-ajax> and instead update your <script> to the following:
Polymer({
is: 'my-parent',
properties: {
collector: {
type: String,
notify: true
},
response: {
type: Object,
observer: '_handleResponse'
}
},
_handleResponse: function(response) {
Polymer.dom(this).querySelector('[name="title"]').itemes = response.title;
Polymer.dom(this).querySelector('[name="description"]').itemes = response.description;
}
})
and then my-child.html's <script> can be updated to the following:
Polymer({
is: 'my-child',
properties: {
itemes: {
type: String
}
}
})
Although that may not be exactly what you want, it shows you how to transfer data from a parent component to its light DOM children. In this example, we're setting the itemes property of each <my-child> and that property is set to render its text value as a local DOM text node.
All of that being said, I'm not sure this approach will work with the Shadow DOM v1 spec (there you may need to have the nodes be direct children of and then perhaps have the as a light DOM child or local/shadow DOM child), but for Polymer 1.x using Shady DOM, it'll do the trick.

Related

How do I update the DOM in polymer 1.x when data is changed?

I am new to Polymer and come from an Angular background where data is shared between components using services.
I have two views (form-view and display-view) that I would like to share the data that is "stored" in an object in another element called data-store.
Here is the plunker
form-view
<dom-module id="form-view">
<template>
<style>
:host {
display: inline-block;
}
</style>
<h1>Form View</h1>
<label for="form-name">Name</label>
<input id="form-name" value="{{formData.name::change}}">
<label for="form-city">City</label>
<input id="form-city" value="{{formData.city::change}}">
<data-store form-data="{{formData}}"></data-store>
</template>
<script>
Polymer({
is: 'form-view',
ready: function() {
console.log("FORM VIEW", this.formData);
},
properties: {
formData: {
type: Object
}
}
});
</script>
</dom-module>
display-view
<template>
<style>
:host {
display: inline-block;
}
</style>
<h1>Display View</h1>
<h4>Name: {{formData.name}}</h4>
<h4>City: {{formData.city}}</h4>
<button id="getData">Get Data</button>
<data-store form-data="{{formData}}"></data-store>
</template>
<script>
Polymer({
is: 'display-view',
properties: {
formData: {
type: Object
}
},
ready: function(){
var that = this;
console.log('display view', this.formData);
this.$.getData.addEventListener('click', function(evt) {
console.log("Form Data from store", that.formData);
that.set('formData.name', that.formData.name);
that.set('formData.city', that.formData.city);
})
}
});
</script>
</dom-module>
data-store
<dom-module id="data-store">
<template>
<style>
:host {
display: inline-block;
}
</style>
</template>
<script>
Polymer({
is: 'data-store',
properties: {
formData: {
type: Object,
notify: true,
value: {
name: 'Hello',
city: 'World'
}
}
},
observers: ['_dataChanged(formData.*)'],
_dataChanged: function(change) {
console.log("DATA CHANGED", change);
console.log("Form Data", this.formData);
}
});
</script>
</dom-module>
I basically want to the display-view to be updated whenever I change an input on the form-view.
If you open plunker you will see that the form-view and display-view both show the original values for name and city from the data-store.
How I'm binding them:
<data-store form-data="{{formData}}"></data-store>
When I change either one of the inputs, the observer in the data-store fires the '_dataChanged' function, but the change is not updated on the display-view.
However, if you click on the "get data" button on the display-view after making a change on the "form-view" you will see that the change shows up on the formData object (in the console.log) just not in the view. I even tried to use:
this.set('formData.name', this.formData.name);
Even this won't update the value on the display-view.
Can someone help me understand why my data is not being updated and how I can update an input on one view and have it change on all other views that are bound to the same object?
Thanks!
Polymer implements the mediator pattern, where a host element manages
data flow between itself and its local DOM nodes.
When two elements are connected with a data binding, data changes can
flow downward, from host to target, upward, from target to host, or
both ways.
When two elements in the local DOM are bound to the same property data
appears to flow from one element to the other, but this flow is
mediated by the host. A change made by one element propagates up to
the host, then the host propagates the change down to the second
element.
So, in the above code you were trying to make data flow between three target or child elements i.e. between data-store, form-view and display-view. That is why the data is not rendering in display-view. It would have displayed if data-store have stored the property in localstorage and other elements used that storage to pull that property. That is one way to do what you are looking for.
Another way is to pass the formData from host element i.e. from parent-view. You can simply do:
<data-store form-data="{{formData}}"></data-store>
<iron-pages selected="[[routeName]]" attr-for-selected="name">
<form-view form-data="{{formData}}" name="form"></form-view>
<display-view form-data="{{formData}}" name="display"></display-view>
</iron-pages>
Check in the plnkr: https://plnkr.co/edit/KLw8G04qVPVPmderLlzd?p=preview.

Polymer DOM update after viewport refresh

I've been trying to work this out but sofar have been unable to find an answer. My Polymer element loads a base template JSON file, which is then run through a dom-repeat to create a basic HTML page.
Then another JSON text-file is loaded, which completes the various areas of the HTML with a JS function.
Upon button-click form child, a function is run that triggers the loading of another JSON file that adds additional info. This all works fine.
But when I go out of the page and back in it, it has remembered all my settings but does not display things correctly. It displays the translatedText well and the html code is there, but it does not complete the html code for the originalText.
It seems to want to load the last JSON file before the DOM is properly rendered. So I want it to refresh the whole DOM, but how do I do this?
My MWE:
<template>
<iron-ajax
auto
url="basetext.json"
handle-as="json"
last-response="{{baseText}}"></iron-ajax>
<div class="textcontent">
<template is="dom-repeat" items="{{baseText.lines}}" as="line">
<div class="lineblock">
<div class="line" id="line{{line.lineid}}" inner-h-t-m-l="{{line.linetext}}"></div>
<template is="dom-if" if="[[extraShowEnabled]]">
<div class="linepi" id='linepi{{line.lineid}}' inner-h-t-m-l="{{line.linetext}}"></div>
</template>
</div>
</template>
</div>
<template is="dom-if" if="[[extraLoadEnabled]]">
<iron-ajax
auto
url="originaltext.json"
handle-as="json"
last-response="{{originalText}}"></iron-ajax>
</template>
<iron-ajax
auto
url="translatedtext.json"
handle-as="json"
last-response="{{translatedText}}"></iron-ajax>
</template>
<script>
Polymer({
is: 'text-page',
properties: {
translatedText: Object,
originalText: Object,
extraShowEnabled: {
type: Boolean,
value: false
},
extraLoadEnabled: {
type: Boolean,
value: false
},
showViewer: {
type: String,
value: "none"
}
},
observers: [
'setView(showViewer)',
' _computeSegments(translatedText,".line")',
' _computeSegments(originalText,".linepi")'
],
ready: function() {
this.addEventListener('eventFromChild', this.changeView);
},
changeView: function(event) {
this.showViewer = event.detail.selectedView;
},
setView: function(showViewer) {
\\ first some code here to reset all css.
if (showViewer === "none") {
this.extraShowEnabled = false;
this.extraLoadEnabled = false;
}
if (showViewer === "sidebyside") {
this.extraShowEnabled = true;
this.extraLoadEnabled = true;
this._computeSegments(this.originalText,".linepi");
this._addSideBySideCode();
}
},
_computeSegments: function(inputText,linetype) {
if (inputText) {
Array.from(this.querySelectorAll(linetype+" sc-segment")).forEach(item => item.innerHTML = inputText.segments[item.id]);
}
},
_addSideBySideCode: function() {
\\ this function just adds some css.
},
});
</script>
I think You should try to use a compute function result as a dom-repeat item source, something like this:
<template is="dom-repeat" items="{{itmesByParamsCompute(baseText, originalText, translatedText, extraloadEnabled, ...)}}" as="line">
Add as many params as You need to recompute on. Then that compute function should return a valid source anytime at least one of the paras changes.
Also keep in mind, that if any of these params will become undefined that compute function might be ignored completely. Work around for this is making this opposite way - one property which is modified from manny observers, something like this:
properties: {
items_to_use: {
type: Array,
value: []
},
translatedText: {
type: Object,
observer: 'updateItemsToUse'
},
originalText: {
type: Object,
observer: 'updateItemsToUse'
}
},
updateItemsToUse: function (data) {
let updatedArray = this.someMixFixFunction(this.item_to_use, data);
this.set('items_to_use', updatedArray);
},
someMixFixFunction: function (old_array, data_to_apply) {
// do some merging or what ever You need here, for example
let updatedArray = old_array.concat(data_to_apply);
return updatedArray;
}

Data Binding between Polymer Templatizer instance and host

I am trying to use the Polymer templatizer to create a single instance of a template, append it into a div and get data binding to work between the host and this instance but am having difficulty getting this to work.
The most simple example I have tried:
HTML
<dom-module id="test-app">
<paper-input label="host" value="{{test}}"></paper-input>
<template id="template">
<paper-input label="instance" value="{{test}}"></paper-input>
</template>
<div id="placehere"></div>
</dom-module>
JS
Polymer({
is: "test-app",
behaviors: [Polymer.Templatizer],
properties: {
test: {
type: String,
value: 'hello',
notify: true,
},
},
ready: function() {
this.templatize(this.$.template);
var clone = this.stamp({test: this.test});
Polymer.dom(this.$.placehere).appendChild(clone.root);
},
});
The idea above is to create the instance of the template, place it into "placehere" and have the two input text boxes keep in sync.
When the page loads, the instance is created successfully and the value in both textboxes is "hello" but changing either input box does nothing.
The documentation on the polymer page seems a bit lightweight:
https://www.polymer-project.org/1.0/docs/api/Polymer.Templatizer
but it mentions the use of _forwardParentProp and _forwardParentPath. How exactly am I supposed to implement them in my situation?
As you have already figured out, you need to implement some of the Templatizer's methods. Particularly the _forwardParentProp and _forwardParentPath methods.
But before I begin, I must also point out one additional error in your custom elements definition. In your dom-module element, you have the element's contents defined without the template. It is essential to wrap everything within a template element. The fixed version of your custom element would be like this:
<dom-module id="test-app">
<template>
<paper-input label="host" value="{{test}}"></paper-input>
<template id="template">
<paper-input label="instance" value="{{test}}"></paper-input>
</template>
<div id="placehere"></div>
</template>
</dom-module>
As for the implementation of the Templatizer methods, you first need to store the stamped instance. After that, both methods that need implementations are more or less simple one-liners.
Here is the full JavaScript part of the custom element:
Polymer({
is: "test-app",
behaviors: [Polymer.Templatizer],
properties: {
test: {
type: String,
value: 'hello',
notify: true,
},
},
ready: function() {
this.templatize(this.$.template);
var clone = this.stamp({test: this.test});
this.stamped = clone.root.querySelector('*'); // This line is new
Polymer.dom(this.$.placehere).appendChild(clone.root);
},
// This method is new
_forwardParentProp: function(prop, value) {
if (this.stamped) {
this.stamped._templateInstance[prop] = value;
}
},
// This method is new
_forwardParentPath: function(path, value) {
if (this.stamped) {
this.stamped._templateInstance.notifyPath(path, value, true);
}
},
});
Here is a working JSBin demo: http://jsbin.com/saketemehi/1/edit?html,js,output

Create my own iron-ajax element

I want to create my own iron-ajax element, that does what iron-ajax does but a little more, add some headers to the request, at the moment I always add the header via data-binding to the headers property of iron-ajax but I dont want to code that on every element I build, I want the iron-ajax Element to do this by itself.
Is there a way to extend iron-ajax?
Edit:
<template>
<iron-ajax
auto
url="[[url]]"
handle-as="json"
debounce-duration="1000"
on-response=""
headers="[[headers]]"
></iron-ajax>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'em-ajax',
properties: {
headers: {
type: Object,
computed: 'computeHeaders(token)'
},
token: {
type: String,
notify: true,
value: "asdf"
},
url: {
type: String
}
},
computeHeaders: function() {
return {'Authorization' : 'Bearer {' + app.accessToken + '}'};
},
handleRequest: function(request) {
console.log("handling request", request);
request.header = this.computeHeaders();
}
});
})();
</script>
<em-ajax
url="http://api.asdf.safd.sdf/profile"
on-response="handleProfile"
></em-ajax>
How can I handle the on-response? Passing a String with the method name does not seem to work.
The Answer I marked is correct. For my edited question I found a solution:
this.fire('em-response', {event: event, request: request});
I fire an event in my custom ajax Element, and in the element I use the custom ajax element I added a listener. Is this a good solution?
You can embed <iron-ajax> into your own <fancy-ajax> component and forward properties in and out between <fancy-ajax> and <iron-ajax>.
<dom-module id="fancy-ajax">
<template>
<iron-ajax ...></iron-ajax>
</template>
</dom-module>

Cross-DOM observers in Polymer

I have developed a component for i18n in Polymer.
Based on <iron-localstorage> it stores and changes the locale.
<iron-localstorage name="marvin-locale-ls"
value="{{locale}}"
on-iron-localstorage-load-empty="initializeDefaultLocale"
></iron-localstorage>
<script>
MarvinLocaleLS = Polymer({
is: 'marvin-locale-ls',
properties: {
locale: {type: String},
...
Also I have a translator component that makes a translation based on this locale.
I want to make something like this:
<script>
Polymer({
is: 'marvin-translate',
ls: new MarvinLocaleLS(),
properties: {
key: {
type: String,
notify: true
},
locale: {
type: Polymer.dom().querySelector('marvin-locale-ls').properties.locale,
observer: '_localeObserver'
}
},
ready: function(){
this.key = this.textContent;
var t = this.ls.getTranslation(this.key); // get translation from Local Storage
this.textContent = (t) ? t : this.key; // show translation or key if there is no translation
},
_localeObserver: function(){
console.log('locale changed')
}
});
</script>
In other words I want to create the observer in 'marvin-translate' for a property in 'marvin-locale-ls'. Is it possible?
Take a look at this component, it will enable you to share variable on a Key/Value basis and access them wherever you want. It also supports data-binding (which iron-meta does not yet): https://github.com/akc42/akc-meta

Categories

Resources