How can I render objects to HTML with saving the previous value? - javascript

I have the following data structure, and I'm trying to render each object individually on click whiteout overwriting the previous value with the current value.
boardCollection =
[
{
id: 1,
dashboardType: "Simple",
fields: [
"Board naspa",
"Cea mai mare mica descriere"
]
},
{
id: 2,
dashboardType: "Simple",
fields: ["Titlu fara idei", "Descriere in speranta ca se va afisa"]
},
{
id: 3,
dashboardType: "Complex",
fields: ["Primu board complex", "descriere dorel", "Hai ca merge cu chiu cu vai"]
},
{
id: 4,
dashboardType: "Complex",
fields: ["Kaufland", " merge si asta ", "aaaaaaaaaaaaaaaaaaaaaaaaaa"]
}
]
in which I am accessing the elements in the following manner ->value/index are defined globally.
display() {
let currentElement = this.boardCollection[this.index]
this.value = currentElement;
if (this.index < this.boardCollection.length - 1) {
this.index++;
} else {
this.index = 0;
}
}
Here is the HTML and the way that i`m trying to render each object.
<div *ngIf="show">
<h1>{{value.dashboardType}}</h1>
<ol *ngFor="let prop of value.fields |keyvalue">
<li>{{prop.value }}</li>
</ol>
</div>
<button (click)="display()">Show</button>
show is set to true in the display method.
What I have achieved so far is to display each object or the properties from them, but each time the button is pressed, the current value will overwrite the previous value, therefore I'm looking for some help into saving the previous value in order to display each object so in the end to have all the objects from the Array rendered to the UI

I would have another array in the TypeScript and keep adding to this array as display is clicked.
boards = [];
...
display(index: number) {
let currentElement = this.boardCollection[this.index]
this.value = currentElement; // might not be needed
this.boards = [...this.boards, ...currentElement]; // append to this.boards immutably so change detection takes effect (this.boards.push won't force change detection)
if (this.index < this.boardCollection.length - 1) {
this.index++;
} else {
this.index = 0;
}
}
...
<div *ngFor="let board of boards>"
<h1>{{board.dashboardType}}</h1>
<ol *ngFor="let prop of board.fields |keyvalue">
<li>{{prop.value }}</li>
</ol>
</div>
<button (click)="display()">Show</button>
Clicking on Show each time should keep on displaying each one one by one.

Related

How to Paginate a Computed Property in Vue

I am creating a Vue app, where a list of jobs will be displayed and this data is coming from a JSON object. In this app I also am adding filtering functionality as well as pagination. So what I have so far is:
<div id="app" v-cloak>
<h2>Location</h2>
<select v-model="selectedLocation" v-on:change="setPages">
<option value="">Any</option>
<option v-for="location in locations" v-bind:value="location" >{{ location }}</option>
</select>
<div v-for="job in jobs">
<a v-bind:href="'/job-details-page/?entity=' + job.id"><h2>{{ job.title }}</h2></a>
<div v-if="job.customText12"><strong>Location:</strong> {{ job.customText12 }}</div>
</div>
<div class="paginationBtns">
<button type="button" v-if="page != 1" v-on:click="page--">Prev</button>
<button type="button" v-for="pageNumber in pages.slice(page-1, page+5)" v-on:click="page = pageNumber"> {{pageNumber}} </button>
<button type="button" v-if="page < pages.length" v-on:click="page++">Next</button>
</div>
<script>
var json = <?php echo getBhQuery('search','JobOrder','isOpen:true','id,title,categories,dateAdded,externalCategoryID,employmentType,customText12', null, 200, '-dateAdded');?>;
json = JSON.parse(json);
var jsonData = json.data;
var app = new Vue({
el: '#app',
data() {
return {
//assigning the jobs JSON data to this variable
jobs: jsonData,
locations: ['Chicago', 'Philly', 'Baltimore'],
//Used to filter based on selected filter options
selectedLocation: '',
page: 1,
perPage: 10,
pages: [],
}
},
methods: {
setPages () {
this.pages = [];
let numberOfPages = Math.ceil(this.jobs.length / this.perPage);
for (let i = 1; i <= numberOfPages; i++) {
this.pages.push(i);
}
},
paginate (jobs) {
let page = this.page;
let perPage = this.perPage;
let from = (page * perPage) - perPage;
let to = (page * perPage);
return jobs.slice(from, to);
},
}
watch: {
jobs () {
this.setPages();
}
},
})
computed: {
filteredJobs: function(){
var filteredList = this.jobs.filter(el=> {
return el.customText12.toUpperCase().match(this.selectedLocation.toUpperCase())
});
return this.paginate(filteredList);
}
}
</script>
So the issue I am running into is that I want the amount of pages to change when the user filters the list using the select input. The list itself changes, but the amount of pages does not, and there ends up being a ton of empty pages once you get past a certain point.
I believe the reason why this is happening is the amount of pages is being set based on the length of the jobs data object. Since that never changes the amount of pages stays the same as well. What I need to happen is once the setPages method is ran it needs to empty the pages data array, then look at the filteredJobs object and find the length of that instead of the base jobs object.
The filteredJobs filtering is a computed property and I am not sure how to grab the length of the object once it has been filtered.
EDIT: Okay so I added this into the setPages method:
let numberOfPages = Math.ceil(this.filteredJobs.length / this.perPage);
instead of
let numberOfPages = Math.ceil(this.jobs.length / this.perPage);
and I found out it is actually grabbing the length of filteredJobs, but since I am running the paginate method on that computed property, it is saying there is only 10 items in the filteredJobs array currently and will only add one pagination page. So grabbing the length of filteredJobs may not be the best route for this. Possibly setting a data variable to equal the filtered jobs object may be better and grab the length of that.

Vue.js 2 - Array change detection

Here's a simplified version of my code :
<template>
/* ----------------------------------------------------------
* Displays a list of templates, #click, select the template
/* ----------------------------------------------------------
<ul>
<li
v-for="form in forms.forms"
#click="selectTemplate(form)"
:key="form.id"
:class="{selected: templateSelected == form}">
<h4>{{ form.name }}</h4>
<p>{{ form.description }}</p>
</li>
</ul>
/* --------------------------------------------------------
* Displays the "Editable fields" of the selected template
/* --------------------------------------------------------
<div class="form-group" v-for="(editableField, index) in editableFields" :key="editableField.id">
<input
type="text"
class="appfield appfield-block data-to-document"
:id="'item_'+index"
:name="editableField.tag"
v-model="editableField.value">
</div>
</template>
<script>
export default {
data: function () {
return {
editableFields: [],
}
},
methods: {
selectTemplate: function (form) {
/* ------------------
* My problem is here
*/ ------------------
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
}
}
}
</script>
Basically I want to update the array EditableFields each time the user clicks on a template. My problem is that Vuejs does not update the display because the detection is not triggered. I've read the documentation here which advise to either $set the array or use Array instance methods only such as splice and push.
The code above (with push) works but the array is never emptied and therefore, "editable fields" keep pilling up, which is not a behavior I desire.
In order to empty the array before filling it again with fresh data, I tried several things with no luck :
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
==> Does not update the display
for (let i = 0; i < form.editable_fields.length; i++) {
this.$set(this.editableFields, i, form.editable_fields[i]);
}
==> Does not update the display
this.editableFields = form.editable_fields;
==> Does not update the display
Something I haven't tried yet is setting a whole new array with the fresh data but I can't understand how I can put that in place since I want the user to be able to click (and change the template selection) more than once.
I banged my head on that problem for a few hours now, I'd appreciate any help.
Thank you in advance :) !
I've got no problem using splice + push. The reactivity should be triggered normally as described in the link you provided.
See my code sample:
new Vue({
el: '#app',
data: function() {
return {
forms: {
forms: [{
id: 'form1',
editable_fields: [{
id: 'form1_field1',
value: 'form1_field1_value'
},
{
id: 'form1_field2',
value: 'form1_field2_value'
}
]
},
{
id: 'form2',
editable_fields: [{
id: 'form2_field1',
value: 'form2_field1_value'
},
{
id: 'form2_field2',
value: 'form2_field2_value'
}
]
}
]
},
editableFields: []
}
},
methods: {
selectTemplate(form) {
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<ul>
<li v-for="form in forms.forms"
#click="selectTemplate(form)"
:key="form.id">
<h4>{{ form.id }}</h4>
</li>
</ul>
<div class="form-group"
v-for="(editableField, index) in editableFields"
:key="editableField.id">
{{ editableField.id }}:
<input type="text" v-model="editableField.value">
</div>
</div>
Problem solved... Another remote part of the code was in fact, causing the problem.
For future reference, this solution is the correct one :
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
Using only Array instance methods is the way to go with Vuejs.

Ng-Repeat, how to test for last ng-show element

I am not trying to add a different class for the last element of an ng-repeat.
I have data to display:
$scope.sizes = [{name:XS,available:true},{name:S,available:true},{name:M,available:false},{name:L,available:true},{name:XL,available:false}];
I want to display the range of data that contains true, i.e. display the above as "XS → L" (note missing out the M option).
Best I have come up with is using ng-repeat:
<span ng-repeat="size in sizes track by $index"
ng-init="sizeIndex = $index"
ng-show="size.available == true && (sizeIndex == 0 || sizeIndex == (sizes.length -1))">
{{size.short}}<span ng-show="sizeIndex ==0"> → </span>
</span>
As expected this fails because size XL available == false, and it won't work if size XS is unavailable.
Is there a permutation of the display logic that I haven't thought of? Or is there a 'proper' way to test if an element is the last (or first) shown in a list?
If you filter by available then a simple $first n $last will work
<li class="animate-repeat" ng-repeat="size in sizes | filter: {available:true}">
<div ng-show="$first">{{size.name}}</div>
<div ng-show="$last">{{size.name}}</div>
</li>
demo https://plnkr.co/edit/QHIKrUOudSYpczWuVdCe?p=preview this a fork of the angular doc example
The best way to do this is to calculate the result in your controller before trying to display it:
$scope.sizes = [
{name: "XS", available: true},
{name: "S", available: true},
{name: "M", available: false},
{name: "L", available: true},
{name: "XL", available: false}
];
DisplayRange();
function DisplayRange () {
var BeginSize = null, EndSize = null;
for (var i=0; i < $scope.sizes.length; i++) {
if ($scope.sizes[i].available === true && BeginSize == null) {
BeginSize = $scope.sizes[i].name;
}
if ($scope.sizes[i].available === true) {
EndSize = $scope.sizes[i].name;
}
}
$scope.FormattedSizes = BeginSize + " → " + EndSize;
}
What this is doing is scanning through each item in the $scope.sizes array. When it finds the first available item, it assigns that name to BeginSize. And each time it sees an available item, it will assign that name to EndSize. We can be sloppy with EndSize because eventually the loop will reach the last available item and assign it to EndSize which will make it correct.
In your HTML, reference it like this:
<span>{{ FormattedSizes }}</span>

Angular js comparison

I have a condition that needs to be checked in my view: If any user in the user list has the same name as another user, I want to display their age.
Something like
<div ng-repeat="user in userList track by $index">
<span class="fa fa-check" ng-if="user.isSelected"></span>{{user.firstName}} <small ng-if="true">{{'AGE' | translate}} {{user.age}}</small>
</div>
except I'm missing the correct conditional
You should probably run some code in your controller that adds a flag to the user object to indicate whether or not he/she has a name that is shared by another user.
You want to minimize the amount of logic there is inside of an ng-repeat because that logic will run for every item in the ng-repeat each $digest.
I would do something like this:
controller
var currUser, tempUser;
for (var i = 0; i < $scope.userList.length; i++) {
currUser = $scope.userList[i];
for (var j = 0; j < $scope.userList.length; j++) {
if (i === j) continue;
var tempUser = $scope.userList[j];
if (currUser.firstName === tempUser.firstName) {
currUser.showAge = true;
}
}
}
html
ng-if='user.showAge'
Edit: actually, you probably won't want to do this in the controller. If you do, it'll run every time your controller loads. You only need this to happen once. To know where this should happen, I'd have to see more code, but I'd think that it should happen when a user is added.
You can simulate a hashmap key/value, and check if your map already get the property name. Moreover, you can add a show property for each objects in your $scope.userList
Controller
(function(){
function Controller($scope) {
var map = {};
$scope.userList = [{
name:'toto',
age: 20,
show: false
}, {
name:'titi',
age: 22,
show: false
}, {
name: 'toto',
age: 22,
show: false
}];
$scope.userList.forEach(function(elm, index){
//if the key elm.name exist in my map
if (map.hasOwnProperty(elm.name)){
//Push the curent index of the userList array at the key elm.name of my map
map[elm.name].push(index);
//For all index at the key elm.name
map[elm.name].forEach(function(value){
//Access to object into userList array with the index
//And set property show to true
$scope.userList[value].show = true;
});
} else {
//create a key elm.name with an array of index as value
map[elm.name] = [index];
}
});
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
HTML
<body ng-app="app" ng-controller="ctrl">
<div ng-repeat="user in userList track by $index">
<span class="fa fa-check"></span>{{user.name}} <small ng-if="user.show">{{'AGE'}} {{user.age}}</small>
</div>
</body>

Angular Chosen default not working with object

I am using https://github.com/localytics/angular-chosen to allow for select tags with search capability for many options.
The problem I'm having is with preselecting an option on an already saved vendor object. When creating a new one there is now issue, but if we're viewing an existing vendor, I want to show the vendor's name in the select box, rather than the placeholder.
<select chosen
ng-model="myVendor"
ng-options="vendor['public-id'] as vendor.name for vendor in vendors"
data-placeholder="Nah">
</select>
And in my controller, I'm setting the model by hand $scope.myVendor = "Some value"
The problem is that I'm populating the options with an object, instead of a key/value. I found an example of it working with a key/value, but haven't had success adapting this to objects as options.
I've even tried setting myVendor to the matching object that I want selected, with no luck.
Plunker of issue
I updated the plunker and change my previous changes on the plugin. this was not the issue. I don't understand how it was giving me errors there.
The solution is to track with an object and two functions the id and the name:
// Controller
$scope.vendors = [
{
"public-id": "1234",
"name": "stugg"
},
{
"public-id": "4321",
"name": "pugg"
}
];
$scope.myVendor = {name: "pugg", id:""};
$scope.updateMyVendorName = function () {
var found = false,
i = 0;
while (!found && i < $scope.vendors.length) {
found = $scope.vendors[i]['public-id'] === $scope.myVendor.id;
if (found) {
$scope.myVendor.name = $scope.vendors[i].name;
}
i++;
}
}
findVendorByName();
function findVendorByName () {
var found = false,
i = 0;
while (!found && i < $scope.vendors.length) {
found = $scope.vendors[i]['name'] === $scope.myVendor.name;
if (found) {
$scope.myVendor.id = $scope.vendors[i]['public-id'];
}
i++;
}
}
// template
<select chosen class="form-control span6" ng-options="vendor['public-id'] as vendor.name for vendor in vendors" ng-model="myVendor.id" ng-change="updateMyVendorName()">
{{myVendor.name}}

Categories