Reputation: 382
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
Upvotes: 0
Views: 97
Reputation: 759
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.
Upvotes: 1