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
Related
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.
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;
}
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.
Very new to Polymer and Polymerfire. I couldn't find an answer here so hoping I can get help here. The basic question I have is "how do I work with the data that polymerfire/firebase-query sends?" Note I'm using polymerfire version 0.9.4, and polymer is version 1.4.0.
I can load my data from Firebase no problem using Firebase query, however some of the values are raw numbers that I need to convert to user friendly information. For example I have time stored in ms that I want to convert to a date, and a numeric field that indicates the "type" of data that is stored and I want to show an icon for it, not just a raw number. I figured my best option would be to use the transactions-complete promise or an observer. Both fire but neither seems to give me access to the data. The Observer's newData is an empty array, and transactions-complete.. well I don't really know what to do with that when the promise fires. Below is my relevant code. I also tried using notify: true, but I seem to not be grasping the concept correctly.
<firebase-query
id="query"
app-name="data"
path="/dataPath"
transactions-complete="transactionCompleted"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{data}}">
<div class="card">
<div>Title: <span>{{item.title}}</span></div>
<div>Date Created: <span>{{item.dateCreated}})</span></div>
<div>Date Modified: <span>{{item.dateModified}}</span></div>
<div>Status: <span>{{item.status}}</span></div>
</div>
</template>
Polymer({
is: 'my-view1',
properties: {
data: {
notify: true,
type: Object,
observer: 'dataChanged'
}
},
dataChanged: function (newData, oldData) {
console.log(newData[0]);
// do something when the query returns values?
},
transactionCompleted: new Promise(function(resolve, reject) {
// how can I access "data" here?
})`
I wound up going another way entirely, which seemed to be a cleaner approach to what I was doing anyways. I broke it down into separate components. This way when the detail component was loaded, the ready function would allow me to adjust the data before it got displayed:
list.html:
<firebase-query
id="query"
app-name="data"
path="/dataPath"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{data}}">
<my-details dataItem={{item}}></my-details>
</template>
details.html
<template>
<div id="details">
<paper-card heading="{{item.title}}">
<div class="card-content">
<span id="description">{{item.description}}</span><br/><br/>
<div class="details">Date Created: <span id="dateCreated">{{item.dateCreated}}</span><br/></div>
<div class="details">Last Modified: <span id="dateModified">{{item.dateModified}}</span><br/></div>
<div class="status"><span id="status">{{item.status}}</span><br/></div>
</div>
</paper-card>
</template>
Then in the javascript ready function I can intercept and adjust the data accordingly:
Polymer({
is: 'my-details',
properties: {
item: {
notify: true,
},
},
ready: function() {
this.$.dateModified.textContent = this.getDate(this.item.dateModified);
this.$.dateCreated.textContent = this.getDate(this.item.dateCreated);
this.$.status.textContent = this.getStatus(this.item.status);
},
Try the following changes:
Take out the transactions-completed attribute - it is only relevant when the query is updating data to Firebase
Change the dom-repeat template to get it's items attribute from convertedData - this allows you to do the data conversions to## Heading ## the results of the firebase-query
<firebase-query
id="query"
app-name="data"
path="/dataPath"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{convertedData}}">
<div class="card">
<div>Title: <span>{{item.title}}</span></div>
<div>Date Created: <span>{{item.dateCreated}})</span></div>
<div>Date Modified: <span>{{item.dateModified}}</span></div>
<div>Status: <span>{{item.status}}</span></div>
</div>
</template>
Add a convertedData property to do your data conversions from data which has the raw data
Change the observer syntax as per the example. This sets up the observer to to observe for changes to deep property values which results in the observer method being fired - see: https://www.polymer-project.org/1.0/docs/devguide/observers#deep-observation
In the observer method you can populate the convertedData object from the data object which should then render the content
Polymer({
is: 'my-view1',
properties: {
data: {
notify: true,
type: Object
},
convertedData: {
notify: true,
type: Object
}
},
// observer syntax to monitor for deep changes on "data"
observers: [
'dataChanged(data.*)'
]
dataChanged: function (newData, oldData) {
console.log(newData);
// convert the "newData" object to the "convertedData" object
}
}
Goal
I'm wanting to use a toggle to filter (or hide via dom-if) elements based on the toggle itself and a boolean property of the element.
So with an array of objects:
[{..., active: true}, {..., active: false}]
I want to hide the "active:false" objects with the toggle.
Problem
I can't figure out how to get the if function "_showAlert(item)" to fire on toggle switch or more importantly if this is even the way I should go about this using Polymer. I'd appreciate guidance on either.
<paper-toggle-button checked="{{activeOnly}}">Active Only</paper-toggle-button>
<paper-listbox>
<template is="dom-repeat" items="[[alerts]]">
<template is="dom-if" if="{{_showAlert(item)}}">
<paper-item>...</paper-item>
</template>
</template>
</paper-listbox>
<script>
(function() {
'use strict';
Polymer({
is: 'alert-list',
properties: {
alerts: {
type: Array
},
activeOnly: {
type: Boolean,
notify: true
}
},
ready: function(){
this.alerts = [{..., active: true}, {..., active: false}];
},
_showAlert: function(item) {
// The alert item will have a boolean property called "active"
return (item.active || !this.activeOnly);
}
})
})();
</script>
I've just appended some data in the ready function for now for development purposes. I can get the list to display just fine and I can verify the binding between the toggle and the "activeOnly" property is working.
Thanks!
I assume you would want your showAlert function to re-evaluate every time either item.active or activeOnly change. To achieve that you have to pass them to the funciton as arguments (also see docs).
<template is="dom-if" if="{{_showAlert(item.active, activeOnly)}}">