Polymer iron-list not updating view on dynamic add - javascript

I am using Polymer 1.7.0 and Angular2 to build a app. I created a custom Polymer element to wrap the iron-list template in order to be able to use it with Angular2 but i have a problem when it comes to dynamicly adding items to the iron-list.
The add function modifies the items array but it doesnt render the new element even if i am triggering the iron-resize event after modifying the items array.
If i delete a item first, and then try to add a element, then it gets rendered.
This is the polymer element i use:
<!--.... dependencies imports .... -->
<dom-module id="role-users-list">
<template>
<style is="custom-style" include="iron-flex iron-flex-alignment custom-layout-classes">
:host {
display: block;
}
</style>
<iron-media-query query="(min-width: 600px)" on-query-matches-changed="queryValueChanged" query-matches="{{wide}}"></iron-media-query>
<iron-list items="{{items}}" class="test" style="height:85%">
<template>
<paper-card class="verticalJustified">
<div class="horizontalJustified">
<div class="card-content horizontalJustified">
<paper-icon-button class="cardIcons" icon="group-work"></paper-icon-button>
<div class="verticalStart">
<span class="cardTitle">{{item.fullName}}</span>
<template is="dom-if" if="{{item.direct}}">
<span class="cardSubTitle">User associated directly </span>
</template>
<template is="dom-if" if="{{!item.direct}}">
<span class="cardSubTitle">Role granted through {{item.groupName}} </span>
</template>
</div>
</div>
<template is="dom-if" if="{{item.direct}}">
<div class="horizontalJustified">
<paper-icon-button class="cardIcons" icon="delete" target-user="{{item.username}}" on-tap="onDelete" (click)="deleteUser(user)"></paper-icon-button>
</div>
</template>
</div>
</paper-card>
</template>
</iron-list>
</template>
<script>
Polymer({
is: 'role-users-list',
onDelete: function(e){
this.fire("deleteTrigger",{data:e.model.item});
},
queryValueChanged: function(e){
this.fire("mediaQueryTrigger",{data: e.detail.value})
},
updateIronList:function(){
this._nodes.filter(function(value){return value.localName === "iron-list"})[0].fire("iron-resize");
},
focusElem:function(){
var test = this._nodes.filter(function(value){return value.localName === "iron-list"})[0];
test.selectItem(this.items[0]);
},
properties: {
items: {
type: Array,
notify: true,
value:[],
}
}
});
</script>
</dom-module>
After adding or removing a element i call the updateIronList function to trigger the rendering with the iron-resize event.
PS: The add functionality is in the parent Angular2 component where i modify the items array.
The only difference i could notice was that since the delete button is inside the polymer custom element, it causes it to focus that item when its clicked.
If i didnt make myself understood, ask and i will clarify.

I fixed my issue by updating the updateIronList function to:
updateIronList:function(){
let ironListElem = this._nodes.filter(function(value){return value.localName === "iron-list"})[0];
ironListElem.fire("iron-resize");
ironListElem._virtualCount = (this.items.length <= 20) ? this.items.length : 20;
},
It seems the issue was caused by the fact that when the items array length changed, iron-list didnt automaticly update the number of items displayed.

Related

Vue mouseover not causing changes to the data

I am trying to attach a v-mouseover directive to a bootstrap Vue element b-list-group-item as shown below.
<b-row>
<b-col cols="3">
<b-list-group>
<b-list-group-item :active="register"
#click="switchRegister" button
#mouseover="isRegisterHover = true"
#mouseleave="isRegisterHover = false"
class="border-0 bg-transparent register"> Register </b-list-group-item>
</b-list-group>
</b-col>
<b-col cols="9">
<div id="action-screen-canvas-register v-if="isRegisterHover"> </div>
</b-col>
</b-row>
The variable isRegisterHover is tied to the boolean value in the data which determines whether or not the div will be shown.
export default {
name: 'Home',
components: {
Navi
},
data() {
return {
isRegisterHover: false,
// ...
}
},
// ...
}
Thing is that the action-canvas-register div remains hidden when I hover the item, Vue devtool also shows that the data remains unchanged when I mouseover them. How do I make the isRegisterHover value change when I mouse-over the item?
You could also do this with pure CSS if you wanted to. You could do something like:
<template>
<div id="target">Hover this</div>
<div>...</div> // This will be hidden when #target is hovered
</template>
<script>
...
</script>
<style>
#target:hover + div {
visibility: hidden; // Hides the element like v-show
or
display: none; // Hides the element like v-if
}
</style>
Your code should work. See this:
I would recommend using v-show instead of v-if. See this.
<script>
export default {
data() {
return {
isRegisterHover: false
}
}
}
</script>
<template>
<div #mouseover="isRegisterHover = true"
#mouseleave="isRegisterHover = false">Hover this</div>
<div v-show="isRegisterHover">This will show/hide (v-show)</div>
<div v-if="isRegisterHover">This will show/hide (v-if)</div>
</template>

Data Driven CSS Grid Vue Component

I want to create a Grid component that accepts the number of columns from the user, accepts data and renders all it's children into consecutive cells.
Something like this.
<Grid :cells="12" :columns="6">
<div>Child1 Cell1</div>
<div>Child2 Cell2</div>
<div>Child3 Cell3</div>
<div>Child4 Cell4</div>
<div>Child5 Cell5</div>
<div>Child6 Cell6</div>
</Grid>
In the Grid.vue component in the template, this is what I expect to do.
<div class="nugget-grid-item" v-for="cell of cells" :key="cell">
{cell}
</div>
This will render something like this on the UI.
The dashed border on each cell is due to the nugget-grid-item CSS class, but CSS is not relevant here, so let's ignore that.
What I am not able to figure out is how do I get this Grid component to display the following.
Isn't there something like this.children from React in Vue?
What you need are slots. See docs here. As you'll see slots allow a parent component to pass DOM elements into a child component. A basic look at them could go like this:
//ChildComponent.vue
<template>
<div>
<p>I'm the child component!</p>
<!-- Content from the parent gets rendered here. -->
<slot></slot>
</div>
</template>
And then you inject content into the slot tags like this:
//ParentComponent.vue
<template>
<div>
<child-component>
<p>I'm injected content from the parent!</p>
<p>I can still bind to data in the parent's scope, like this! {{myVariable}}</p>
</child-component>
</div>
</template>
Slots can get pretty complex and do a lot of things so are well worth looking into.
Further to your below comment, you can put a v-for in the grid. This outputs what you seem to be after. I've put an input in to accept the users number of columns as you said and it then renders that number of cells. You can of course use multiple slots and named slots and scoped slots but I'll leave it up to you how you expand on this.
//Grid.vue
<template>
<div class="cell">
<slot></slot>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
.cell {
height: 40px;
width: 60px;
border: 1px solid gray;
}
</style>
and parent:
<template>
<div class="content">
<label>Enter number of columns</label>
<input v-model.number="col" type="number">
<Grid v-for="(n, i) in col" :key="i" >
<div>Child{{n}} Cell{{n}}</div>
</Grid>
</div>
</template>
<script>
import Grid from '#/components/admin/Grid'
export default {
layout: 'admin',
components: {
Grid
},
data: () => ({
col: 4
}),
}
</script>

Nested Iron Ajax

Ok. So my last post was too ambiguous. For my second post, let me try to approach the same problem in hopefully a little more straighforward manner. Below is the code. Here is a screenshot of the results I get. Regarding the second iron-ajax call, if I use curl in terminal with this () I get what I want (it's a link preview service, so title, img, desc etc). Trying to accomplish the same with iron-ajax post with required parameters defined per spec. I don't get any console errors (for the first time) and based on the [object.Object] result I get when I output the last-response variable in the body of second dom-repeat, appears to be returning a json object just like the first iron-ajax call (which does work, includes the link but not enough data about it, hence running link through second service that returns the data I want to display).
Result from running code locally
CODE:
<dom-module id="my-new-view">
<template>
<!-- Defines the element's style and local DOM -->
<style>
:host {
display: block;
padding: 16px;
}
</style>
<iron-ajax auto
url="https://api.rss2json.com/v1/api.json?rss_url=http://feeds.feedburner.com/DrudgeReportFeed"
params="{"fmt":"xml-rss"}"
handle-as="json"
last-response="{{ajaxResponse}}"></iron-ajax>
<p>First: {{ajaxResponse}}</p>
<template is="dom-repeat" items="[[ajaxResponse.items]]" as="item" index-as="item_no">
<p>{{item.title}}</p>
<iron-ajax auto
method="post"
url="https://guteurls.de/api/"
params="{"u":"{{item.guid}}", "r":"https://127.0.0.1", "e":"s652imb8et42xd0bd", "t":"json"}"
handle-as="json"
last-response="{{newAjaxResponse}}"></iron-ajax>
<p>Second: {{newAjaxResponse}}</p>
<template is="dom-repeat" items="[[newAjaxResponse.newItems]]" as="newItem" index-as="newItem_no">
<p>{{newItem.title}}</p>
<paper-card heading="{{newItem.title}}" image="{{newItem.image.url}}" alt="{{newItem.title}}">
<div class="card-content">
<h1>Description: {{newItem.desc}}</h1>
<p>Test</p>
</div>
<div class="card-actions">{{newItem.title}}
<paper-button>Share</paper-button>
<paper-button>Explore!</paper-button>
</div>
</paper-card>
</template>
</template>
</template>
<script>
class MyNewView extends Polymer.Element {
static get is() { return 'my-new-view'; }
}
customElements.define(MyNewView.is, MyNewView);
</script>
</dom-module>
Problems and Solutions:
params="{"fmt":"xml-rss"}"
Quoting not done properly. You can you single quote as well like
params='{"fmt":"xml-rss"}' or params="{'fmt':'xml-rss'}"
First: {{ajaxResponse}} and Second: {{newAjaxResponse}}
You can use console to debug since you cannot display object like that
params="{"u":"{{item.guid}}", "r":"https://127.0.0.1", "e":"s652imb8et42xd0bd", "t":"json"}"
Quoting not done properly.
Attribute binding i.e. {{item.guid}} must be followed by $.
Change to params$='{"u":"{{item.guid}}", "r":"https://127.0.0.1", "e":"s652imb8et42xd0bd", "t":"json"}'
newAjaxResponse.newItems
There is no newItems in newAjaxResponse. Just use newAjaxResponse
[Note: newAjaxResponse is returned as Object which must be converted to Array since dom-repeat works only with Array.]
Before you define fields like desc, image.url make sure it exists.
Working code:
<dom-module id="my-new-view">
<template>
<!-- Defines the element's style and local DOM -->
<style>
:host {
display: block;
padding: 16px;
}
</style>
<iron-ajax auto url="https://api.rss2json.com/v1/api.json?rss_url=http://feeds.feedburner.com/DrudgeReportFeed" params='{"fmt":"xml-rss"}' handle-as="json" last-response="{{ajaxResponse}}"></iron-ajax>
<p>First: {{ajaxResponse}}</p>
<template is="dom-repeat" items="[[ajaxResponse.items]]" as="item" index-as="item_no">
<p>{{item.title}}</p>
<iron-ajax auto method="post" url="https://guteurls.de/api/" params$='{"u":"{{item.guid}}", "r":"https://127.0.0.1", "e":"s652imb8et42xd0bd", "t":"json"}' handle-as="json" last-response="{{newAjaxResponse}}"></iron-ajax>
<p>Second: {{newAjaxResponse}}</p>
<template is="dom-repeat" items="[[_toArray(newAjaxResponse)]]" as="newItem" index-as="newItem_no">
<paper-card heading="{{newItem.title}}" image="{{newItem.img}}" alt="{{newItem.title}}">
<div class="card-content">
<h1>Description: {{newItem.description}}</h1>
<p>Test</p>
</div>
<div class="card-actions">{{newItem.title}}
<paper-button>Share</paper-button>
<paper-button>Explore!</paper-button>
</div>
</paper-card>
</template>
</template>
</template>
<script>
class MyNewView extends Polymer.Element {
static get is() { return 'my-new-view'; }
_toArray(obj) {
var tempArray = [];
tempArray.push(obj);
//console.log(tempArray);
return tempArray;
}
}
customElements.define(MyNewView.is, MyNewView);
</script>
</dom-module>
You can check the working demo here.

How to change polymer 1.0 paper-button color dynamically from an array of colors?

I have an array buttonColors, which has set of colors in the hex format.
Now I want to display set of paper-button each with the color present in the buttonColors Array. How to achieve it in polymer 1.0?
<template is="dom-repeat" items="{{buttonColors}}">
<paper-button style="background-color:{{item}}" >
<b>click me</b>
</paper-button>
</template>
The above snippet does not seem to work. Kindly help.
You need to create a function and call it in following way
<template is="dom-repeat" items="{{buttonColors}}">
<paper-button style="{{computeStyle(item)}}" >
<b>click me</b>
</paper-button>
</template>
<script>
computedStyle:function(cl)
{
var s= "background-color:"+cl;
return s;
}
</script>
ebidel's comment is excellent as always. (He is one of the Google geniuses responsible building Polymer BTW)
1.0 doesn't support expressions in {{}} bindings. You'll need to make it a computed property: style="{{_computeStyle(item)}}" ... Documentation
Below, I have written out some example code for you to follow.
Example Code
<dom-module id="x-element">
<template is="dom-repeat" items="{{buttonColors}}">
<paper-button style$="{{_computeStyle}}"> <b>click me</b> </paper-button>
</template> ...
<script>
(function() {
Polymer({
is: "x-element",
properties: {
value: {
type: Number,
notify: true
}
},
_computeStyle: function(item) {
// Compute style here
return "background-color:red";
}
});
})()
</script>
</dom-module>

How to use Sessions and Templates to show/hide elements?

I have 7 question I want to ask individually one per time on the page. I have every of them in single template and I know I should use Session.set - Session.get to show/hide them one each time. But I really don't know how and can't find an good example of how to do it. This is how I structured layout template with 7 questions inserted.
<template name="layout">
{{#if first}}
{{> submitProblem}}
{{/if}}
{{#if second}}
{{> submitWhy}}
{{/if}}
...
</template>
First needs to be displayed when the page opens, and then I tried to do this with Sessions, but have no idea...
Meteor.startup(function () {
Session.set("first", true);
});
Template.layout.events({
'click .btn1':function() {
Session.set("first", false);
Session.set("second", true);
}
});
It seems to me that using jQuery to do show/hide with setting display:block/none is easier to do ?
If you want to hide items on page, you should define and use .hide or .hidden class in CSS. Showing/hiding items using jQuery show/hide is considered bad practice. You should render all hidden templates wrapped with .hidden class. The first template you want to show will not have hidden class.
CSS:
.hidden {
display: none;
}
Templates:
<template name="layout">
{{> submitProblem}}
{{> submitWhy}}
...
</template>
<template name="submitProblem">
<div id="submit-problem">
...
</div>
</template>
<template name="submitWhy">
<div id="submit-why" class="hidden">
...
</div>
</template>
JS:
Template.layout.events({
'click .btn1':function() {
//show/hide items with removeClass()/addClass()
$('#submit-problem').addClass('hidden');
$('#submit-why').removeClass('hidden');
}
});
Use template helpers to interpret whether first, second, etc are true or false.
Template.layout.helpers({
first: function() {
return Session.get('first');
},
second: function() {
return Session.get('second');
}
});

Categories

Resources