Binding sub property of array element in Polymer - arrays

I have simple iron-selector and dom-repeat. When I change sub property of selected item dom-repeat is not refreshed, but item of array is updated.
<dom-module id="my-element">
<template>
<iron-selector selected="{{selected}}" attr-for-selected="v">
<template is="dom-repeat" items="{{array}}">
<div v="{{item}}">{{item.summary}}</div>
</template>
</iron-selector>
<paper-input label="summary" value="{{selected.summary}}"></paper-input>
</template>
</dom-module>
Polymer({
is: "my-element",
properties: {
array: {
type: Array,
value: () => {
return [{summary: '1'}, {summary: '2'}, {summary: '3'}]
}
}
},
});
jsfiddle
I can not understand how bind sub-property of selected element and dom-repeat item.

array-selector should solve your binding issues.
<dom-module id="employee-list">
<template>
<h2> Employee list: </h2>
<template is="dom-repeat" id="employeeList" items="{{employees}}">
<div>
<button on-click="toggleSelection">Select</button>
<span>{{item.first}} {{item.last}} </span>
</div>
</template>
<array-selector id="selector" items="{{employees}}" selected="{{selected}}">
</array-selector>
<h3> Change {{selected.last}} first name: </h3>
<paper-input value="{{selected.first}}"></paper-input>
</template>
</dom-module>
<script>
Polymer({
is: 'employee-list',
ready: function() {
this.employees = [
{first: 'Bob', last: 'Smith'},
{first: 'Sally', last: 'Johnson'},
];
},
toggleSelection: function(e) {
var item = this.$.employeeList.itemForElement(e.target);
this.$.selector.select(item);
}
});
</script>
https://jsfiddle.net/aek6py7s/

Related

Simple List Rendering in Vue with finding Index and passing props

so I do the beginning todo list stuff. I have this array
state() {
return {
news: [
{
id: 1,
title: "Titel 1",
text: "Beispieltext 1"
},
{
id: 2,
title: "Titel 2",
text: "Beispieltext 2"
}
]
}
},
I want to list items (NewsItem) in a list (NewsItemList) for every entry in the news-array. As simple as that.
This is my NewsItem
<template>
<div class="newsitem">
<h1
v-for="nachricht in news"
:key="nachricht.id"
>{{nachricht.title}}</h1>
<p
v-for="nachricht in news"
:key="nachricht.id"
>{{nachricht.text}}</p>
</div>
</template>
<script>
export default {
data() {
return {};
}
};
</script>
And this is my NewsItemList
<template>
<ul>
<li
v-for="nachricht in news"
:key="nachricht.id"
>
<NewsItem />
</li>
</ul>
</template>
<script>
import NewsItem from "#/components/NewsItem";
export default {
components: {
NewsItem,
},
computed: {
news() {
return this.$store.getters.getNewsList;
}
}
};
</script>
I want to map through my array in NewsItemList and give the information as props to my NewsItem. What is the simplest way?
In React, I needed to find the index with the findIndex() function. How can I do this in vue?
As I am just beginning, could you help me to find a simple way? Thank you!
Props
NewsItem
<template>
<div class="newsitem">
<h1>{{ title }}</h1>
<p>{{ text }}</p>
</div>
</template>
<script>
export default {
props: {
title: String,
text: String,
},
data() {
return {}
},
}
</script>
Now you can use that in your NewsItemList
<template>
<ul>
<li v-for="nachricht in news" :key="nachricht.id">
<NewsItem :title="nachricht.title" :text="nachricht.text"/>
</li>
</ul>
</template>
If I understood you correctly, you just need to pass prop with news item object :
Vue.component('NewsItem', {
template: `
<div class="newsitem">
<h1 >{{item.title}}</h1>
<p>{{item.text}}</p>
</div>
`,
props: ['item']
})
new Vue({
el: "#demo",
data() {
return {
news: [
{
id: 1,
title: "Titel 1",
text: "Beispieltext 1"
},
{
id: 2,
title: "Titel 2",
text: "Beispieltext 2"
}
]
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<ul>
<li
v-for="nachricht in news"
:key="nachricht.id"
>
<news-item :item="nachricht"></news-item>
</li>
</ul>
</div>

Toggle visibility in angular

I’m trying to toggle the visibility of a div in angular
This is my view:
<div ng-repeat="item in data | orderBy:'address' | groupBy:'address'" >
<h5 onclick="toggle_visibility('item1');">{{item.address}}</h5>
<div id="item1">
<ul>
<li>First Name: {{item.firstname}} </li>
</ul>
</div>
In controller:
$scope.data = [
{
firstname: "user",
address: “address1”
},
{
firstname: "user1",
address: “address2”
},
{
firstname: "user2",
address: “address1”
}
];
The issue is when I click on any address header it hides or shows the first name within the first header and it should be when I click on the address header it shows or hides the first name within that address header that I clicked on it. How can I fix this?
Html
<div ng-repeat="item in data | orderBy:'address' | groupBy:'address'" >
<h5 ng-click="toggle_visibility(item);">{{item.address}}</h5>
<div ng-hide="item.invisible">
<ul>
<li>First Name: {{item.firstname}} </li>
</ul>
</div>
JS
$scope.toggle_visibility = function(item) {
item.invisible = !item.invisible
}
add additional boolean property to array and assign that property to ng-hide. then inside click function change the boolean value of that property.
angular.module("app",[])
.controller("ctrl",function($scope){
$scope.data = [
{
firstname: "user",
address: "address1",
hideAddress : false
},
{
firstname: "user1",
address: "address2",
hideAddress : false
},
{
firstname: "user2",
address: "address3",
hideAddress : false
}
];
$scope.toggle_visibility = function(item){
item.hideAddress = !item.hideAddress;
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div ng-repeat="item in data | orderBy:'address' " >
<h5 ng-click="toggle_visibility(item);">{{item.address}}</h5>
<div ng-hide="item.hideAddress">
<ul>
<li>First Name: {{item.firstname}} </li>
</ul>
</div>
</div>
</div>

polymer 1.0 dom-repeat render not updating dom

I have an element which renders a array of objects using dom-repeat. There is also a feature to modify a particular object. However, after updating the object, even if the array is updated, the dom does not reflect the change (render is called as well). I am using Polymer provided mutation function (this.slice, have a look in the _commitChange function below). Can someone point out what is wrong here?
One thing to note is that individual items in the array are objects rather than primitives /
<dom-module id="em-person-qualifications">
<template>
<style include="shared-styles"></style>
<style>
:host {
display: block;
}
iron-pages {
height: 100%;
}
</style>
<iron-pages id="container" selected="0">
<div id="listContainer">
<template id="listTemplate" is="dom-repeat" items="[[data]]">
<paper-item>
<paper-item-body two-line>
<div>
<span>[[item.qualificationLevel.level]]</span>,
<span>[[item.qualificationLevel.levelCategory]]</span>
</div>
<div secondary>
<span>[[item.fromInstitute.edumateOrgDisplayName]]</span>
<span>[[item.status]]</span>
</div>
</paper-item-body>
<paper-icon-button icon="more-vert" on-tap="_loadModifyUI"></paper-icon-button>
</paper-item>
</template>
</div>
<div id="detailContainer">
<paper-toolbar>
<div class="title">
<us-com-i18n key="qualificationDetail"></us-com-i18n>
</div>
<paper-icon-button id="button" icon="clear" on-click="_showList"></paper-icon-button>
</paper-toolbar>
<div>
<iron-label for="levelContainer">
<us-com-i18n key="level"></us-com-i18n>
</iron-label>
<div id="levelContainer" class="layout horizontal wrap">
<us-com-freeorref data="{{currItem.qualificationLevel.level}}"></us-com-freeorref>
<paper-dropdown-menu label="select">
<paper-menu class="dropdown-content" attrForSelected="data-value" selected="{{currItem.qualificationLevel.levelCategory}}">
<paper-item data-value="10">Graduate</paper-item>
<paper-item data-value="11">Post-graduate</paper-item>
</paper-menu>
</paper-dropdown-menu>
</div>
<iron-label for="majorSubjectContainer">
<us-com-i18n key="majors"></us-com-i18n>
</iron-label>
<div id="majorSubjectContainer">
<template is="dom-repeat" items="[[currItem.majorSubjects]]">
<em-party-subject data="{{item}}"></em-party-subject>
</template>
</div>
<iron-label for="minorSubjectContainer">
<us-com-i18n key="minors"></us-com-i18n>
</iron-label>
<div id="minorSubjectContainer">
<template is="dom-repeat" items="[[currItem.minorSubjects]]">
<em-party-subject data="{{item}}"></em-party-subject>
</template>
</div>
<iron-label for="during">
<us-com-i18n key="during"></us-com-i18n>
</iron-label>
<div class="layout horizontal wrap" id="during">
<span><us-com-i18n key="from"></span>
<us-com-date data="{{currItem.fromMonthYear}}"></us-com-date>
<span><us-com-i18n key="to"></span>
<us-com-date data="{{currItem.toMonthYear}}"></us-com-date>
</div>
<paper-input type="text" value="{{currItem.status}}" label="status">
<us-com-formattedlongtext data="{{currItem.achievmentStatement}}" label="achievements"></us-com-formattedlongtext>
<div class="buttons">
<paper-button tabindex="0" raised autofocus on-click="_commitChange">
<iron-icon icon="check"></iron-icon>Ok
</paper-button>
</div>
</div>
</div>
</iron-pages>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'em-person-qualifications',
properties: {
data: {
type: Array,
value: function() {
return [{
'qualificationLevel': {
'level': 'Bsc',
'levelCategory': 10
},
'majorSubjects': [],
'minorSubjects': [],
'fromMonthYear': {},
'toMonthYear': {},
'fromInstitute': {
'edumateOrgDisplayName': 'XYZ College'
},
'status': 'ABCD',
'achievmentStatement': {}
}, {
'qualificationLevel': {
'level': 'M-Tech',
'levelCategory': 11
},
'majorSubjects': [],
'minorSubjects': [],
'fromMonthYear': {},
'toMonthYear': {},
'fromInstitute': {
'edumateOrgDisplayName': 'ABC College'
},
'status': 'EFGH',
'achievmentStatement': {}
}];
}
},
currItem: Object,
currIndex: Number,
currChange: String
},
behaviors: [app.I18nBehavior],
_showList: function() {
this._commitChange();
//this.$.container.selected = '0';
},
_loadAddUI: function() {
this.currChange = 'A';
this.currItem = {};
this.$.container.selected = '1';
},
_loadModifyUI: function(e) {
this.currChange = 'M';
this.currItem = e.model.item;
this.currIndex = this.data.indexOf(e.model.item);
this.$.container.selected = '1';
},
_loadDeleteUI: function() {
this.currChange = 'D';
//Find out the index from the data-index attribute of the button
this.currIndex = 0;
},
_commitChange: function() {
//var model = e.model;
//this.splice('data',)
if (this.currChange === 'A') {
this.push('data', this.currItem);
} else if (this.currChange === 'M') {
this.splice('data', this.currIndex, 1, this.currItem);
console.log('data[this.currIndex] = ' + this.data[this.currIndex].status);
} else if (this.currChange === 'D') {
this.splice('data', this.currIndex, 1);
}
this.$.listTemplate.render();
this.$.container.selected = '0';
}
});
})();
</script>
</dom-module>
Strings as I see in most examples.
I'm on a huge Polymer 1.9 project, and is currently switching to Polymer 2.0, and I can tell that things like updating a dom-repeat is handled in a better way in Polymer 2.
Updating subproperties in a dom-repeat is a nightmare in 1.0, and the simplest solution is to override dirty checking, where Polymer is checking every single property to see if the DOM needs to update.
var copiedData = JSON.parse( JSON.stringify(data) );
this.set('data', []);
this.set('data', copiedData);
If you're desperate, use this method, but it can create a performance issue on phones or if the list is over about 100 items. You could probably also just do ...
this.set('data', JSON.parse( JSON.stringify(data) ));
... as a totally new array will override dirty checking. Anyways, a dom-repeat wont update with this.splice if the length of the array doesn't change, so I would do something like this.set('data.' + this.currIndex, this.currItem) to update the subproperty directly instead of the splicing that occurs here:
_commitChange: function() {
// ...
} else if (this.currChange === 'M') {
this.splice('data', this.currIndex, 1, this.currItem);
Also, you don't need this as Polymer should update automatically:
this.$.listTemplate.render();

Data-binding is not updating ng-repeat section

I'm new to angularjs and I have a data binding issue.
After each repeated line, I want to show a number of mutually exclusive options. These would be images using a background-image, and when the option is selected I add the css class 'selected' to the span containing the image.
I've simplified it below to show options A, B and C and to make the currently selected one bold. By clicking on A, B or C you change the option.
The inner ng-repeat section is not evaluated again (it seems) when the model changes, but as the "Debugging category" shows, the outer ng-repeat is re-evaluated and the "Debugging category" shows correctly that the category has been changed.
I'm clearly missing something about angularjs; how can I get the inner ng-repeat to re-evaluate to that the correct style is applied to the A, B and C options?
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
var testApp = angular.module('testApp', []);
testApp.controller('NamesCtrl', function ($scope) {
$scope.categories = [ 'A', 'B', 'C' ];
$scope.names = [
{ name: 'John', category: 'C' },
{ name: 'Cindy', category: 'A' },
{ name: 'Patrick', category: 'B' }
];
$scope.changeCategory = function(name, category) {
name.category = category;
};
});
</script>
<body ng-app="testApp">
<h1>Names</h1>
<div ng-controller="NamesCtrl">
<ul>
<li ng-repeat="name in names">
<span>{{name.name}}</span>
<span ng-repeat="category in categories" ng-init="selected = (category == name.category)">
<span ng-style="{'true': {'font-weight': 'bold'}, false: {}}[selected]" ng-click="changeCategory(name, category)">{{category}}</span>
</span>
<em>(Debugging category: {{name.category}})</em>
</li>
</ul>
</div>
</body>
ng-init expression is not watched and updated automatically whenever related scope property value changes, you could just solve this by removing ng-init and using the expression directly
<span ng-style="{'true': {'font-weight': 'bold'}}[category == name.category]"
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
var testApp = angular.module('testApp', []);
testApp.controller('NamesCtrl', function ($scope) {
$scope.categories = [ 'A', 'B', 'C' ];
$scope.names = [
{ name: 'John', category: 'C' },
{ name: 'Cindy', category: 'A' },
{ name: 'Patrick', category: 'B' }
];
$scope.changeCategory = function(name, category) {
name.category = category;
};
});
</script>
<body ng-app="testApp">
<h1>Names</h1>
<div ng-controller="NamesCtrl">
<ul>
<li ng-repeat="name in names">
<span>{{name.name}}</span>
<span ng-repeat="category in categories" >
<span ng-style="{'true': {'font-weight': 'bold'}}[category == name.category]" ng-click="changeCategory(name, category)">{{category}}</span>
</span>
<em>(Debugging category: {{name.category}})</em>
</li>
</ul>
</div>
</body>

AngularJS: Count Filtered Items

I am showing subsets of a list if a checkbox is checked. I would like to replace the X next to the checkbox with the count of the list matching the selection criteria. I have a plunker that does everything but count the subset here.
My Controller looks like this:
var app = angular.module('app', []);
app.controller('MainController', function($scope){
$scope.cbMarvel = true;
$scope.cbDCComics = true;
$scope.heroes = [
{
id: 1,
name: 'Iron Man',
fname: 'Tony',
lname: 'Stark',
location: 'Stark Tower',
comic: 'Marvel'
},
{
id: 2,
name: 'Batman',
fname: 'Bruce',
lname: 'Wayne',
location: 'Bat Cave',
comic: 'DC'
},
{
id: 3,
name: 'Superman',
fname: 'Clark',
lname: 'Kent',
location: 'Metroplis',
comic: 'DC'
},
{
id: 1,
name: 'Daredevil',
fname: 'Jack',
lname: 'Murdock',
location: 'Court Room',
comic: 'Marvel'
},
{
id: 5,
name: 'Flash',
fname: 'Barry',
lname: 'Allen',
location: 'Speedline',
comic: 'DC'
},
{
id: 6,
name: 'Hulk',
fname: 'Bruce',
lname: 'Banner',
location: 'Labratory',
comic: 'Marvel'
},
{
id: 7,
name: 'Hawkeye',
fname: 'Clint',
lname: 'Barton',
location: 'Nest',
comic: 'Marvel'
},
{
id: 8,
name: 'Thor',
fname: 'Donald',
lname: 'Blake',
location: 'Asgard',
comic: 'Marvel'
}
];
});
And my view looks like this:
<!DOCTYPE html>
<html ng-app="app">
<head>
<link data-require="bootstrap#*" data-semver="3.2.0" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.css" />
<link data-require="bootstrap-css#*" data-semver="3.2.0" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" />
<script data-require="bootstrap#*" data-semver="3.2.0" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.js"></script>
<script data-require="jquery#2.0.3 current" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script data-require="angular.js#1.2.20" data-semver="1.2.20" src="https://code.angularjs.org/1.2.20/angular.js"></script>
<script data-require="angular-ui-bootstrap#*" data-semver="0.11.0" src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.0.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="MainController">
<fieldset>
<legend>Comments Log</legend>
<div class="row">
<div class="col-md-4">
<input type="checkbox" ng-model="cbMarvel"/> Marvel [X]
</div>
<div class="col-md-4"> </div>
<div class="col-md-4">
<input type="checkbox" ng-model="cbDCComics"/> DC Comics [X]
</div>
</div>
<div class="row"> </div>
<div class="row col-md-10">
<div ng-if="heroes.length == 0"><b>No Heroes Found!</b>
</div>
<div ng-repeat="h in heroes | filter:{comic:'Marvel'}" ng-show="cbMarvel">
{{ h.name}} - {{h.comic}}
</div>
<div ng-repeat="h in heroes | filter:{comic:'DC'}" ng-show="cbDCComics">
{{ h.name}} - {{h.comic}}
</div>
</div>
</fieldset>
</body>
</html>
Assuming your list of people is in data variable and you filter people using query model, the following code will work for you:
Number of visible people: {{(data|filter:query).length}}
Total number of people: {{data.length}}
summary
{{data.length}} - prints total number of people
{{(data|filter:query).length}} - prints filtered number of people
You could set that count in the view model itself while binding the data or just have a method on the scope that returns the count.
app.controller('MainController', function($scope, filterFilter){
....
$scope.getCount = function(strCat){
return filterFilter( $scope.heroes, {comic:strCat}).length;
}
...
});
and use it as:-
Marvel [{{getCount("Marvel")}}]
.....
DC Comics [{{getCount("DC")}}]
Plnkr
If the list is non changing when you are on the page i would suggest finding out the length and binding it to a property in the view model itself, and use it in the view.
//Set your data model
$scope.cbMarvel = {value:true, count:getCount('Marvel')};
$scope.cbDCComics = {value:true, count:getCount('DC')};
and in your view
<input type="checkbox" ng-model="cbMarvel.value"/> Marvel [{{cbMarvel.count}}]
Plnkr2
If your dataset is huge, instead of using filter inside the getCount, use a forEach and populate the count for each type at once.
Infact you do not need a filter at all, it seems inefficient to iterate through the same list using a filter in your case. Your's is a static list so categorize it in the controller itself.
var comics = $scope.comics = {}; //Dictionary of comics
//Create the collection here.
angular.forEach(heroes, function(itm){
if(!comics[itm.comic]){
comics[itm.comic] = {name:itm.comic, value:true, count:1, items:[itm] };
return;
}
comics[itm.comic].count++; //Incr count
comics[itm.comic].items.push(itm); //push specific item
});
and remove all the filters in your view and do:-
<div ng-repeat="h in comics.Marvel.items" ng-show="comics.Marvel.value">
{{ h.name}} - {{h.comic}}
</div>
<div ng-repeat="h in comics.DC.items" ng-show="comics.DC.value">
{{ h.name}} - {{h.comic}}
</div>
Plnk3 - the better one
Possible solution 1: Inline
You could actually save a reference to the filtered results in a variable: h in filtered.marvel = (heroes | filter:{comic:'Marvel'}), which you could use like so: filtered.marvel.length.
See: Plunkr
Possible solution 2: In the controller
You could also move this code to your controller:
$scope.filteredHeroes.marvel = $filter('filter')($scope.heroes, {comic:'Marvel'});
, which you could use by ng-repeat="hero in filteredHeroes.marvel"
and {{filteredHeroes.marvel.length}}
(Don't forget to add $filter as a controller dependency)
See: Plunkr
To find the count objects, I use <scope_obj>.length in the .html template.
Here's my controller:
conciergeControllers.controller('GuestMsgPreviewCtrl', ['$scope', 'GuestMessages',
function($scope, GuestMessages) {
$scope.guests = GuestMessages.query();
}]);
And template (each guest object has a messages attribute that is an array object, so .length returns the number of nested message objects:
<ul ng-repeat="guest in guests">
<li>[[ guest.messages.length ]]</li>
</ul>

Resources