Data binding between two Polymer elements using polymer 1.0 - data-binding

Question
In the below example, how do I bind the obj.name variable of the <input> field in <test-element2> to <test-element> ?
Background:
Below is my code. I have two polymer elements. test-element has the data binded to obj.name. test-element2 has an input field which is observed by the function objChanged. Whatever value I change in the input field it changes and prints in test-element2 but the change isn't reflected in test-element. Can any body help get the value reflected to test-element1? I have a solution using this.fire("object-change") for when the text changes but I am looking for a solution without using event listeners.
One more thing is that I need to create an element from the script and it cannot be hardcoded in the HTML DOM.
Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
<script src="../../bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../bower_components/polymer/polymer.html"/>
</head>
<body>
<dom-module id="test-element">
<template>
<div>Hello <span>{{obj.name}}</span></div>
</template>
<script>
TestElement = Polymer({
is: "test-element",
properties: {
"obj": {
type: Object,
notify: true
}
},
observers: [
"objChanged(obj.name)"
],
"objChanged": function() {
var that = this;
console.log("First element in 1",that.obj);
}
});
</script>
</dom-module>
<dom-module id="test-element2">
<template>
<input value="{{obj.name::input}}"/>
</template>
<script>
Polymer({
is: "test-element2",
properties: {
"obj": {
type: Object,
notify: true,
value: {
"name": "Charlie"
}
}
},
observers: [
"objChanged(obj.name)"
],
ready: function() {
var element = new TestElement();
element.set("obj", this.obj);
this.appendChild(element);
},
"objChanged": function() {
console.log("changed in test-element2:", this.obj);
}
});
</script>
</dom-module>
<test-element2></test-element2>
</body>
</html>

If you include <test-element> in the <template> of test-element2 you can avoid using event listeners or observers. In this way test-element2 handles the data binding between the input and <test-element> for you.
Below is a live working example that maintains the obj property as you have set it up in your elements.
<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="test-element">
<template>
<div>Hello <span>[[obj.name]]</span>
</div>
</template>
<script>
TestElement = Polymer({
is: "test-element",
properties: {
"obj": {
type: Object,
notify: true
}
}
});
</script>
</dom-module>
<dom-module id="test-element2">
<template>
<input value="{{obj.name::input}}" />
<test-element obj="[[obj]]"></test-element>
</template>
<script>
Polymer({
is: "test-element2",
properties: {
"obj": {
type: Object,
notify: true,
value: {
"name": "Charlie"
}
}
}
});
</script>
</dom-module>
<test-element2></test-element2>
Currently, imperative data-binding is not supported in Polymer 1.0 outside of <template is="dom-bind">.
Polymer 1.0 Node.bind() - Can I create a binding via javascript instead of double braces?
Binding imperatively
[1.0] Data-binding: Is there any way to do this imperatively?
I would recommend setting up observers like the example below or adjusting your requirements to include <test-element> in test-element2.
button {
display: block;
}
<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="test-element">
<template>
<div>Hello <span>[[obj.name]]</span>
</div>
</template>
<script>
TestElement = Polymer({
is: "test-element",
properties: {
obj: {
type: Object,
notify: true
}
}
});
</script>
</dom-module>
<dom-module id="test-element2">
<template>
<input value="{{obj.name::input}}" />
</template>
<script>
Polymer({
is: "test-element2",
properties: {
obj: {
type: Object,
notify: true,
value: {
"name": "Charlie"
}
}
},
observers: ["objNameChanged(obj.name)"],
objNameChanged: function(newValue) {
Polymer.dom(document).querySelectorAll("test-element").forEach(function(element) {
element.notifyPath("obj.name", newValue);
});
Polymer.dom(this.root).querySelectorAll("test-element").forEach(function(element) {
element.notifyPath("obj.name", newValue);
});
}
});
</script>
</dom-module>
<test-element2></test-element2>
<button>Add test-element to <em>test-element2</em>
</button>
<button>Add test-element to <em>body</em>
</button>
<script>
var testElement2 = document.querySelector("test-element2");
var createTestElement = function(insertPoint) {
var testElement = new TestElement();
testElement.notifyPath("obj.name", testElement2.obj.name);
insertPoint.appendChild(testElement);
};
document.querySelector("button:nth-of-type(2)").addEventListener("click", function() {
createTestElement(Polymer.dom(document).querySelector("body"));
});
document.querySelector("button").addEventListener("click", function() {
createTestElement(Polymer.dom(testElement2.root));
});
</script>

If you choose to break out your elements into their own files, you could follow this Plunker example (by nazerke) demonstrating two-way data binding by having one component observing another's property.
Code
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="parent-element.html">
<link rel="import" href="first-child.html">
<link rel="import" href="second-child.html"> </head>
<body>
<parent-element></parent-element>
</body>
</html>
parent-element.html
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="parent-element">
<template>
<first-child prop={{value}}></first-child>
<second-child feat1={{prop}}></second-child> In parent-element
<h1>{{value}}</h1> </template>
<script>
Polymer({
is: "parent-element",
properties: {
value: {
type: String
}
},
valueChanged: function() {
console.log("value changed");
}
});
</script>
</dom-module>
first-child.html
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="first-child">
<template>
<p>first element.</p>
<h2>{{prop}}</h2> </template>
<script>
Polymer({
is: "first-child",
properties: {
prop: {
type: String,
notify: true
}
},
ready: function() {
this.prop = "property";
}
});
</script>
</dom-module>
second-child.html
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="second-child">
<template>
<p>Second element.</p>
<h2>{{feat1}}</h2> </template>
<script>
Polymer({
is: "second-child",
properties: {
feat1: {
type: String,
notify: true,
value: "initial value"
}
},
ready: function() {
this.addEventListener("feat1-changed", this.myAct);
},
myAct: function() {
console.log("feat1-changed ", this.feat1);
}
});
</script>
</dom-module>

If you choose to break out your elements into their own files, you could use <iron-meta> to two-way data bind as described here:
Example Code:
<iron-meta key="info" value="foo/bar"></iron-meta>
...
meta.byKey('info').getAttribute('value').
or
document.createElement('iron-meta').byKey('info').getAttribute('value');
or
<template>
...
<iron-meta id="meta"></iron-meta>
...
this.$.meta.byKey('info').getAttribute('value');
....
</template>

If you choose to break out your elements into their own files, you could use <iron-localstorage> to two-way data bind as described here.
Example Code:
<dom-module id="ls-sample">
<iron-localstorage name="my-app-storage"
value="{{cartoon}}"
on-iron-localstorage-load-empty="initializeDefaultCartoon"
></iron-localstorage>
</dom-module>
<script>
Polymer({
is: 'ls-sample',
properties: {
cartoon: {
type: Object
}
},
// initializes default if nothing has been stored
initializeDefaultCartoon: function() {
this.cartoon = {
name: "Mickey",
hasEars: true
}
},
// use path set api to propagate changes to localstorage
makeModifications: function() {
this.set('cartoon.name', "Minions");
this.set('cartoon.hasEars', false);
}
});
</script>

<dom-module id="test-element">
<template>
<div>Hello <span>{{name}}</span></div>
</template>
<script>
Polymer({
is: "test-element",
properties: {
name: String
}
});
</script>
</dom-module>
<dom-module id="test-element2">
<template>
<input value="{{name::input}}"/>
<test-element name="[[name]]"></test-element>
</template>
<script>
Polymer({
is: "test-element2",
properties: {
name: String
}
});
</script>
</dom-module>

Related

How to pass parameter of a function with v-for in Vue?

I am trying to pass a parameter to a function by looping through an the array items with v-for.
<template>
<v-app>
<v-app-bar app>
<v-app-bar-nav-icon #click="drawer = !drawer"></v-app-bar-nav-icon>
<v-spacer></v-spacer>
<h1 ref="y"></h1>
</v-app-bar>
<v-content>
<router-view />
<v-navigation-drawer v-model="drawer" class="x">
<v-list-item
v-for="item in items"
:key="item.unidade"
:to="item.link"
:#click="change(item.method)"
>{{item.unidade}}</v-list-item>
</v-navigation-drawer>
</v-content>
</v-app>
</template>
<script>
export default {
name: "App",
data: () => ({
items: [
{ unidade: "IPE", link: "/ipe", method: "IPE" },
{ unidade: "DCSI", link: "/dcsi", method: "DCSI" },
{ unidade: "RT", link: "/rt", method: "RT" }
],
drawer: false
}),
methods: {
change(val) {
console.log(val);
this.$refs.y.innerText = val;
}
}
};
</script>
<style lang="stylus" scoped>
.x {
position: absolute;
}
</style>
I want the parameter in items arrray to be passed to change(val) method giving each v-list-item a distinct event listener.
Then I want h1 with the ref="y" to change it's text based on the v-list-item I click. But so far I am getting the browser error of "Error in render: "TypeError: Cannot set property 'innerText' of undefined""
Instead of setting the innerText of the <h1> you could instead bind the innerText to a reactive variable. You could create a variable in data that could store the selected method and then bind that to the innerText using {{}} syntax. Doing it this way would be more inline with Vue best practices. Let me show you what I mean.
<template>
<v-app>
<v-app-bar app>
<v-app-bar-nav-icon #click="drawer = !drawer"></v-app-bar-nav-icon>
<v-spacer></v-spacer>
<h1 ref="y">{{ selectedMethod }}</h1>
</v-app-bar>
<v-content>
<router-view />
<v-navigation-drawer v-model="drawer" class="x">
<v-list-item
v-for="item in items"
:key="item.unidade"
:to="item.link"
:#click="change(item.method)"
>{{item.unidade}}</v-list-item>
</v-navigation-drawer>
</v-content>
</v-app>
</template>
<script>
export default {
name: "App",
data: () => ({
items: [
{ unidade: "IPE", link: "/ipe", method: "IPE" },
{ unidade: "DCSI", link: "/dcsi", method: "DCSI" },
{ unidade: "RT", link: "/rt", method: "RT" }
],
selectedMethod: '', // Initially blank
drawer: false
}),
methods: {
change(val) {
this.selectedMethod = val; // Update the value of selectedMethod
}
}
};
</script>
<style lang="stylus" scoped>
.x {
position: absolute;
}
</style>
Hope this helps!

How to share data between two elements in Polymer 2.0

I have the following elements, which simply has an attribute, which the element itself sets:
<link rel="import" href="../polymer/polymer-element.html">
<dom-module id="test-element-1">
<template>
<style>
:host {
display: block;
}
</style>
<h2>Hello [[prop1]]!</h2>
</template>
<script>
/**
* `test-element-1`
* Test Element 1
*
* #customElement
* #polymer
* #demo demo/index.html
*/
class TestElement1 extends Polymer.Element {
static get is() { return 'test-element-1'; }
static get properties() {
return {
prop1: {
type: String,
value: 'test-element-1',
notify: true,
readOnly: true
}
};
}
}
window.customElements.define(TestElement1.is, TestElement1);
</script>
</dom-module>
and I'd like a second element to be able to use the same data:
<link rel="import" href="../polymer/polymer-element.html">
<dom-module id="test-element-2">
<template>
<style>
:host {
display: block;
}
</style>
<h2>Hello [[prop1]]!</h2>
</template>
<script>
/**
* `test-element-2`
* Test Element 2
*
* #customElement
* #polymer
* #demo demo/index.html
*/
class TestElement2 extends Polymer.Element {
static get is() { return 'test-element-2'; }
static get properties() {
return {
prop1: {
type: String,
notify: false,
readOnly: false
}
};
}
}
window.customElements.define(TestElement2.is, TestElement2);
</script>
</dom-module>
I'd like Test Element 2 to be able to get the value of prop1 from Test Element 1:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>test-element-2 demo</title>
<script src="../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../iron-demo-helpers/demo-pages-shared-styles.html">
<link rel="import" href="../../iron-demo-helpers/demo-snippet.html">
<link rel="import" href="../test-element-2.html">
<link rel="import" href="../../test-element-1/test-element-1.html">
<custom-style>
<style is="custom-style" include="demo-pages-shared-styles">
</style>
</custom-style>
</head>
<body>
<div class="vertical-section-container centered">
<h3>Basic test-element-2 demo</h3>
<demo-snippet>
<template>
<test-element-1 prop1="{{prop1Value}}"></test-element-1>
<test-element-2 prop1="{{prop1Value}}"></test-element-2>
</template>
</demo-snippet>
</div>
</body>
</html>
Here's the output of my demo though:
What is it that I'm doing wrong?
Since you are using index.html or any other HTML page to display the value of polymer elements, it cannot bind the value of prop1Value.
If you do same thing using a polymer-element then it will definitely work.
A property is declared implicitly if you add it to a data binding or
add it as a dependency of an observer, computed property, or computed
binding.
Polymer automatically creates setters for these implicitly declared
properties. However, implicitly declared properties can't be
configured from markup.
The issue is explained by #Osifara, if you want share property with children components you need to make the top level parent a webcomponent,
you need to do something like this:
<body>
......the other code
<dom-module id="my-index">
<template>
<demo-snippet>
<template>
<test-element-1 prop1="{{propIndexTest}}"></test-element-1>
<test-element-2 prop1="{{propIndexTest}}"></test-element-2>
</template>
</demo-snippet>
</template>
</dom-module>
<my-index></my-index>
......the other code
</body>
<script>
class MyIndex extends Polymer.Element {
static get is() {
return 'my-index';
}
static get properties() {
return {
propIndexTest: {
type: String,
value: 'We are sharing the same value'
notify: false
}
};
}
}
window.customElements.define(MyIndex.is, MyIndex);
</script>
So now becames your top level parent container and then you can use the Polymer to work with that.

How to configure neon cascaded animation with firebase and iron-list

I have a main-app element in which I have a questions-list element. Firebase query in main-app fetches an array and passes it to questions-list. I want all of the individual paper-cards to have cascaded slide-up-animation and fade-in-animation
main-app.html
<firebase-query
id="query"
path="/Questions"
order-by-child="QTimestamp"
data="{{questions}}">
</firebase-query>
<neon-animated-pages role="main" selected="[[data.page]]" attr-for-selected="name">
<questions-list name="Questions" questions="{{questions}}"></questions-list>
</neon-animated-pages>
questions-list.html
<iron-list hidden$="[[!questions]]" items="[[questions]]" as="question" scroll-target="Questions-container" id="questionslist">
<template>
<div>
<paper-card animated elevation=1 id="qcard">
<div id="cuicontainer_qcard" class="cuicontainer">
<paper-avatar id="cui" label="[[question.Username]]" src="[[question.photoURL]]"></paper-avatar>
<template is="dom-if" if="{{!mobileview}}" restamp="true">
<paper-tooltip id="vcardforqcard" for="cui" position="right" marginTop="14">
<profile-vcard name="[[question.Username]]" image="[[question.photoURL]]" description="[[ud.description]]" stars="[[ud.Stars]]"></profile-vcard>
</paper-tooltip>
</template>
</div>
<p id="q" class="q">
<iron-selector selected="{{data.page}}"
attr-for-selected="name">
<template is="dom-if" if="{{mobileview}}" restamp="true">
<a name="Question" id="qlink" style="text-decoration: none;color: black ;" href="#/Question/[[question.qis]]/[[question.$key]]">[[question.qis]]</a>
</template>
<template is="dom-if" if="{{!mobileview}}" restamp="true">
<a name="Question" id="qlink" style="text-decoration: none;color: rgb(111, 111, 111);" href="#/Question/[[question.qis]]/[[question.$key]]">[[question.qis]]</a>
</template>
</iron-selector>
</p>
</paper-card>
</div>
</template>
</iron-list>
<script>
(function() {
'use strict';
Polymer({
is: 'questions-list',
behaviors: [
Polymer.IronResizableBehavior,
Polymer.NeonSharedElementAnimatableBehavior,
Polymer.NeonAnimationRunnerBehavior
],
properties: {
animationConfig: {
value: function() {
return {
'entry': [{
name: 'fade-in-animation',
node: this,
// timing: {delay: 200}
}, {
name: 'cascaded-animation',
animation: 'fade-in-animation',
timing: {delay: 5000},
}],
'exit': [{
name: 'slide-left-animation',
node: this
}, {
name: 'fade-out-animation',
node: this
}]
}
}
},
questions: {
type: Object,
notify: true,
reflectToAttribute: true,
},
},
ready: function() {
this.async(function() {
var nodeList = document.querySelectorAll('#qcard');
// console.log(nodeList);
this.animationConfig['entry'][0].nodes = Array.prototype.slice.call(nodeList);
});
},
listeners: {
'neon-animation-finish': '_onNeonAnimationFinish'
},
show: function() {
this.opened = true;
this.style.display = 'block';
this.playAnimation('entry');
},
hide: function() {
this.opened = false;
this.playAnimation('exit');
},
_onNeonAnimationFinish: function() {
if (!this.opened) {
this.style.display = 'none';
}
},
});
})();
imports in questions-list
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/q-card/q-card.html">
<link rel="import" href="../../bower_components/neon-animation/neon-shared-element-animatable-behavior.html">
<link rel="import" href="../../bower_components/neon-animation/animations/slide-from-left-animation.html">
<link rel="import" href="../../bower_components/neon-animation/animations/slide-from-bottom-animation.html">
<link rel="import" href="../../bower_components/neon-animation/animations/slide-left-animation.html">
<link rel="import" href="../../bower_components/neon-animation/animations/slide-up-animation.html">
<link rel="import" href="../../bower_components/neon-animation/animations/slide-down-animation.html">
<link rel="import" href="../../bower_components/neon-animation/animations/cascaded-animation.html">
<link rel="import" href="../../bower_components/neon-animation/animations/transform-animation.html">
<link rel="import" href="../../bower_components/neon-animation/animations/hero-animation.html">
<link rel="import" href="../../bower_components/neon-animation/neon-animatable-behavior.html">
<link rel="import" href="../../bower_components/neon-animation/web-animations.html">
<link rel="import" href="../../bower_components/neon-animation/neon-animation-runner-behavior.html">
<link rel="import" href="../../bower_components/iron-list/iron-list.html">
<link rel="import" href="../../bower_components/iron-resizable-behavior/iron-resizable-behavior.html">
<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout.html">
<link rel="stylesheet" href="../../styles/main.css">
<link rel="import" href="../../styles/shared-styles.html">
Right now it gives this error:
error
What should i do to make the paper-cards run cascaded-animation when data loads
You need to import web-animations polyfill as well. It will be in the same folder as of neon-animation.
<link rel="import" href="../../neon-animation/web-animations.html">

Flick from a textarea to a ui-codemirror frame

I want to make an editor of files. By the following code (JSBin), we list all the file names on the left hand, and their body on the right hand. I use ui-codemirror to style the body of the files.
However, when we click on the file names and switch from one to another, from time to time, we could see the quick flick from a textarea to a codemirror frame, which is not a good experience for an editor.
Does anyone have a solution to avoid the flick? Addtionally, is it a good direction to make an editor by angularJS and ui-codemirror?
<html ng-app="flapperNews">
<head>
<link rel="stylesheet" href="https://codemirror.net/lib/codemirror.css">
<script src="https://code.jquery.com/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.3.2/angular-ui-router.js"></script>
<script src="https://codemirror.net/lib/codemirror.js"></script>
<script src="https://codemirror.net/mode/xml/xml.js"></script>
<script src="https://codemirror.net/mode/htmlmixed/htmlmixed.js"></script>
<script src="https://codemirror.net/mode/javascript/javascript.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.11.0/ui-bootstrap-tpls.js"></script>
<script>
var app = angular.module('flapperNews', ['ui', 'ui.router']);
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('file', {
template: '<textarea ui-codemirror="editorOptionsHTML" ng-model="file.body"></textarea>',
controller: 'FileCtrl',
params: { name: null }
})
}]);
app.controller('MainCtrl', ['$scope', '$state', function ($scope, $state) {
$scope.files = [
{ name: "index.html", body: "<html><body>index.html</body></html>" },
{ name: "index.js", body: "the body of index.js" },
{ name: "test.html", body: "the body of test.html" }];
}]);
app.controller('FileCtrl', ['$scope', '$stateParams', function ($scope, $stateParams) {
$scope.file = $scope.files.find(function (file) { return file.name === $stateParams.name });
$scope.editorOptionsHTML = { mode: 'text/html', lineNumbers: true, matchBrackets: true };
}]);
</script>
</head>
<body ng-controller="MainCtrl">
<div class="row">
<div class="col-sm-3 col-md-3 col-xl-3 col-lg-3">
<div ng-repeat="file in files track by $index">
<a ui-sref="file({name: file.name})">{{file.name}}</a>
</div>
</div>
<div class="col-sm-9 col-md-9 col-xl-9 col-lg-9">
<ui-view></ui-view>
</div>
</div>
</body>
</html>
Every time you click on the link, it is creating new editor, which is an expense operation.
The best approach is to create one editor and load the content on every click.
so I removed the ui-router linking (ui-sref) and related controllers
<script>
var app = angular.module('flapperNews', ['ui', 'ui.router']);
app.controller('MainCtrl', ['$scope', '$state', function ($scope, $state) {
$scope.files = [
{ name: "index.html", body: "<html><body>index.html</body></html>" },
{ name: "index.js", body: "the body of index.js" },
{ name: "test.html", body: "the body of test.html" }];
$scope.editorOptionsHTML = { mode: 'text/html', lineNumbers: true, matchBrackets: true };
// for every click event it will load the content
$scope.go=function(file){
$scope.file=file;
}
}]);
and the body
<body ng-controller="MainCtrl">
<div class="row">
<div class="col-sm-3 col-md-3 col-xl-3 col-lg-3">
<div ng-repeat="file in files track by $index">
<a ng-click="go(file)">{{file.name}}</a>
</div>
</div>
<div class="col-sm-9 col-md-9 col-xl-9 col-lg-9">
<textarea ui-codemirror="editorOptionsHTML" ng-model="file.body"></textarea>
</div>
</div>
</body>
Example : JSBin
you could avoid the flick by adding hide class to textarea like the following code ( jsbin )
template: '<textarea class="hide" ui-codemirror="editorOptionsHTML" ng-model="file.body"></textarea>',

Polymer dom-repeat with firebase children

I have a firebase structure like this:
"workouts" : {
"-KBJiXAHZV2B8sj9-FNk" : {
"exercise_templates" : {
"-KEsVT-ukSHMNevLFCa1" : true,
"-KEslP747OVTdIYrNmCv" : true,
"-KEslUBaSsmTOiVgV_h_" : true,
"-KFpJyXrZwY_aPiDGGvw" : true
},
"name" : "ICF A"
}
}
"exercise_templates" : {
"-KEsVT-ukSHMNevLFCa1" : {
"name" : "Overhead Press",
"reps" : "5",
"sets" : "5",
"workouts" : {
"-KBJiXAHZV2B8sj9-FNk" : true,
"-KEsU4Q0irvgh33I20JF" : true
}
},
I'm trying to loop through all exercise_templates that belong to a workout:
<template is="dom-repeat" items="[[workouts]]" as="workout">
<paper-card heading="[[workout.name]]">
<div class="card-content">
[[workout.__firebaseKey__]]
<template is="dom-repeat" items="[[workout.exercise_templates]]" as="template" handle-as="json">
{{template}}
</template>
</div>
<div class="card-actions">
<paper-button>Some action</paper-button>
</div>
</paper-card>
</template>
But I get:
[dom-repeat::dom-repeat]: expected array for `items`, found Object {-KEsVT-ukSHMNevLFCa1: true, -KEslP747OVTdIYrNmCv: true, -KEslUBaSsmTOiVgV_h_: true, -KFpJyXrZwY_aPiDGGvw: true}
How would I go about referencing an exercise_template by iterating through the exercise_templates in workouts?
Custom element
<dom-module id="my-workout">
<template>
<template is="dom-bind">
<style>
:host {
display: block;
}
span {
#apply(--paper-font-body1);
}
</style>
<firebase-collection
limit-to-first="10"
location="https://blazing-inferno-5257.firebaseio.com/workouts"
data="{{workouts}}" keys="{{keys}}"></firebase-collection>
<template is="dom-repeat" items="[[workouts]]" as="workout">
<paper-card heading="[[workout.name]]">
<div class="card-content">
<template is="dom-repeat" items="[[_computeExerciseTemplates(workout)]]">
<li>{{item}}</li>
</template>
</div>
<div class="card-actions">
<paper-button>Do workout</paper-button>
</div>
</paper-card>
</template>
</template>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'my-workout',
_computeExerciseTemplates: function(workout) {
return Object.keys(workout.exercise_templates);
},
ready: function() {
}
});
})();
</script>
</dom-module>
Assuming exercise_templates cannot be changed into a simple array (or that it's undesirable), you could use a computed binding for exercise_templates to get its keys:
<template is="dom-repeat" items="[[_computeExerciseTemplates(workout)]]">
<li>{{item}}</li>
</template>
where _computeExerciseTemplates is defined as:
<script>
Polymer({
...
_computeExerciseTemplates: function(workout) {
return Object.keys(workout.exercise_templates);
}
});
</script>
<head>
<base href="https://polygit.org/polymer+:master/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>
<template is="dom-repeat" items="[[workouts]]" as="workout">
<h2>{{workout.name}}</h2>
<ul>
<template is="dom-repeat" items="[[_computeExerciseTemplates(workout)]]">
<li>{{item}}</li>
</template>
</ul>
</template>
</template>
<script>
Polymer({
is: 'x-foo',
properties: {
workouts: {
type: Array,
value: function() {
return [{
//"-KBJiXAHZV2B8sj9-FNk" : {
"exercise_templates": {
"-KEsVT-ukSHMNevLFCa1": true,
"-KEslP747OVTdIYrNmCv": true,
"-KEslUBaSsmTOiVgV_h_": true,
"-KFpJyXrZwY_aPiDGGvw": true
},
"name": "ICF A"
//}
}]
}
}
},
_computeExerciseTemplates: function(workout) {
return Object.keys(workout.exercise_templates);
}
});
</script>
</dom-module>
</body>
jsbin

Resources