polymer submenus and items from json - json

I'm trying to generate Polymer submenus and items from json. The code is just nested submenu & item templates:
<polymer-element name="years-submenu" noscript>
<template>
<core-ajax auto url="../json/years and offices.json" response="{{items}}" handleAs="json"></core-ajax>
<template repeat="{{item in items}}">
<core-submenu icon="visibility" label="{{item.year}}">
<template repeat="{{office in item.offices}}">
<core-item id="{{item.year}} {{office}}" label="{{item.year}} {{office}}"></core-item>
</template>
</core-submenu>
</template>
</template>
</polymer-element>
The json data could be structured any old way, but currently it looks like this:
[
{
"offices": [
"Mayor",
"Council At-Large"
],
"year": "2014"
},
{
"offices": [
"Council Chairman",
"Council At-Large",
"Council Ward 2",
"Council Ward 4"
],
"year": "2012"
}
]
This renders the years, but not the office names. Oddly, if I inspect the elements in chrome, I can see that it is inserting the {{item.year}} and {{office}} for the id but not for the label. I've tried various ways of explicitly binding, but to no avail; of course, I may have been trying the wrong ways. Any ideas on how to make this work would be very much appreciated.

It turns out that if I put wrap my submenu code inside within my custom element (rather than calling the submenu from the main page that wraps my custom element inside , it works.
So, calling the element posted in the question as follows does not work:
<core-menu selected="{{selected}}" valueattr="id" theme="core-light-theme">
<years-submenu></years-submenu>
</core-menu>
But the following called from the main page does work:
<polymer-element name="years-menu" noscript>
<template>
<core-menu selected="{{selected}}" valueattr="id" theme="core-light-theme">
<core-ajax auto url="../json/years and offices.json" response="{{items}}" handleAs="json"></core-ajax>
<template repeat="{{item in items}}">
<core-submenu icon="visibility" label="{{item.year}}">
<template repeat="{{office in item.offices}}">
<core-item id="{{item.year}} {{office}}" label="{{item.year}} {{office}}"></core-item>
</template>
</core-submenu>
</template>
</core-menu>
</template>
</polymer-element>
I don't fully understand why, but problem solved.

I created a polymer element to create a menu from JSON. It has some other cool features, and I am going to continue to enhance it.
It doesnt exactly fit your question but check it out.
Create a github issue/pull request if you have any issues/ideas
https://github.com/sup3rb0wlz/nb-menu

Related

nesting elements and dynamic content inside dom-repeat - Polymer 1.0

I have a <parent> element, a <tabs> element inside it with an arbitrary number of tabs (purely for hiding/showing logic in the UI), and a <child> element inside each <tab>. Right now I have the following working:
<!-- inside parent.html: -->
<tabs></tabs>
<!-- inside tabs.html: -->
<template is="dom-repeat" items="{{tabs}}" as="tab" index-as="item_no">
<section>
<child id="element-{{tab.index}}"></child>
</section>
</template>
Only <parent> knows how many instances of <child> there needs to be (<tabs> merely receives an array from <parent> and iterates over it.
Is there a way to not hard-code <child> inside the local DOM of <tabs>? Thinking of using <content> and light DOM but no idea where to even start. Would it be a promising route to take?
Desired state:
<!-- inside parent.html: -->
<tabs>
// somehow specify base content to be rendered in each tab
</tabs>
<!-- inside tabs.html: -->
<template is="dom-repeat" items="{{tabs}}" as="tab" index-as="item_no">
<section>
// somehow inject stuff from parent element, perhaps via <content>?
</section>
</template>
This is my interpretation of your question, so I am not really sure if it will be OK with you. If I misunderstood you, please drop a comment and I will gladly update my answer.
I have come up with a simple element composition, requiring no dom-repeat or manual template stamping. The solution consists of two custom elements, namely my-parent and my-child.
The definitions for both custom elements are the following:
<dom-module id="my-parent">
<template>
<tabs>
<content></content>
</tabs>
</template>
<script>
Polymer({
is: 'my-parent',
});
</script>
</dom-module>
<dom-module id="my-child">
<template>
<section>
<content></content>
</section>
</template>
<script>
Polymer({
is: 'my-child',
});
</script>
</dom-module>
And the proposed usage of them is the following:
<my-parent>
<my-child>First tab</my-child>
<my-child>Second tab</my-child>
<my-child>Third tab</my-child>
</my-parent>
Online demo: http://jsbin.com/hibuzafapu/1/edit?html,output
The resulting computed HTML code looks something like this:
<my-parent>
<tabs>
<my-child>
<section>
First tab
</section>
</my-child>
<my-child>
<section>
Second tab
</section>
</my-child>
<my-child>
<section>
Third tab
</section>
</my-child>
</tabs>
</my-parent>
If I understood you correctly, then only the <my-child> tag wrapping the <section> tag is redundant. Currently the aforementioned tag does nothing and is just a block-level element that wraps everything (just like a div). If this bothers you, then you can actually omit the <section> tag and put all the styling directly on the <my-child> tag.
In this case, the resulting computed HTML would look something like this:
<my-parent>
<tabs>
<my-child>
First tab
</my-child>
<my-child>
Second tab
</my-child>
<my-child>
Third tab
</my-child>
</tabs>
</my-parent>
UPDATE
In order to add some dynamics to the solution (adding/removing tabs), you have two options: use dom-repeat and stamp the items in light DOM, or push the items array into the my-parent element and use dom-repeat there. Both options are very similar to implement and don't have much difference in the way they work.
Option A: stamping in light DOM:
Definitions for both custom elements remain unchanged, the only difference is how you use them. Instead of hardcoding the light DOM, you make it more dynamic.
<dom-module is="tmp-element">
<template>
<my-parent>
<template is="dom-repeat" items="[[myItems]]">
<my-child>[[item.content]]</my-child>
</template>
</my-parent>
</template>
<script>
Polymer({
is: 'tmp-element',
ready: function() {
this.myItems = [
{ content: "First tab" },
{ content: "Second tab" },
{ content: "Third tab" },
],
};
});
</script>
</dom-module>
<tmp-element></tmp-element>
The tmp-element is used purely to create a binding scope and to feed the data into the dom-repeat.
Live demo: http://jsbin.com/gafisuwege/1/edit?html,console,outputenter link description here
Option B: stamping inside parent:
In this option, the parent needs to have an additional property, in which will we will supply the array of items.
The new version of the my-parent element is the following:
<dom-module id="my-parent">
<template>
<tabs>
<template is="dom-repeat" items="[[items]]">
<my-child>[[item.content]]</my-child>
</template>
</tabs>
</template>
<script>
Polymer({
is: 'my-parent',
properties: {
items: Array,
},
});
</script>
</dom-module>
And the usage is:
<dom-module is="tmp-element">
<template>
<my-parent items="[[myItems]]"></my-parent>
</template>
<script>
Polymer({
is: 'tmp-element',
ready: function() {
this.myItems = [
{ content: "First tab" },
{ content: "Second tab" },
{ content: "Third tab" },
];
},
});
</script>
</dom-module>
<tmp-element></tmp-element>
Here, I have also used a tmp-element (a different one than before) to feed the my-parent its' data.
Live demo: http://jsbin.com/kiwidaqeki/1/edit?html,console,output

Polymer feed value into second dom-repeat

I'm trying to use a dom-repeat inside a dom-repeat over a json array inside a json array. How can pass a value from the first array over to the second dom-repeat?
To illustrate, I have the following json array which loads fine:
"books": [
{
"title": "book1",
"slug": "b1",
"authors": ["author1","author2"],
"blurb": "Some text"
},
{
"title": "book2",
"slug": "b2",
"authors": ["author2","author3"],
"blurb": "some more text"
}]
I'm iterating over the array 'books' and retrieve title and blurb. Then underneath, I want both authors of each book listed and I also want a link to each one in the format of /slug/author (f.i. /b1/author1). But because in the second dom-repeat I redefine "items", the 'slug' is no longer available. How do I go about this?
<template>
<iron-ajax
auto
url="somelist.json"
handle-as="json"
last-response="{{someList}}"></iron-ajax>
<template is="dom-repeat" items="{{someList.books}}">
<paper-collapse-group>
<paper-collapse-item header$="{{item.title}}">
<p>{{item.blurb}}</p>
</paper-collapse-item>
<template is="dom-repeat" items="{{item.authors}}">
<a href$="/[[slug]]/[[item]]">{{item}}</a></div>
</template>
</paper-collapse-group>
</template>
</template>
I'm also very new to Polymer so thank you for helping me learn!
Use the as attribute to use a custom name for the item property.
Example:
<template>
<iron-ajax
auto
url="somelist.json"
handle-as="json"
last-response="{{someList}}"></iron-ajax>
<template is="dom-repeat" items="{{someList.books}}" as="book">
<paper-collapse-group>
<paper-collapse-item header$="{{book.title}}">
<p>{{book.blurb}}</p>
</paper-collapse-item>
<template is="dom-repeat" items="{{book.authors}}" as="author">
<a href$="/[[book.slug]]/[[author]]">{{author}}</a></div>
</template>
</paper-collapse-group>
</template>
</template>
So what you can do is to create another element, let's say that would look like:
<dom-module id="authors">
<template>
<template is="dom-repeat" items="[[authors]]">
<a href$="/[[slug]]/[[item]]">[[item]]</a>
</template>
</template>
<script>
Polymer({
is: 'authors',
properties: {
authors: Array,
slug: String
},
});
</script>
</dom-module>
and then inject it into your code like:
<template is="dom-repeat" items="[[someList.books]]">
<paper-collapse-group>
<paper-collapse-item header$="[[item.title]]">
<p>[[item.blurb]]</p>
</paper-collapse-item>
<authors slug="[[item.slug]]" authors="[[item.authors]]"></authors>
</paper-collapse-group>
</template>

app-indexeddb-mirror with Polymerfire

I built a simple page that has multiple tabs. Each tab loads a feed (collection/list) of articles from Firebase and renders cards on the page. Everything works as I wanted until I tried to persist the visited feeds into indexeddb with app-indexeddb-mirror.
Here's what I did:
<dom-module id="my-view1">
<template>
<style include="shared-styles">
</style>
<paper-tabs id="tabs"
attr-for-selected="value"
selected="{{selectedFeed}}"
scrollable>
<template is="dom-repeat"
items="[[feeds]]"
as="feed">
<paper-tab value="[[feed.key]]">[[feed.name]]</paper-tab>
</template>
</paper-tabs>
<firebase-query id="[[selectedFeed]]_feed"
app-name="myfirebaseapp"
path="/myfirebaseappdb/feed/[[selectedFeed]]"
data="{{articles}}">
</firebase-query>
<app-indexeddb-mirror
key="[[selectedFeed]]"
data="[[articles]]"
persisted-data="{{persistedArticles}}">
</app-indexeddb-mirror>
<template is="dom-repeat"
items="[[persistedArticles]]"
as="article">
<paper-card image="[[article.image]]" alt="image">
<div class="card-content">
<h1 class="card-text">[[article.title]]</h1>
<h4 class="card-text">[[article.abstract]]</h4>
</div>
</paper-card>
</template>
</template>
<script>
Polymer({
is: 'my-view1',
ready: function () {
this.feeds = [
{name: "Feed1", key: "feed1"},
{name: "Feed2", key: "feed2"},
{name: "Feed3", key: "feed3"},
{name: "Feed4", key: "feed4"}
];
}
});
</script>
What I want to do is cache each feed into indexeddb as an entry (feed name as key and the data as value), so they can be loaded when the app is offline. That's basically what app-indexeddb-mirror is for, right?
However I cannot get my head around the data flow between firebase-query and app-indexeddb-mirror, and I keep getting indexeddb entry overwritten/emptied when switching tabs.
Is there something I am not doing right? Thanks.
I solved this issue by swapping firebase-query and app-indexeddb-mirror.
I never knew sequence matters in Polymer, also this order is quite counter-intuitive.

Polymer 1.0: Clickable item in dom-repeat to e.g. iron-page which contains further info (contact list)

I'm setting up a contact list in Polymer 1.0. When the user clicks on a name, there should be a (animated) page opened for further details. All of these data elements are pulled from an external .json file.
Two questions for this approach..:
1) where to begin? How do I wrap, for example, an iron-page or neon-animated-page around my current setup (which is searchable, which is also the -temporary- reason it's a dom-repeat instead of an iron-list):
<template id="resultlist" is="dom-repeat" items="{{data}}" filter="contactFilter">
<paper-item>
<paper-item-body two-line>
<div>{{item.name}}</div>
<div secondary>{{item.number}}</div>
</paper-item-body>
</paper-item>
</template>
2) For quick try-out with binding options I've created an paper-dialog (instead of an page behaviour) which displays further data for the chosen person... On top of that paper-dialog should the chosen name being displayed. But I only get the first name of the array in my .json file. How can I setup the code to display the {{item.name}} of the chosen item?
Ps. I'm aware of the contacts-app from Rob Dodson (https://github.com/robdodson/contacts-app), but I can't figure out how it should be done in Polymer 1.0.
Update 27.10.2015
After Hugo's answer I'm not able to get the solution to work in an dom-module structure.
Sorry for misunderstanding, but I can't figure out where I'm wrong.
Having to following:
phonebook.html, which acts like an index
...
<body unresolved>
<template is="dom-bind" id="application">
<neon-animated-pages selected="[[selected]]" entry-animation="fade-in-animation" exit-animation="fade-out-animation">
<contact-list></contact-list>
<contact-details></contact-details>
</neon-animated-pages>
</template>
<script>
var application = document.querySelector('#application');
application.selected = 0;
document.addEventListener('show-details', function() {
application.selected = 1;
});
document.addEventListener('show-list', function() {
application.selected = 0;
});
</script>
</body>
DOM-module contact-list.html, the list it self.
<dom-module id="contact-list">
<template>
<style include="phonebook-styles"></style>
<iron-ajax url="../data/data.json" handle-as="json" last-response="{{data}}" auto></iron-ajax>
<div class="container">
<h3>Contactlist:</h3>
<div class="template-container">
<template is="dom-repeat" id="templateUsers" items="{{data}}">
<paper-item on-tap="showDetails">
<paper-item-body two-line>
<div>{{item.name}}</div>
<div secondary>{{item.phonenumber}}</div>
</paper-item-body>
<div class="item-details-link">
<iron-icon icon="account-circle"></iron-icon>
</div>
</paper-item>
</template>
</div>
</div>
</template>
<script>
Polymer({
is: 'contact-list',
properties: {
selectedContact:{
type:Object,
value:function(){
return null;
}
}
},
showDetails: function(ev) {
var data = this.$.templateUsers.itemForElement(ev.target);
//alert(JSON.stringify(data)) // works with data chosen data selection...
this.selectedContact = data;
this.fire('show-details', this.selectedContact);
}
});
</script>
</dom-module>
DOM-module contact-details.html, the details-list.
<dom-module id="contact-details">
<template>
<!-- Do I need to declare the .json in my details module? -->
<iron-ajax url="../data/data.json" handle-as="json" last-response="{{data}}" auto></iron-ajax>
<paper-icon-button icon="arrow-back" on-tap="showList"></paper-icon-button>
<h3>Contact details</h3>
<template is="dom-repeat" items="{{data}}">
<div>{{selectedContact.name}}</div>
</template>
</template>
<script>
Polymer({
is: 'contact-details',
showList: function() {
this.fire('show-list');
}
});
</script>
</dom-module>
Everything, like the transitions, work. The chosen contact is also displayed in an alertbox (commented out in contact-list.html), but isn't forwarded to the contact-details.html page.
There are multiple steps to implement the solution:
Setup the neon animated pages ( one page would be the contact list, the other page would be the details )
Display the list of contacts ( you already have this one )
Add a "selectedContact" property to your element
Add a tap/click handler to the list items element and inside the handler set the selectedContact. You need to get the contact item from the DOM element clicked. ( Check an example here : http://jsbin.com/lofarabare/6/edit )
You can bind the contact details page elements to the selectedContact properties, e.g {{selectedContact.name}}
Inside the handler also Change the neon animated pages selected property to have it display the animation to the other page.
-- Extra feedback
I checked the way you handle events, feedback below:
Give the elements some id so you can add the event listener directly to them (e.g application.$.myContactList.addEventListener('show-detail',function(ev){...})
The way you fire the event from the contact-list is correct, however you are not reading the event data inside the event listener for the 'show-detail' event. The event listener receives the event as argument "ev". You can get the event data using ev.detail
With the event data (the selected contact) you can update your contact details component. Give it some id like 'details' and just update the 'selectedContact' property. **You need to declare the selectedContact in the details component, right now you don't have it there **

Recommendations for managing multiple instances of the same polymer element in a page?

I have a general question. One of the major benefits of building a new polymer element is that it can be used like a native HTML element in a page. So, depending on the element that you build, it's logical that you would be able to add multiple instances of that element in a page.
Say I build a simple task list polymer element that has multiple views. A simple view that just lists the task names in a list and a detailed view that list the tasks and many other properties of the task in a list.
Then I add the element to my page multiple times. Maybe I want one instance of the element to list tasks related to Home and another to list tasks related to Work. But I want to send a link to someone with the Home task list opened in the simple view and the Work task list opened in detailed view. Or maybe I want the Home task list opened in edit mode and the Work task list opened in view mode.
How would you build the element so that you can change attributes/settings to more then one of these elements on a page?
The beauty of polymer is that you can change your component view by just adding / changing attributes to it.
Create custom tags and provide specific attributes depending on your requirement (HOME / WORK profile), and change your view accordingly.
Example:
Step 1: Create task container
<polymer-element name="task-list" noscript>
<template>
<h3>Tasklist</h3>
<core-menu id="tasks">
<content></content>
</core-menu>
</template>
</polymer-element>
Step2: Create task component
<polymer-element name="add-task" attributes="label detail">
<template>
<div id="task">
<input type="checkbox" id="tick" on-click="{{lineThrough}}" /> {{label}}
<div style="color:#999;margin: 5px 25px;">
{{detail}}
</div>
</div>
</template>
<script>
Polymer('add-task', {
lineThrough: function() {
this.$.task.style.textDecoration = this.$.tick.checked ? 'line-through': 'initial';
}
});
</script>
</polymer-element>
And now using above components, you can create your basic task list:
<task-list>
<add-task label="Learn Polymer" detail="http://www.polymer-project.org/"></add-task>
<add-task label="Build something great" detail="create polymer element"></add-task>
</task-list>
Screenshot
Now, To have control over changing task view (list / detailed / editable). Just add 2 attributes to task-list component. To control child view add-task from parent task-list element, you need to publish properties of your child element.
Your child component should be:
<polymer-element name="add-task" attributes="label detail">
<template>
<div id="task">
<template if="{{isEditable}}">
<input value="{{label}}" />
</template>
<template if="{{!isEditable}}">
<input type="checkbox" id="tick" on-click="{{lineThrough}}" /> {{label}}
</template>
<template if="{{isDetailed}}">
<div style="color:#999;margin: 5px 25px;">
{{detail}}
</div>
</template>
</div>
</template>
<script>
Polymer('add-task', {
publish: {
isDetailed: false,
isEditable: false
},
lineThrough: function() {
this.$.task.style.textDecoration = this.$.tick.checked ? 'line-through': 'initial';
}
});
</script>
</polymer-element>
Parent component with required attributes
<polymer-element name="task-list" attributes="editable detailed">
<template>
<h3>Tasklist</h3>
<core-menu flex id="tasks">
<content></content>
</core-menu>
</template>
<script>
Polymer('task-list', {
editable: false,
detailed: false,
domReady: function() {
var items = this.$.tasks.items;
for(var i = 0; i < items.length; i++) {
items[i].isDetailed = this.detailed;
items[i].isEditable = this.editable;
}
}
});
</script>
</polymer-element>
That's it, now you can control your task view by specifying required attributes to your parent component.
<task-list detailed editable>
<add-task label="Learn Polymer" detail="http://www.polymer-project.org/"></add-task>
<add-task label="Build something great" detail="create polymer element"></add-task>
</task-list>
Screenshots
With detailed and editable attributes
Without detailed and editable attributes