Sorting in Meteor JS - javascript

I have written a small code in MeteorJS which uses Mongodb and Iron:router (I am new to MeteorJS). Everything worked fine initially and towards the end of it, sorting stopped working. To explain a little more, the list of websites must be sorted according to the upvotes it has received and date of creation. Here are the relevant sections of the code:
//Sorting websites based on userfilter or votes
websites:function(){
if (Session.get("userFilter")){
return Websites.find({ createdBy: Session.get("userFilter") }, { sort: { createdOn: -1, up: -1 }});
} else {
return Websites.find({},{ sort: { createdOn: -1, up: -1 }});
}
},
{{#each websites}}
<div class="col-xs-12 col-md-3">
<div class="thumbnail">
{{title}}
<p class="site_desc">{{description}}</p>
<br>
<p class="upvote_button">Upvote: {{up}}</p>
<p class="downvote_button">Downvote: {{down}}</p>
<a href="#" class="btn btn-default js-upvote" id="upvote_button">
<span class="glyphicon glyphicon-arrow-up" aria-hidden="true"></span>
</a>
<a href="#" class="btn btn-default js-downvote" id="downvote_button">
<span class="glyphicon glyphicon-arrow-down" aria-hidden="true"></span>
</a>
<br>
<p class="added-by">Added By:
{{getUser createdBy}}
</p>
<p>Added On: {{createdOn}}</p>
Details
</div>
</div>
{{/each}}

When you specify sorting according to more than one property, Mongo will sort first by the first property, then for documents that fall at the same "position", it will use your second property to order those, etc.
So you will get documents (websites) sorted by createdOn date first, and for those which have the exactly same date (including time if present), by up value. I guess this is not what you would like.
Furthermore, since you use a dictionary / mapping object type to specify your sort, you will depend on the JS engine ordering implementation. In most cases, it will order your properties by insertion order, i.e. 1) createdOn 2) up in your case.
http://docs.meteor.com/api/collections.html#sortspecifiers
The [{a: 1, b: -1}] form will only work if your JavaScript implementation preserves the order of keys in objects. Most do, most of the time, but it’s up to you to be sure.
In the end, you might have looked for this sorting:
Websites.find({}, {
sort: [
["up", "desc"], // Sort first by "up" value
["createdOn", "desc"] // then for websites with same up value, order by date
]
})

You may want ot do something like this
Websites.find({}).sort({createdOn:-1, up:-1})
Hope it works
if not try adding this
Websites.ensureIndex({createdOn: -1, up: -1})
Thanks

Related

How do I remove duplicate values ​in a v-for array?

I coded like this to remove duplicate values.
vue
<div class="col-md-6" style="float: left">
<ul class="list-group">
<li class="list-group-item"
:class="{ active: index1 == currentIndex1 }"
v-for="(member, index1) in uniqueMemName"
v-bind:value="member.mem_name"
:key="index1"
#click="setActiveMember(member, index1)"
>
<strong style="margin-bottom: 5px"> {{member.mem_name}} </strong>
</li>
</ul>
</div>
vue (script)
computed: {
uniqueMemName() {
return _.uniqBy(this.members, function(m) {
return m.mem_name;
});
}
},
I also installed lodash. But I get an error. Which part is wrong?
Please let me know if there is another method other than the one I wrote.
++) error console
console window
++) array information:
I have tables A and B. Table A imports only the mem_name column. Table B imports all columns.
Example ->
a.mem_name
b.col1
b.col2
mem1
10
20
mem1
30
40
mem2
50
60
I'm working on making duplicate mem_names into one at this time. Using lodash's unique features.
If you want to use lodash just for this, which sounds like the case, I suggest that there may be a better way without it, only using newer vanilla JS:
...
computed: {
uniqueMemName() {
return [...new Set(this.members.map(m => m.mem_name))]
}
}
Sets are always unique, so mapping and converting to a set and then back to array gives us a unique array.

How to add values to an array from variables in Vue.Js to make bar chart

I'm trying to add values, stored in variables, to be added to an array to display as a bar chart in Vue.js
I tried adding values by using series[0]=ServiceArea1;.
This is what I got so far:
barChart: {
data: {
series [0] : ServiceArea1Total,
series [1] : ServiceArea2Total,
series [2] : ServiceArea3Total,
series [3] : ServiceArea4Total,
series [4] : ServiceArea5Total,
labels: ['Western', 'Northern', 'Eastern', 'Southern', 'Central'],
series: [ ]
}
}
If I enter the values like so
series: [[542, 443, 320, 780, 553],]
the output comes however since I am calling the values from a database, I cannot enter the values statically.
The HTML part is below:
<div class="row">
<div class="col-md-6">
<chart-card :chart-data="barChart.data"
:chart-options="barChart.options"
:chart-responsive-options="barChart.responsiveOptions"
chart-type="Bar">
<template slot="header">
<h4 class="card-title">Statewide Service Area Kids</h4>
<p class="card-category">Click on a service area to view more information</p>
</template>
<template slot="footer">
<div class="legend">
<i class="fa fa-circle text-info"></i> Service Area
</div>
<hr>
<div class="stats">
<i class="fa fa-check"></i> Data information certified
</div>
</template>
</chart-card>
</div>
It depends on how the data is structured when it comes back from the database. At the most basic, you'd loop through an array or object from the database and call Array.push for each element.. Ex: series.push(x)
This may help:
How do I push items into an array in the data object in Vuejs? Vue seems not to be watching the .push() method
Assuming that you have a model like :
let barChart = {
services: [
ServiceArea1Total,
ServiceArea2Total,
ServiceArea3Total,
ServiceArea4Total,
ServiceArea5Total,
],
labels: ['Western', 'Northern', 'Eastern', 'Southern', 'Central'],
series: []
}
You are storing your services call in barChart.services, your labels in barChart.labels (which is not used here) and your total series in barChart.series.
For getting all your data from your services and storing it in barChart.series you have to do something like :
barChart.services.forEach( function( service ){
barChart.series.push( service() );
});
With that code you gonna call all your functions in services and push the data in the array series for each one.
May I have miss understood what you are trying to do ?

knockoutjs deep copy observable array

I have an obeservable array in Knockout, say data that contains objects with observable properties. In one part of my application I want to allow users to add to this array and ultimately mess about with it, but if they cancel to revert the array to the original state.
One solution I though of is to create a copy called dataCopy that replaces the original array in the event of cancellation, but this only creates another pointer to the same underlying data, so it will also reflect the changes that were made which is not what I want.
I've tried converting it to javascript, via ko.toJS(data) and this creates a js array of the data, however all of the objects lose their observable properties and when I re-create the data using ko.observableArray(dataCopy) the app falls over as the properties of the objects are no longer observable.
How can I create a clone of a knockout array rather than another pointer?
This is an example of how you could achieve this using the ko mapping plugin for deeply copying your observable arrays.
I doubt any other method of copying would work for you such as .slice or ko.toJS since as you mentioned you want a deep copy which goes as far as the underlying object's observable propertieš.
The important part here is the function :
function obsArrDeepCopy(from, to) {
ko.mapping.fromJS(ko.toJS(from), {}, to);
}
Which will do the deep copy by first converting the source array to a plan JS array and then populate the target array converting to observables the underlying object properties.
Full working example:
function Person(name) {
this.name = ko.observable(name);
}
var viewModel = {
people: ko.observableArray([
new Person("Annabelle"),
new Person("Bertie"),
new Person("Charles")
]),
peopleCopy: ko.observableArray(),
newName: ko.observable(''),
addPerson: function() { this.peopleCopy.push(new Person(this.newName())) },
commit: function() { obsArrDeepCopy(this.peopleCopy, this.people) },
cancel: function() { obsArrDeepCopy(this.people, this.peopleCopy) },
};
ko.applyBindings(viewModel);
obsArrDeepCopy(viewModel.people, viewModel.peopleCopy);
function obsArrDeepCopy(from, to) {
ko.mapping.fromJS(ko.toJS(from), {}, to);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<h2> Initial </h2>
<ul data-bind="foreach: people">
<li>
<span data-bind="text: name" />
</li>
</ul>
<br/><br/>
<h2> Copy </h2>
<ul data-bind="foreach: peopleCopy">
<li>
<span data-bind="text: name" />
</li>
</ul>
<input type="text" data-bind="textInput: newName" />
<button type="button" data-bind="click: addPerson"> Add </button>
<br/><br/>
<button type="button" data-bind="click: commit"> Commit </button>
<button type="button" data-bind="click: cancel"> Cancel </button>

Meteor / Blaze: updating list properties with minimal redraw

I'm encountering serious performance issues related to Blaze redraw. I know that my data structure is at fault but I can't find a way to fix it. This is a bit of a long one: I'll do my best to lay it out clearly.
The goal / the problem
I want to create a schedule view where users can schedule tests. Users should be able to drag and drop test slots, but the resulting redraw is so slow that the thing is unworkable.
The data
I have a Sittings collection which stores details about testing times. Each sitting has a list of scheduleGroups (i.e. exam1 and exam2 happen on Tuesday, exam3 and exam4 happen on Wednesday) The structure looks more or less like this:
var sitting = {
"_id" : "sittingId",
"name" : "Amazing Sitting",
"locationIds" : [
"room1_id",
"room2_id"
],
"startDate" : ISODate("2015-07-01T04:00:00Z"),
"endDate" : ISODate("2015-07-02T04:00:00Z"),
"scheduleGroups" : [
{
"_id" : "dEb3mC7H8RukAgPG3",
"name" : "New group",
"booths" : [
{
"boothNumber" : 1,
"slots" : [
{
"examId" : "exam1",
"dayNumber" : 1,
"startTime" : {
"hour" : 8,
"minutes" : 0
},
"endTime" : {
"hour" : 9,
"minutes" : 30
},
"candidateId" : "odubbD42kHDcR8Egc",
"examinerIds" : [
"GQmvyXbcmMckeNKmB",
"GfoBy4BeQHFYNyowG"
]
}
]
}
]
"locationId" : "room1_id"
}],
"examIds" : [
"exam1",
"exam2",
"exam3"
]
};
The HTML
Sitting is defined as global context in Router.current().data hook. This is part of the redraw problem. Anyway, the following HTML produces the following layout (img below).
<template name='scheduler'>
<div class='container'>
<div class='tabpanel'>
<div class='tab-content' id='scheduler-content'>
{{#each getScheduleGroups}}
<div class='container-fluid schedule-wrapper'>
<div class='row'>
<div class='scheduler-inner-box placeholder-box'></div>
{{#each booths}}
<div class='group-column-head scheduler-inner-box'>{{boothNumber}}</div>
{{/each}}
</div>
<!-- Sittings can take place over several days, normally 2 days -->
{{#each sittingDays}}
<div class='day-wrapper'>
<h3 class='text-center'>Day {{this}}</h3>
<!-- This helper returns a list of start times: [June 1 9AM, June 1 915AM....] -->
{{#each getScheduleTimes ..}}
<div class='row time-row'>
<div class='group-row-head scheduler-inner-box'>
<span class='box-content'>{{this}}</span>
</div>
<!-- Loop through each booth, so that each slot has a square for that boot
i.e. June 1 9AM has space for booth 1, and booth 2, and booth 3, etc
-->
{{#each ../../booths}}
<!-- Paper starting now tells us if there is a slot scheduled to begin at this time or wher its empty-->
<div class='scheduler-inner-box {{#unless paperStartingNow ../.. ..}}open-booth{{/unless}}'>
{{#with paperStartingNow ../.. .. boothNumber}}
<!--
getSlotStyle calculates height and colour, depending on how long the paper is and whether
it's ready to go: i.e. does it have a candidate and the right number of examiners?
-->
<div class='paper-tile tile' {{getSlotStyle this ../boothNumber 'paper'}}>
<span class='slot-name'>{{getSlotName}}</span>
</div>
{{#if slotHasCandidate}}
<div class='candidate-tile tile' {{getSlotStyle this ../boothNumber 'candidate'}}>
<span class='slot-name candidate-name'>{{getCandidateDisplay}}</span>
</div>
{{/if}}
{{#each slotExaminers}}
<div class='examiner-tile tile' {{getSlotStyle .. ../../boothNumber 'examiner' index}}>
<span class='slot-name examiner-name'>{{profile.name}}</span>
</div>
{{/each}}
{{/with}}
</div>
{{/each}}
</div>
{{/each}}
</div>
{{/each}}
</div>
{{/each}}
</div>
</div>
</div>
The Issue
When a user drops a slot (purple tile) onto an empty space, or drags a candidate or examiner to another tile, I update the collection and let Meteor redraw. However the whole thing redraws and I can't find a way to isolate.
Here's the code for moving a whole slot, candidates examiners and all.
moveSlot: function (originalBoothNumber, originalSlot, dropBoothNumber, newSlot) {
check( originalBoothNumber, Number );
check( originalSlot, Object );
check( dropBoothNumber, Number );
check( newSlot, Object );
//pull the old slot
var originalBooth = _.findWhere( this.booths, {boothNumber: originalBoothNumber} );
originalBooth.slots = _.without( originalBooth.slots, originalSlot );
//insert the new one
var dropBooth = _.findWhere( this.booths, {boothNumber: dropBoothNumber} );
dropBooth.slots.push( newSlot );
var modifier = {
$set: {}
};
modifier["$set"]["scheduleGroups." + this.getIndex() + ".booths"] = this.booths;
return Sittings.update({_id: this.getSitting()._id }, modifier);
},
Do I need to change the way I store Sitting.booths? If anyone can recommend a better data structure that will trigger a redraw of just the slot, I'd really appreciate it. I've found a hack that puts every slot into a session variable but there's got to be another way.
Thanks for your patience and your help,
db
Problem
It's because you store the whole set of slots inside one document of booth. Once you update a slot, the whole document is consider updated.
Solutions
IMHO, I don't think you need to change the structure, unless you have build a feature to search for slot or booth. You can set it up by create a series of reactiveVariable or a reactiveDictionary, your template should depends only on that reactive variable. Every time you updated, consider to sync between two of them, either manually or automatically.
PS. I'm not doing any affiliate with this site: https://www.sportsplus.me. But if you sign up -> home -> mlb -> anycontest -> click on plus, you can see their masterpiece infinite scroll with partly redraw. (Open your dev-console and watch the changes in element). They do exactly the same thing you're trying to do with minimum redraw.

Ember.js: Deleting an Item from a Sorted ArrayController

In the Ember app I'm building, I've got an ArrayController managing a list of items with several columns of data for each record object in the array with a sort button in each column header in the view. I have set up the list to sort on a given column per Balint Erdi's recommended method here. You will see this sorting in my code below.
The sorting works fine. However, the problem arises when I remove an item from the array. Currently, when I attempt to remove an item from the array, the correct item is apparently removed from the array and is properly deleted from the store and the delete is saved to my backend. However, after the item removal, my view is not correct. In some cases, the wrong item is shown as removed, in other cases, no item is shown as removed. Yet IF I press sort again, the view is updated correctly.
So, the index of the array is obviously getting off some how, but I'm not sure how and all of my attempts to apply the tricks of others are not working!
Here is my route object:
App.UsersFilesRoute = Ember.Route.extend({
model: function () {
return this.modelFor('users').get('files');
}
});
Here is my ArrayController:
App.UsersFilesController = Ember.ArrayController.extend({
sortProperties: ['name'],
sortedFiles: Ember.computed.sort('model', 'sortProperties'),
actions: {
addFile: function(file) {
var newFile = this.store.createRecord('file', {
name: file.name.trim(),
fileSize: file.size,
loaded: false
});
this.pushObject(newFile);
},
sortBy: function (sortProperties) {
this.set('sortProperties', [sortProperties]);
},
removeFile: function (fileToRemove) {
var _this = this;
var file = this.store.find('file', fileToRemove.get('id'));
file.then( function (file) {
_this.removeObject(file);
file.deleteRecord();
file.save();
});
},
saveFile: function (file) {
....
}
}
});
And here is my template code:
<div class="hidden-xs row user-file-header-row">
<div class="col-sm-5 col-md-5 user-file-header">
File Name
<button type="button" class="btn-xs btn-default files-sort-btn" {{ action 'sortBy' 'name'}}></button>
</div>
<div class="col-sm-1 col-md-1 user-file-header">
Size
<button type="button" class="btn-xs btn-default files-sort-btn" {{ action 'sortBy' 'fileSize'}}></button>
</div>
</div>
{{#each file in sortedFiles}}
<div class="row user-file user-file-break">
<div class="col-xs-11 col-sm-5 col-md-5 user-file-name">
<a {{ bind-attr href="file.path" }} >{{ file.name }} </a>
</div>
<div class="col-xs-9 col-sm-1 col-md-1">
{{ format-file-size file.fileSize }}
</div>
<div class="col-xs-9 col-sm-1 col-md-1">
<button type="button" class="btn-xs btn-default files-list-btn" {{ action 'removeFile' file }}></button>
</div>
</div>
{{/each}}
NOTE: There is some similarity between my question and this other StackOverflow question: After using jQuery UI to sort an Ember.js item, using Ember Data's model.deleteRecord() doesn't work, however, I've attempted to apply that answer my own problem with no success. Furthermore, I have no jQuery going on here in my sorting.
OK. I have found an answer, or rather an answer has found me.
My problem was that in the code above I was removing the itemfrom the ArrayController and then calling .delete() and .save(). This sequences of calls was sending conflicting signals to Ember on how to update my view. Apparently, the .removeObject() was actually removing the item from the array, but then the subsequent .delete()/.save() was setting the model behind the view to a state just before deletion (not sure about that but that's what I saw happening).
So anyways, .destroyRecord() returns a promise, so I moved the .removeObject() within the .then() for the promise, and that resolves the issue.
So, the following code in the removeFile action resolved the issue:
removeFile: function () {
var self = this;
var fileToRemove = this.get('fileToRemove');
var file = this.store.find('file', fileToRemove.get('id'));
file.then (function (file) {
file.destroyRecord().then(function(){
self.get('model').removeObject(file);
});
});
}
Note that you don't have to do the this.store.find() first, you could simply do the following:
removeFile: function () {
var self = this;
var fileToRemove = this.get('fileToRemove');
fileToRemove .destroyRecord().then(function(){
self.get('model').removeObject(file);
});
}
However, I chose to be conservative and double-check the store. That seems safer to me.

Categories

Resources