Polymer DOM update after viewport refresh - javascript

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;
}

Related

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

How to display information from a parent object in Polymer?

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.

Polymer inline conditional, or, how do I display a loading image?

If I wanted to display a loading image and I was using JQuery to populate my data, I would do something like this:
HTML
<div id="loadthis"><img src="loading.gif"></div>
JS
$('#loadthis').html("Received Data");
However, with Polymer, my template looks like this:
<div id="loadthis">{{receiveddata}}</div>
and I have this property:
Polymer({
is: 'test-loading',
properties: {
receiveddata: {
type: String,
value: ''
}
}
});
I have tried simply putting html in the value for receiveddata but quickly discovered that won't work. I've also tried moustache syntax of {{#if}} etc, but that didn't work at all.
How can I display an image until the receiveddata property is populated?
Based on #a1626's comment mentioning dom-if I came up with this solution:
<div id="loadthis">
<template is="dom-if" if="{{isloading}}">
<img src="loading.gif">
</template>
<template is="dom-if" if="{{!isloading}}">
{{receiveddata}}
</template>
</div>
Then I added an isloading property in my Polymer definition.
Polymer({
is: 'test-loading',
properties: {
receiveddata: {
type: String,
value: ''
},
isloading: {
type: Boolean,
value: true
}
}
});
Now I just set it to false when the data has loaded.
self.isloading = false;
If you want to load local image dynamically, you should load it with full address.
For example, your loading.gif image is under folder /images/loading.gif or /views/loading/loading.gif. Though you can use it like <img src="loading.gif"> statically, once you want to use it dynamically, you should write this.receiveData = '<img src= "/images/loading.gif">'; or this.receiveData = '<img src= "/views/loading/loading.gif">';

polymerfire/firebase-query transaction complete event

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
}
}

Polymer "dom-if" using toggle and item property

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)}}">

Categories

Resources