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();
Related
Javascript:
// List of Products
const productsJSON = 'json/products.json';
// Component - Product Select
app.component('product-select', {
data() {
return {
selected: '',
options: []
}
},
template: `
<p v-for="(option, index) in options">test</p>
<div class="ui fluid labeled multiple search selection dropdown">
<input type="hidden"
name="products"
v-model="selected"
#change="selectProducts">
<i class="dropdown icon"></i>
<div class="default text">Select Products</div>
<div class="menu">
<div v-for="(option, index) in options"
class="item"
v-bind:data-value="option.name">
{{ option.name }}
</div>
</div>
</div>
`,
methods: {
selectProducts(event) {
this.selected = event.target.value.split(',');
console.log(this.selected);
}
},
beforeMount() {
const jsonResults = [];
this.options = jsonResults;
$.getJSON(productsJSON, function (data) {
jsonResults.push(...data);
});
console.log(jsonResults);
console.log(this.options)
}
});
I'm simply trying to populate the options: [] array with the array of objects returned from the JSON file in the $.getJSON function. Here is what the JSON file looks like:
[
{
"name": "White Gummy",
"value": "White Gummy"
},
{
"name": "Red Gummy",
"value": "Red Gummy"
},
{
"name": "Blue Gummy",
"value": "Blue Gummy"
}
]
My v-for is returning absolutely nothing, and the results of my two console.log functions are as follows:
Does anyone have any idea on what I'm doing wrong or if there is a better way to populate my array with the external .json file?
#Luckyfella provided a solution, which can be found in the created() lifecycle hook below:
// Component - Product Select
app.component('product-select', {
data() {
return {
selected: '',
options: null
}
},
template: `
<div class="ui fluid labeled multiple search selection dropdown">
<input type="hidden"
name="products"
v-model="selected"
#change="selectProducts">
<i class="dropdown icon"></i>
<div class="default text">Select Products</div>
<div class="menu">
<div v-for="(option, index) in options"
class="item"
v-bind:data-value="option.name">
{{ option.name }}
</div>
</div>
</div>
`,
methods: {
selectProducts(event) {
this.selected = event.target.value.split(',');
console.log(this.selected);
}
},
created: function () {
fetch(productsJSON)
.then(r => r.json())
.then(options => {
this.options = options;
});
}
});
I'm currently working on a private chatroom.
i started two browser from which one in incognito
when i send a message from browser window one i console.log the chats array but it's an vue observer object.
when i send a message from browser window 2 i console.log the same chats array, but it tels me it's an array
the first browser window gives an error this.chats.push is not a function
the second browser window accepts the function and saves the message and adds it to the chats array.
Can anybody explain to me why it is doing that?
Just curious
<template>
<div class="card card-default chat-box">
<div class="card-header">
<b :class="{'text-danger':isBlocked}">
{{friend.first_name}}
<span v-if="isBlocked">(Blocked)</span>
</b>
<!-- Close button -->
<a href="" #click.prevent="close">
<font-awesome-icon icon="times" class="float-right"/>
</a>
<!-- Close button ends here -->
<!-- Options -->
<div class="dropdown float-right">
<b-dropdown variant="link" no-caret toggle-class="text-decoration-none">
<template v-slot:button-content>
<font-awesome-icon icon="ellipsis-v"/>
</template>
<b-dropdown-item href="" #click.prevent="block" v-if="!isBlocked">Block</b-dropdown-item>
<b-dropdown-item href="" #click.prevent="unblock" v-if="isBlocked">Unblock</b-dropdown-item>
<b-dropdown-item href="" #click.prevent="clear">Clear</b-dropdown-item>
<b-dropdown-divider></b-dropdown-divider>
<b-dropdown-item active>Active action</b-dropdown-item>
<b-dropdown-item disabled>Disabled action</b-dropdown-item>
</b-dropdown>
</div>
<!-- Options Ends -->
</div>
<div class="card-body" v-chat-scroll>
<p class="card-text" :class="{'text-right': chat.type === false}" v-for="chat in chats" :key="chat.id">
{{chat.message}}
</p>
</div>
<form class="card-footer" #submit.prevent="send">
<div class="form-group">
<input type="text" class="form-control" placeholder="Write your message"
:disabled="isBlocked" v-model="message"
>
</div>
</form>
</div>
</template>
<script>
import {faIcons} from "#fortawesome/free-solid-svg-icons";
import {DropDownButtonPlugin} from "#syncfusion/ej2-vue-splitbuttons";
Vue.use(DropDownButtonPlugin);
export default {
props: ['friend'],
computed: {
queen() {
return faIcons;
}
},
data() {
return {
chats: [],
message: null,
isBlocked: false
}
},
methods: {
send() {
if (this.message) {
this.pushToChats(this.message);
axios.post('/dashboard/chats/messages/store', {
session_id: this.friend.session.id,
message: this.message,
to_user_id: this.friend.id
});
this.message = null;
}
},
pushToChats(message) {
// this.getAllMessages();
console.log(this.chats);
console.log(typeof this.chats);
this.chats.push({message: message, type: false, send_at: 'Just Now'});
},
close() {
this.$emit('close');
},
clear() {
this.chats = [];
},
block() {
this.isBlocked = true
},
unblock() {
this.isBlocked = false;
},
getAllMessages() {
axios.post(`/dashboard/chats/session/${this.friend.session.id}`)
.then(res => this.chats = res.data
);
}
},
created() {
this.getAllMessages();
Echo.private(`Chat.${this.friend.session.id}`).listen('PrivateChatEvent', (e) => {
this.chats.push({message: e.content, type: true, send_at: 'Just Now'})
});
},
name: "MessageComponent"
}
</script>
<style scoped>
.chat-box {
height: 400px;
}
.card-body {
overflow-y: scroll
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
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/
Here I am using angular.js to show a list of people
<div class="recipient" ng-repeat="person in people">
<img src="{{person.img}}" /> person.name
<div class="email">person.email</div>
</div>
$scope.people = [{id:1}, {id:2}, {id:3}, {id:4}];
The looks is like below
What I want to do is I can select multiple items and by click a OK button, I can get a list of selected items. so If I select id 1 and id 2, then I want to get return a list of [{id:1},{id:2}]
How could I implement it in angular.js
Well I guess that if you're looping through a collection of people using a ng-repeat, you could add the ng-click directive on each item to toggle a property of you're object, let's say selected.
Then on the click on your OK button, you can filter all the people that have the selected property set to true.
Here's the code snippet of the implementation :
<div class="recipient" ng-repeat="person in people" ng-click="selectPeople(person)">
<img src="{{person.img}}" /> person.name
<div class="email">person.email</div>
</div>
<button ng-click="result()">OK</button>
function demo($scope) {
$scope.ui = {};
$scope.people = [{
name: 'Janis',
selected: false
}, {
name: 'Danyl',
selected: false
}, {
name: 'tymeJV',
selected: false
}];
$scope.selectPeople = function(people) {
people.selected = !people.selected;
};
$scope.result = function() {
$scope.ui.result = [];
angular.forEach($scope.people, function(value) {
if (value.selected) {
$scope.ui.result.push(value);
}
});
};
}
.recipient {
cursor: pointer;
}
.select {
color:green;
}
.recipient:hover {
background-color:blue;
}
<script src="https://code.angularjs.org/1.2.25/angular.js"></script>
<div ng-app ng-controller="demo">
<div class="recipient" ng-repeat="person in people" ng-click="selectPeople(person)" ng-class="{ select: person.selected }">
<div class="name">{{ person.name }}</div>
</div>
<button ng-click="result()">OK</button>
Result :
<ul>
<li ng-repeat="item in ui.result">{{ item.name }}</li>
</ul>
</div>
If you only want to show checked or unchecked you could just apply a filter, but you would need to toggle the filter value from undefined to true if you didn't wan't to get stuck not being able to show all again.
HTML:
<button ng-click="filterChecked()">Filter checked: {{ checked }}</button>
<div class="recipient" ng-repeat="person in people | filter:checked">
<input type='checkbox' ng-model="person.isChecked" />
<img ng-src="{{person.img}}" />{{ person.name }}
<div class="email">{{ person.email }}</div>
</div>
Controller:
// Apply a filter that shows either checked or all
$scope.filterChecked = function () {
// if set to true or false it will show checked or not checked
// you would need a reset filter button or something to get all again
$scope.checked = ($scope.checked) ? undefined : true;
}
If you want to get all that have been checked and submit as form data you could simply loop through the array:
Controller:
// Get a list of who is checked or not
$scope.getChecked = function () {
var peopleChkd = [];
for (var i = 0, l = $scope.people.length; i < l; i++) {
if ($scope.people[i].isChecked) {
peopleChkd.push(angular.copy($scope.people[i]));
// Remove the 'isChecked' so we don't have any DB conflicts
delete peopleChkd[i].isChecked;
}
}
// Do whatever with those checked
// while leaving the initial array alone
console.log('peopleChkd', peopleChkd);
};
Check out my fiddle here
Notice that person.isChecked is only added in the HTML.
I am using angular-ui's sortable-ui module and am trying to raise a cancel so that the dragged items returns to it original location in the source list. Unfortunately I cannot get this working. Here is an example:
http://jsfiddle.net/Ej99f/1/
var myapp = angular.module('myapp', ['ui.sortable']);
myapp.controller('controller', function ($scope) {
$scope.list = ["1", "2", "3", "4", "5", "6"];
$scope.list2 = ["7", "8", "9"];
$scope.sortableOptions = {
update: function(e, ui) {
if (Number(ui.item.text()) === 6) {
ui.item.parent().sortable('cancel');
}
},
receive: function(e, ui) {
ui.sender.sortable('cancel');
ui.item.parent().sortable('cancel');
},
connectWith: ".group",
axis: 'y'
};
});
angular.bootstrap(document, ['myapp']);
Any help would be gratefully appreciated.
well, when it comes to angular, all roads lead to the data "the single source of truth". So update your model back to it's original state, before the move, and you're all set :)
example below has two lists, the first one being restricted for
its sorting (the update method)
and for sending an item (receive method on list 2)
the second list you can sort, and send items to list 1
(using foundation4 for css)
<div ng-app="test">
<div ng-controller="sortableTest">
<div class="small-4 columns panel">
<ul data-drop="true"
ui-sortable="sortable.options.list1" ng-model="sortable.model.list1">
<li ng-repeat="fruit in sortable.model.list1"
data-id="{{ fruit.id }}">{{ fruit.label }}</li>
</ul>
</div>
<div class="small-4 columns panel">
<ul data-drop="true"
ui-sortable="sortable.options.list2" ng-model="sortable.model.list2">
<li ng-repeat="element in sortable.model.list2"
data-id="{{ element.id }}">{{ element.label }}</li>
</ul>
</div>
<div class="clear"></div>
<br />
<span ng-repeat="fruit in sortable.model.list1">{{ fruit.label }} </span><br />
<span ng-repeat="element in sortable.model.list2">{{ element.label }} </span><br />
<span ng-repeat="fruit in sortable.oldData.list1">{{ fruit.label }} </span><br />
<span ng-repeat="element in sortable.oldData.list2">{{ element.label }} </span><br />
</div>
</div>
js:
var test = angular.module('test', ['ui.sortable']);
test.controller('sortableTest', function($scope, $timeout) {
$scope.sortable = {
model: {
list1: [{id: 1, label: 'apple'},{id: 2, label: 'orange'},{id: 3, label: 'pear'},{id: 4, label: 'banana'}],
list2: [{id: 5, label: 'earth'},{id: 6, label: 'wind'},{id: 7, label: 'fire'},{id: 8, label: 'water'}]
},
oldData: {
list1: [],
list2: []
},
options: {
list1: {
update: function(event, ui) {
console.debug('up-list1');
$scope.sortable.oldData.list1 = $scope.sortable.model.list1.slice(0);
$scope.sortable.oldData.list2 = $scope.sortable.model.list2.slice(0);
// DO NOT USE THIS! it messes up the data.
// ui.item.parent().sortable('cancel'); // <--- BUGGY!
// uncomment and check the span repeats..
$timeout(function(){
$scope.sortable.model.list1 = $scope.sortable.oldData.list1;
$scope.sortable.model.list2 = $scope.sortable.oldData.list2;
});
},
connectWith: 'ul'
},
list2: {
update: function(event, ui) {
console.debug('up-list2');
},
connectWith: 'ul',
receive: function(event, ui) {
console.debug('re-list2');
$timeout(function(){
$scope.sortable.model.list1 = $scope.sortable.oldData.list1;
$scope.sortable.model.list2 = $scope.sortable.oldData.list2;
});
}
}
}
};
});
you can of course use a service or something to store the old value. One can use ui.sender to differentiate the senders, if you have more that two..