Sorting a JSON response in Angular by keys - javascript

Alright, I've been stuck on this one for a while and can't find an adequate solution out there. Basically, I've grouped the posts by date server-side and I want to sort the groups by decending date in Angular. I'm assuming a custom filter is the way to go but I haven't been able to get it to work.
Here's some of the code:
JSON Response:
{"September 20th":[{"id":5,"title":"Test 1","url":"www.google.com","tagline":"Test tagline 1","created_at":"2014-09-20T19:30:44.672Z","updated_at":"2014-09-20T19:30:44.672Z","vote_count":5}],"September 21st":[{"id":6,"title":"Test 2","url":"www.google.com","tagline":"Test tagline 2","created_at":"2014-09-21T00:00:00.000Z","updated_at":"2014-09-20T19:32:41.409Z","vote_count":8}]}
HTML:
<section ng-controller='MainController'>
<ul ng-repeat="(date, posts) in postList | filter?">
<h1>{{ date }}</h1>
<li ng-repeat='post in posts'>
<p>{{ post.vote_count }}</p>
<button ng-click='upvote(post)' ng-disabled='!currentUser || hasVoted(post.id)'></button>
{{ post.title }}
</li>
</ul>
</section>
This displays the information perfectly, just in the incorrect order of dates.
I appreciate any help you can give me!

It's a bit hacky, but it works:
Example
Html:
<section ng-controller='MainController'>
<ul ng-repeat="posts in postList | orderObjectBy:'created_at':true">
<h1>{{ posts.__originalKey }}</h1>
<li ng-repeat='post in posts'>
<p>{{ post.vote_count }}</p>
<button ng-click='upvote(post)' ng-disabled='!currentUser || hasVoted(post.id)'></button>
{{ post.title }}
</li>
</ul>
</section>
CustomFilter:
.filter('orderObjectBy', function() {
return function(items, field, reverse) {
var filtered = [];
angular.forEach(items, function(item, key) {
item.__originalKey=key;
filtered.push(item);
});
filtered.sort(function (a, b) {
return (a[field] > b[field] ? 1 : -1);
});
if(reverse) filtered.reverse();
return filtered;
};
})

Related

How to increment a loop in Vue

I've got a multi-object array and I'd like to have it loop through a particular group of objects.
This is my code so far
<template>
<div>
<h1>My Test App</h1>
<button v-on:click="getHockeyData">Get Team Data</button>
<div v-for="hockeyData in hockeyDataList" :key="hockeyData.id" >
<p>{{ hockeyDataList.teams[0].roster.roster[1].person.fullName }}</p>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: "Weather",
data() {
return {
hockeyDataList: []
};
},
methods: {
getHockeyData() {
axios.get("https://statsapi.web.nhl.com/api/v1/teams/21?expand=team.roster").then(response => (this.hockeyDataList = response.data));
}
}
};
</script>
I know the loop won't work in it's current state. The portion of my output within the loop that I need to increment is roster[1] - I'm new to Vue so not sure how to use the v- commands to increment that 1 until there are no more instances of it.
Any help or feedback would be greatly appreciated!
Just loop over the others, really you want to start from teams as that's the way the JSON is structured
Example using various parts of the JSON, view the file for others.
<div v-for="team in hockeyDataList.teams" :key="team.id">
<h1>{{ team.name }}</h1>
<div>{{ team.venue.name }}, {{ team.venue.city }}</div>
<div>Created in: {{ team.firstYearOfPlay }}</div>
<div>Division: {{ team.division.name }} - Conference: {{ team.conference.name }}</div>
<h2>Roster</h2>
<div v-for="player in team.roster.roster" :key="team.id + '-' + player.person.id">
<h3>{{ player.person.fullName }}</h3>
<div>
Number: {{ player.jerseyNumber }} - Position: {{ player.position.name }} {{ player.position.type }}
</div>
</div>
</div>
<div v-for="team in hockeyDataList.teams" :key="team.id" >
<p v-for="roster in team.roster.roster :key="roster.id">
{{ roster.person.fullName }}
</p>
</div>
It's not necessary, but if you want to, you can get index in a v-for as well:
<div v-for="(team, index) in hockeyDataList.teams" :key="team.id" >
<p>
{{ hockeyDataList.teams[index].roster.roster[1].person.fullName }}
</p>
</div>
v-for increments the index automatically for you. The problem is that the api returns a json and not an array we can iterate on. By looking at the api response, we can see that teams is the array we can iterate on.
<v-for="(team, i) in hockeyDataList.teams" :key="i">
where index automatically increments until the end of list. We can then iterate through the roster.
<v-for="(roster, j) in team.roster.roster" :key="j">
Putting it all together
<div v-for="(team, i) in hockeyDataList.teams" :key="i">
<div v-for="(roster, j) in team.roster.roster" :key="j">
<p>{{ hockeyDataList.teams[i].roster.roster[j].person.fullName }}</p>
</div>
</div>

total basket reactive vuejs

Is it possible to make the total reactive? for example, in my basket I have two articles for a price of 18 €, if I add an article I want the total to change without updating the page.
my property calculate total returns me for two options: 14,0012,00 I would have liked it to calculate the price of the two options dynamically: 14,00 + 12,00 = 26
thank you for help
<template>
<div>
<div >
<div >
<u>
<li v-for="item in shop" :key="item.id">
{{ item.label}} : {{ item.cost }}€
</li>
</u>
<button #click="addItem">Add</button>
</div>
<div >
<u>
<li v-for="item in shop" :key="item.id">
{{ item.label}} : {{ item.cost }}€
</li>
</u>
</div>
<p>{{ total}}</p>
</div>
</div>
</template>
<script>
computed:{
totalbasket(){
let total = 0
this.shop.forEach(item=>{
total += item.cost
})
return total
}
}
</script>
It looks like the problem is your item.cost is a string, not a float.
Parsing the cost to floats before adding them might do the trick:
totalbasket(){
let total = 0
this.shop.forEach(item=>{
total += parseFloat(item.cost)
})
return total
}
And at the moment you have {{total}} in your template. You probably want to change this to {{totalBasket}}.
As mentioned by tony19 javascripts parseFloat function does not recognize commas as decimal points. There's a question about how to solve it here.
I would add an Add Button to each item you want to add and then call addItem(item). It would be something like this:
...
<li v-for="item in shop" :key="item.id">
{{ item.label}} : {{ item.cost }}€
<button #click="addItem(item)">Add</button>
</li>
Then, the addItem method adds the item, which would trigger totalbasket:
methods: {
addItem(item) {
this.shop.push(item)
},
...
}
And in your template, you should print totalbasket instead of total:
<p>{{ totalbasket }}</p>
This should do it.

Vue.js nested for loop with search filter

I have a JSON object with nested objects that I am iterating over to pull out data. All is working fine, but I'd like to add a search/filter implementation so that the search is being done on the second level of the nested for loop. I have it somewhat working but im not getting any data returned. Here is an example:
https://codesandbox.io/s/vue-template-s9t9o
In the HelloWorld component is where the search/filter is happening.
As you can see its not outputting the rest of the data after it passes through the searchFilter method.
To make it work without the search/filter, change the following on line #6:
from: <div class="contentSingle" v-for="(c, i) in searchFilter" :key="i">
to: <div class="contentSingle" v-for="(c, i) in cont" :key="i">
Anyone can think of what I can do to make this work? I need to filter by the elements inside each of the content inside the main data object. You can find the data object inside the FauxData/dataContent.js dir.
Thanks a lot.
-S
You should use methods instead of computed:
methods: {
filterValue(content) {
return content.filter(item => {
let itemUpper = item.content.toUpperCase();
let searchUpper = this.search.toUpperCase();
return itemUpper.indexOf(searchUpper) > -1;
});
}
}
and then in HTML code:
<section id="content">
<input type="text" id="search" name="search" v-model="search" placeholder="Search Content...">
<div v-for="(cont, index) in content" :key="index" class="contentWrapper">
<h1>{{ index }}</h1>
<div class="contentSingle" v-for="(c, i) in filterValue(cont)" :key="i">
<h3>{{ c.title }}</h3>
<div v-html="c.content"></div>
</div>
</div>
</section>
Updated
If you want to hide the empty section, then use computed value:
computed: {
filteredData() {
return Object.keys(this.content).reduce((a, cKey) => {
const data = this.filterValue(this.content[cKey]);
if (data.length) {
a[cKey] = data;
}
return a;
}, {});
}
},
methods: {
filterValue(content) {
return content.filter(item => {
let itemUpper = item.content.toUpperCase();
let searchUpper = this.search.toUpperCase();
return itemUpper.indexOf(searchUpper) > -1;
});
}
}
Use filteredData in outer v-for
<section id="content">
<input type="text" id="search" name="search" v-model="search" placeholder="Search Content...">
<div v-for="(cont, index) in filteredData" :key="index" class="contentWrapper">
<h1>{{ index }}</h1>
<div class="contentSingle" v-for="(c, i) in cont" :key="i">
<h3>{{ c.title }}</h3>
<div v-html="c.content"></div>
</div>
</div>
</section>
Demo on codepen

Angular HTTP request follow link to another endpoint

I am using ng-repeat to print a list of posts to the page via the WordPress REST-API. I am using Advanced Custom Fields on each post. From what I can see everything is working, but the post data is not showing in one container, yet it is displaying in another. I should also mention that this is set up like tabs. (user clicks a tab for a post and it displays that posts data)
Here is the code:
var homeApp = angular.module('homeCharacters', ['ngSanitize']);
homeApp.controller('characters', function($scope, $http) {
$scope.myData = {
tab: 0
}; //set default tab
$http.get("http://bigbluecomics.dev/wp-json/posts?type=character").then(function(response) {
$scope.myData.data = response.data;
});
});
homeApp.filter('toTrusted', ['$sce',
function($sce) {
return function(text) {
return $sce.trustAsHtml(text);
};
}
]);
HTML:
<section class="characters" ng-app="homeCharacters" ng-controller="characters as myData">
<div class="char_copy">
<h3>Meet the Characters</h3>
<div ng-repeat="item in myData.data" ng-bind-html="item.content | toTrusted" ng-show="myData.tab === item.menu_order">
<!--this is the section that will not display-->
<h3>{{ item.acf.team }}</h3>
<h2>{{ item.acf.characters_name }} <span>[{{item.acf.real_name}}]</span></h2>
<p class="hero_type">{{ item.acf.hero_type }}</p>
{{ item.acf.description }}
Learn More
</div>
</div>
<div class="char_tabs">
<!--if I put the identical {{}} in this section it WILL display-->
<nav>
<ul ng-init="myData.tab = 0" ng-model='clicked'>
<li class="tab" ng-repeat="item in myData.data" ng-class="{'active' : item.menu_order == myData.tab}">
<a href ng-click="myData.tab = item.menu_order">
<img src="{{ item.featured_image.source }}" />
<h3>{{ item.title }}</h3>
</a>
</li>
</ul>
</nav>
</div>
</section>
I should also mention that I use Ng-inspector, and it does show the data being pulled in. I can confirm this via the console. I have checked to ensure no css is in play; the div is totally empty in the DOM.
I appreciate all the help the GREAT angular community has shown!

remove angular limitTo on ng-click

So I have a list of posts and they are all different lengths so I'm cutting them off at the 500th character, but I want to show the remainder of the post on an ng-click. It seems like there might be some "angular way" to do this but I have not found it through google.
<ul id = "postsList">
<li ng-repeat="post in Posts" >
<p>{{ post.messageBody | limitTo:500 }}</p>
<button ng-click = "undoLimitTo()">View</button>
</li>
</ul>
I would write it like:
set editable value for limit and give Posts length
<ul id = "postsList">
<li ng-repeat="post in Posts" ng-init="limit= 500">
<p>{{ post.messageBody | limitTo:limit }}</p>
<button ng-click = "limit = Posts.length">View</button>
</li>
</ul>
Try this:
<ul id = "postsList" ng-init="limit = 500">
<li ng-repeat="post in Posts" >
<p>{{ post.messageBody | limitTo:limit }}</p>
<button ng-click = "limit = Number.MAX_SAFE_INTEGER">View</button>
</li>
</ul>
EDIT
This is bullshit. It will change the limit for all posts.
You could in your controller add a limit property to the Posts. And then:
<ul id = "postsList">
<li ng-repeat="post in Posts" >
<p>{{ post.messageBody | limitTo:post.limit }}</p>
<button ng-click = "post.limit = post.messageBody.length">View</button>
</li>
</ul>
I have put together a fiddle for you that does what I think you are after. The key is to keep track of the listLimit inside a controller, which changes upon clicking on the more/less text.
var module = angular.module("MyModule", []);
module.controller("MyController", function($scope) {
// create the dummy list items
$scope.list = [];
for(var i=0; i<100; i++){
$scope.list.push({
value: i
});
}
// set the initial item length
$scope.totalItems = 5;
// more/less clicked on
$scope.more = function(){
if($scope.totalItems === 5){
$scope.totalItems = $scope.list.length;
}else{
$scope.totalItems = 5;
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyModule" ng-controller="MyController">
<ul>
<li ng-repeat="item in list | limitTo:totalItems">
This is item {{$index}}
</li>
</ul>
<button type="button" ng-click="more()">{{totalItems === 5 ? 'more' : 'less'}}</button>
</div>
extremely simple version:
in controller:
$scope.limit = 3;
$scope.setLimit = function (number) {
$scope.limit = number;
}
in HTML
<div ng-repeat="item in items | limitTo:limit">
<div ng-show="ng-show="items.length > limit && $index == 0">
<span ng-click="setLimit (items.length)">show all</span>
</div>
<div>{item.yourexpression}}</div>
<div>
now it will only show the div if there are more items as the limit and it will show it only above the first item, we send the length of the ng-repeat to the controller and update the limit. Done!

Categories

Resources