I am creating a simple list of messages similar to "Whatsapp". I want to include small blurb saying today, yesterday etc.
JSON format:
{
"id":"2",
"chat_id":"2",
"msg":"sample message 1",
"timestamp":1404803173
}
HTML:
<ul>
<li class="time"><span>Today/Yesterday/2DaysAgo</span></li>
<li ng-repeat="result in results">{{result.msg}}<small>{{result.timestamp*1000 | date:'medium'}}</small></li>
</ul>
I want to show the first <li> (class="time") only once for the day (not for all message). Is there any better way I can do this?
Do you have a server side? If yes, the best way to do this would be to:
Show the "time" li upon first load every new day.
set a flag on the server side to "true" once the li is loaded
Set the flag to false at 0000 hrs every night (or at first load every morning).
Check upon every load if the flag was set to true that day already.
Not sure if this can be done purely from client side. One way thats worth a show would be to set the flag in LocalStorage once every day (append the date maybe? ) and check this flag upon each load..
IMHO the best approach would be to sort messages by time then to split them by day and as output repeat days and messages in days, with other approaches it can be really overcomplicated and not worth the time
Considering that this JSON is coming from a Service and it is an array:
var json=[
{
"id":"1",
"chat_id":"2",
"msg":"sample message 1",
"timestamp":1404803173
},
{
"id":"2",
"chat_id":"2",
"msg":"sample message 2",
"timestamp":1404803174
},
...]
then you could create a new object on your controller looping through the items from your JSON and adding a unique key value for all the days, so you can filter it e.g.: the dates without "/"
$scope.json = Service.json;
$scope.dates = [];
$scope.messages = [];
for(i=0; i<$scope.json.length; i++){
var d = new Date($scope.json[i].timestamp*1000);
var index = String(d.getDate()) + String((d.getMonth()+1)) + String(d.getFullYear());
var obj = {id: index, data: $scope.json[i]};
$scope.messages.push(obj);
if($scope.dates.indexOf(index) == -1) $scope.dates.push(index);
}
Then, on your view:
<ul ng-repeat="date in dates">
<li class="time"><span>{{date | someFilterToGetDayFromDate}}</span></li>
<li ng-repeat="message in messages | filter:{'id': date}:true">{{message.msg}}<small>{{message.timestamp*1000 | date:'medium'}}</small></li>
</ul>
I didn't test the code, but hopefully you will get the idea of what to do here.
If it is not clear, let me know and I will try to explain further.
Related
Is there a way to organize/sort a md-virtual-repeat?
Im feeding it an array of data for example: {a: "123", b: "321"} and then I can do {{loop.a}} and {{loop.b}} for example.
But what if I wanted to make it so that it will show the highest value in B at the top, and its lowest value on the bottom. Descending Order. How would I achieve that?
My code for the actual ng-repeat process is following:
window.proxies=[];
$scope.nproxies={
_l:0,
_t:0,
getItemAtIndex: function(i) {
if (i>this._l) {
if (this._t<=i) {
this._t+=5;
this._l=this._t;
};
}
return window.proxies[i];
},
getLength: function() {
return window.proxies.length < 8 ? 8 : window.proxies.length;
}
};
Basically its setup to get only if there is actually more to get, but always have atleast 8 "rows" setup, so essentially my table will atleast always have 8 minimum rows (While those rows could be empty) its just a better way to make it look like a proper table.
It does everything essentially like the official demo.
But as you can see, there isnt exactly a way for me to change organization due to how it gets the values, by index :/
The only way I see of filtering anything here is by grabbing window.proxies and shuffling, ordering whatever and then returning it back to window.proxies. The issue with that is its relatively slow, will often UI block for large values and could cause race-conditions.
Any ideas appreciated!
The best way to have smooth UI with sorted md-virtual-repeat displaying large amount of sorted data is to sort your dataset on server side, otherwise you'll get very awkward user experience. Consider the case, when you load another portion of data, and the items of this data should be inserted into different locations of the sorted dataset. This will cause either your user looses actual position while scrolling or not see new data at all (or both).
If your dataset is not too large (less than 20k records in case of using AngularJS), you can load the whole dataset to client side, sort it and then feed it to md-virtual-repeat, which will display only the items that may be displayed in the container (and not the whole dataset) and reuse existing items while scrolling.
Here is an example of client-side sorting of 10K records, where proxies are sorted by proxy port in descending order: https://plnkr.co/edit/mbMjakZFeHoQeM5OLCfh?p=preview . It took 12ms on my machine. I don't know how much metadata on proxies you have, but in my case 10K records size was 50KB, which is smaller than size of most of the decorative images on the page...
HTML
<div ng-controller="ctrl as ctrl" ng-cloak="">
<p>Processing time: {{ctrl.sortTime}}ms</p>
<md-content layout="column">
<md-virtual-repeat-container id="vertical-container">
<div md-virtual-repeat="item in ctrl.items" md-on-demand="" class="repeated-item" flex="">
{{item.n}}:{{item.p}}
</div>
</md-virtual-repeat-container>
</md-content>
</div>
JavaScript
angular
.module('app', ['ngMaterial'])
.controller('ctrl', function($http, $scope) {
var ctrl = this;
Promise.all([ // emulate pagination
$http.get('data0.json'),
$http.get('data1.json')
]).then(function(responses) {
$scope.$apply(function() {
var t = Date.now();
ctrl.items = {
data: responses.reduce(function(a, r) {
return a.concat(r.data) // Combine all responses
}, []).sort(function(a, b) { // Sort by 'p' property in descending order
if(a.p > b.p) return -1;
if(a.p < b.p) return 1;
return 0;
}),
getItemAtIndex: function(idx) { return ctrl.items.data[idx]; },
getLength: function() { return ctrl.items.data.length }
};
ctrl.sortTime = Date.now() - t;
});
});
});
Note:
Also, browser has only one UI thread, and memory allocated to web workers is isolated, so the UI may be slow, but you can never get into situation of race-condition when using JavaScript (either server side or client side).
I'm using firebase to save posts that have the following data:
createdAt: "Sun Apr 03 2016 18:32:46 GMT-0300 (BRT)"
What I'm trying to achieve is to get the most recent posts first and then load the older ones while the user scrolls down.
With posts retrieved using ngInfiniteScroll I'm being able to order desc using <div ng-repeat="post in posts | orderBy:'-createdAt'"> but ngInfiniteScroll keep returning the old posts first. I'm ordering but i'm ordering the older ones.
I already tried using the same logic ("-createdAt") in ngInfiniteScroll but it was not effective.
My js is pretty much this:
var baseRef = new Firebase(FBURL).child("posts");
var scrollRef = new Firebase.util.Scroll(baseRef, "createdAt");
$scope.posts = $firebaseArray(scrollRef);
$scope.posts.scroll = scrollRef.scroll;
Security and rules:
"posts": {
".read": true,
".indexOn": "createdAt",
"$post": {
".read": true,
".write": true,
"$other": {
".validate": true
}
}
}
Looks like you found your solution but you were on the right track piping with the orderBy but just try tweaking it a little. Try using orderBy:'+':true", where orderBy this says you want to order it by something and the +:true says order it where anything new is on top, where -:true would say order new content on the bottom. But you can look the angular docs for it here. So if where handling it on the HTML side it would look something like this ng-repeat="post in posts| orderBy:'+':true" or:
<div ng-repeat="post in posts| orderBy:'+':true">
[....post info here]
</div>
But give that a try, hope it helps.
After looking a little deeper at the available documentations I could find an ugly workaround here.
It is basically to use a negative timestamp and retrieve the createdAt normally (ascending).
So when saving the data I'm doing the following:
{
createdAt: Firebase.ServerValue.TIMESTAMP,
createdAtDesc: 0 - Date.now()
}
Still looking for a better solution.
I'm using NodeJS, ANgularJS, and MongoDB with mongoose
Here is my model :
var PostSchema = new mongoose.Schema({
nomReseau : String,
corps : String,
etat : String,
section : String
});
I got a function that change the attribute etat:
$scope.passer = function(index){
var post = $scope.posts[index];
post.etat = "enCours";
Posts.update({id: post._id}, post);
$scope.editing[index] = false;
}
I'm using a ng-repeat for show object in my database :
<ul>
<li ng-repeat="post in posts ">
<p>
<a ng-show="!editing[$index]" href="#/{{post._id}}">{{post.corps}}</a>
</p>
<button ng-show="!editing[$index]" ng-click="passer($index)">Passer</button>
</li>
</ul>
I can see all post in my database and when I click on the button this works perfectly the attribute etat change and all is fine.
But when I add a filter in the ng-repeat like this :
<li ng-repeat="post in posts | filter:{ etat:'aTraiter'} ">
The filter works great I have all post with the attribute etat:'aTraiter'
But if I click on my previous button and change the attribute etat nothing change and I try with other functions they all work wihout the filter but when I put it nothing work.
The problem is that $index will change if less data is shown (because you're filtering). you could use directly post variable
ng-click="passer(post)"
and your function should be something like
$scope.passer = function(post){
post.etat = "enCours";
Posts.update({id: post._id}, post);
var index = $scope.posts.findIndex(function(p) { /* comparison to get original index */ }); /* keep in mind findIndex is not supported on IE, you might want to use filter or for loop instead) */
$scope.editing[index] = false;
}
you could handle editing in the post variable directly. So in your passer function you can do this
post.editing = false;
and in your view
ng-show="!post.editing"
this way you won't use $index and you will prevent all issues with being updated by filters
There are bugs in AngularJS v1.4 where in certain situations the ng-repeat breaks. I upgraded to v1.6 and it went away.
Do you have any controllers/services that access $scope.editing? If so, you might be setting the $scope.editing[$index] equal a previous state where it wasn't equal to false. You may also want to consider that you are assuming $scope.editing[$index] is going to be a boolean. if it has any other type such as string or number then it will evaluate to true.
Otherwise none of your results have the attribute etat equal to 'aTraiter' so they aren't showing. Have you verified that any of them actually do have etat equal to 'aTraiter'. You might be changing that value somewhere else. Possibly from the Passer function
I'm creating an application to manage restaurant orders.
I create the menu from $http so I've this list:
<div class="row vertical" style="background-image: url(/gest/images/etichette/ANTIPASTI.png);border-color: #0CF">
<div class="card piatti col s2" ng-repeat="anti in antis | filter:{tipo:'ANTIPASTI'}">
<div class="card-content"> <span class="card-title truncate red darken-3">{{anti.piatto}}</span> </div>
<div class="card-action"> {{n}}</div>
</div>
</div>
The div with class "row vertical" contain one time starters, then pasta, then beef ecc.
So I use ng-repeat each time, and filter by tipo.
My question is: is there any way to make ng-repeat only one time to show all menu (orderer before by starters, then pasta, beef ecc)?
I have this data (is a restaurant menu):
piatto: name of the the dish
tipo: category of the dish (like pasta, beef, fish, starters ecc)
I would show with only one repeat all the dishes ordered so:
starters, pasta, beef, fish, dessert etc.
And I would create each time a new row
From what I understand you already have all your date on the antis and you just want to filter it by type or do you want to OrderIt by a certain type?
This fiddle for example would order by name, but you can also provide an array with functions to retrieve each type in the way that you like, you can read about it here.
But basically you'd do
anti in antis | orderBy:'+tipo'
or
anti in antis | orderBy: [ function(){}, function(){} ]
EDIT:
As #yarons mentioned you can also chain strings to filter even further. I've updated the Fiddle so now the filter would be anti in antis | orderBy:['+tipo', '+piato']" which indicates that first the tipo would be alphabetically ordered ascending (+ indication) and after that the piato would also be alphabetically ascending.
If you'd want to define a different order than the alphabetical one I think you can use a sort of ENUM for the tipo as in:
var tipoENUM = {};
tipoENUM['ANIPASTI'] = 0;
tipoENUM['PASTA'] = 1;
tipoENUM['PIZZA'] = 2;
tipoENUM['BEEF'] = 3;
tipoENUM['DESERT'] = 4;
So that way you'd avoid using the string for the order, see following fiddle for the example.
EDIT 2:
Ok, so if you receive the data via the HTTP request it's better if you create a order function to help you, check this updated fiddle, like so:
// The enum would be defined as before but:
$scope.orderTipo = function (dish) {
return tipoENUM[dish.tipo];
}
On the HTMl you'll do:
ng-repeat="anti in antis | orderBy:[orderTipo, '+piato']"
Ok your example is perfect but I would repeat each time the "tipo" and then the relative "piato" in a list....something like this:
ANTIPASTI
- bruschetta
- suppli
- fritto
PRIMI
- caqrbonara
- amatriciana
etc.
Is it possible?
I am using jQuery UI Sortable to sort some rows on my website.
Here is my view:
<section class="data-list">
<article id="item_<%= item.id %>" class="data sortable">
<!-- stuff goes here -->
</article>
</section>
Here is my JS:
$('.data-list').sortable({items: '> .data'}).bind('sortupdate', function() {
var release = getUrlVars()["id"];
$.ajax({
type: 'POST',
data: $(this).sortable("serialize"),
url: '/release_items/'+ release +'/prioritize'
});
});
This creates a params['item'] array that looks like this:
["1537", "1536", "1540", "1541", "1542", "1543", "1544", "1545", "1547", "1546"]
Here is my controller code:
def prioritize
#release = Release.find(params[:id])
item = #release.release_items
item.each do |i|
i.priority = params['item'].index(i.id.to_s).to_i + 1
i.save
end
end
My problem is that i have several release_items that are distinguished by an item_type column. And as it stands now i currently don't have a good way in my controller to filter by item_type in my #release = Release.find(params[:id]) line.
What i would like to do is make sure in the item.each do |i| loop that the priority is only set IF the item is in the params['item'] array. How would i do that? Please let me know if this is not clear enough. Thanks!
EDIT 1:
For every release, there are n number of release items. THose release items are separated for display on the site by their item_type column (e.g., General, Project, Data, Patch). So on the site there are 4 different lists of items and i want to be able to sort those 4 lists individually. Like i stated above, the params['item'] array being passed to the prioritize action in the controller has only the items that need to be sorted, which is want i want.
The problem i'm running in to is that the first two lines in the prioritize action will get all of the release items, not just the ones in the params['item'] array. The values in the array are the IDs of the release items that need to be sorted. Instead of getting all release items, i want to only get the items that are in the params['item'] array. I do have a ReleaseItem model as well i can select from. So, i'm trying to do this: (i know this isn't the correct code, just for clarity sake)
item = ReleaseItem.find(conditions: "id in params['item']")
Does that make a little more sense? I appreciate your help!
I think I understand what you are trying to do...
def prioritize
#release = Release.find(params[:id])
items = #release.release_items.where("id IN (?)", params['item'])
items.each_with_index do |item, index|
item.priority = index + 1
item.save
end
end