/templates/public/scripts/models/Participant.js
JavaScript | 564 lines | 531 code | 20 blank | 13 comment | 171 complexity | 4f3c21b2b3cdb2c53781869cad8f1272 MD5 | raw file
Possible License(s): BSD-3-Clause
- define(["backbone", "underscore", "jquery", "models/Entry", "models/Station"],
- function(Backbone, _, $, Entry, Station) {
- var TIME_MISSING = Number.MAX_VALUE,
- Participant, Participants;
- Participant = Backbone.Model.extend({
- idAttribute: "bibNumber",
- url: function() {
- var url = "/newAPI/events/" + this.get("raceEvent_id") + "/participants";
- if (!this.isNew()) {
- url += "/" + this.id;
- }
- return url;
- },
- formatName: function() {
- return this.get("lastName") + ", " + this.get("firstName");
- },
- getEntrys: function() {
- var self = this;
- if (self.has("entrys")) {
- return self.get("entrys");
- }
- return Entry.list.fetchAll(self.collection.eventId, self.id).pipe(function(entrys) {
- self.set({entrys: entrys});
- return entrys;
- });
- },
- getRaceTime: function(startStation, station) {
- var entrys = this.getEntrys();
- if (entrys.promise) { // we dont' have entries yet
- return TIME_MISSING;
- }
- var eStart = entrys.get(startStation),
- eCur = entrys.get(station);
- if (!eStart || !eCur) {
- return TIME_MISSING;
- } else {
- var a = eCur.getValidTime();
- var b = eStart.getValidTime();
- if (!a || !b) {
- return TIME_MISSING;
- }
- return a-b;
- }
- },
- getOrCreateEntry: function(stationNo) {
- var self = this;
- return $.when(this.getEntrys()).pipe(function(entrys) {
- if (entrys.get(stationNo)) {
- return entrys.get(stationNo);
- } else {
- entrys.add({
- station_stationNumber: stationNo,
- participant_bibNumber: self.id,
- time_in: null,
- time_out: null
- });
- return entrys.get(stationNo);
- }
- });
- },
- calcTimeDelta: function(firstStation, secondStation) {
- var e1 = this.has("entrys") && this.get("entrys").get(firstStation);
- var e2 = this.has("entrys") && this.get("entrys").get(secondStation);
- if (!e1 || !e2) {
- return null;
- } else {
- var t1 = e1.get("time_out") || e1.get("time_in");
- var t2 = e2.get("time_in") || e2.get("time_out");
- if (!t1 || !t2) { return null; }
- // If we got this far, we have two times and we need the delta between them
- return t2-t1;
- }
- },
- getTimeInAt: function(stationNo, format) {
- var e = this.has("entrys") && this.get("entrys").get(stationNo);
- if (!e) {
- return null;
- } else {
- var t = e.get("time_in");
- if (format) {
- format = this.collection.raceEvent.get("finishStation") == stationNo ? "HH:mm:ss" : "HH:mm";
- return t && t.toString(format);
- } else {
- return t;
- }
- }
- },
- getTimeOutAt: function(stationNo, format) {
- var e = this.has("entrys") && this.get("entrys").get(stationNo);
- if (!e) {
- return null;
- } else {
- var t = e.get("time_out");
- if (format) {
- format = this.collection.raceEvent.get("finishStation") == stationNo ? "HH:mm:ss" : "HH:mm";
- return t && t.toString(format);
- } else {
- return t;
- }
- }
- },
- getTimesForGrid: function(stationNo) {
- var evt = this.collection.raceEvent,
- finishStation = evt.get("finishStation");
- var e = this.has("entrys") && this.get("entrys").get(stationNo);
- if (!e) {
- return [null, null];
- } else {
- return [e.getGridTimeIn(finishStation == stationNo), e.getGridTimeOut()];
- }
- },
- getTimes: function(stationNo) {
- var e = this.has("entrys") && this.get("entrys").get(stationNo);
- if (!e) {
- return [null, null];
- } else {
- return [e.get("time_in"), e.get("time_out")];
- }
- },
- isDroppedAt: function(stationNo) {
- return (this.get("dnfStation") == stationNo);
- },
- isDroppedBy: function(stationNo) {
- var dnfStation = this.get("dnfStation");
- if (dnfStation || dnfStation === 0) {
- return dnfStation <= stationNo;
- } else { return false; }
- },
- getLastSeenAt: function() {
- if (!this.has("entrys")) {
- return false;
- } else {
- return this.get("entrys").max(function(e) { return e.hasValidTime() ? e.get("station_stationNumber") : false; });
- }
- },
- isExpectedAt: function(stationNo) {
- var evt = this.collection.raceEvent,
- startStation = evt.get("startStation");
- if (this.isDropped() || !this.has("entrys")) {
- return false;
- }
- var entrys = this.get("entrys");
- if (!entrys.get(startStation)) {
- return false;
- } else {
- return !entrys.any(function(e) {
- return e.get("station_stationNumber") >= stationNo && e.hasValidTime();
- });
- }
- },
- wasMissedAt: function(stationNo) {
- var evt = this.collection.raceEvent,
- startStation = evt.get("startStation");
- if (!this.has("entrys")) {
- // To be "missed" you must have started the race
- return false;
- }
- var entrys = this.get("entrys");
- var cEntry = entrys.get(stationNo);
- if (cEntry && cEntry.hasValidTime()) {
- // They have been seen at the given station so they weren't missed
- return false;
- } else if (this.get("dnfStation") && this.get("dnfStation") > stationNo) {
- return true;
- } else {
- return entrys.any(function(e) {
- return e.get("station_stationNumber") >= stationNo && e.hasValidTime();
- });
- }
- },
- hasBadDataAt: function(stationNo) {
- var curEntry = this.getStationEntry(stationNo),
- prevEntry = curEntry && curEntry.prevStationEntry,
- curIn = curEntry && curEntry.timeIn,
- curOut = curEntry && curEntry.timeOut,
- prevIn = prevEntry && prevEntry.timeIn,
- prevOut = prevEntry && prevEntry.timeOut,
- // when comparing to the previous station, prefer current in over current out
- // but previous out over previous in
- curStationTime = curIn || curOut,
- prevStationTime = prevOut || prevIn;
- // "bad data" means that the time out is earlier than the time in,
- // or the time at this station is BEFORE the time at the previous station.
- if ((curOut && curIn && curOut < curIn) || (curStationTime && prevStationTime && curStationTime <= prevStationTime)) {
- return true;
- }
- // if we didn't find bad data, it must be good!
- return false;
- },
- wasSeenAt: function(stationNo) {
- if (!this.has("entrys")) {
- return false;
- }
- var entry = this.get("entrys").get(stationNo);
- return !!(entry && entry.hasValidTime());
- },
- isDropped: function() {
- return (this.get("dnfStation") || this.get("dnfStation") === 0);
- },
- getEventId: function() {
- return this.collection.eventId;
- },
- getSex: function() {
- // This is ugly, but we'll default to assuming "male" unless known otherwise
- return String(this.get("sex")).toUpperCase() == "F" ? "F" : "M";
- },
- validate: function(attrs) {
- var self = this;
- var col = this.collection;
- attrs.bibNumber = Number(attrs.bibNumber || this.id);
- attrs.age = Number(attrs.age || this.get("age"));
- if ("firstName" in attrs && !attrs.firstName) {
- return "Please enter a first name";
- } else if ("lastName" in attrs && !attrs.lastName) {
- return "Please enter a last name";
- }
- if ("entrys" in attrs) {
- if (this.get("entrys")) {
- this.get("entrys").unbind("change");
- }
- attrs.entrys.bind("change", function(entry) { self.trigger("change", self, entry); });
- }
- },
- clearPositionDelta: function() {
- this._prevStation = null;
- },
- setPosition: function(sNo, position, distance, calcDelta) {
- var self = this;
- if (!self.positionData) {
- self.positionData = {};
- }
- var pdata = self.positionData,
- prevStation = self._prevStation,
- prevDistance = self._prevDistance,
- delta = null,
- timeDelta = null,
- distDelta = null;
- if (prevStation !== null && calcDelta && !self.wasMissedAt(prevStation)) {
- delta = self.getPosition(prevStation);
- timeDelta = self.calcTimeDelta(prevStation, sNo);
- if (delta || delta === 0) {
- delta = position - delta;
- }
- if (prevDistance !== null) {
- distDelta = parseFloat(distance) - prevDistance;
- }
- }
- if (calcDelta) {
- self._prevStation = sNo;
- self._prevDistance = parseFloat(distance) || null;
- }
- pdata[sNo] = {
- pos: position,
- delta: delta,
- timeDelta: timeDelta,
- distDelta: distDelta || null
- };
- },
- getPosition: function(sNo) {
- if (this.positionData && this.positionData[sNo]) {
- var p = this.positionData[sNo].pos;
- if (p || p === 0) {
- return p;
- } else {
- return "N/A";
- }
- } else {
- return null;
- }
- },
- getPositionDelta: function(sNo) {
- if (this.positionData && this.positionData[sNo]) {
- var d = this.positionData[sNo].delta;
- if (d || d === 0) {
- return d;
- } else {
- return "N/A";
- }
- }
- },
- clearStationEntries : function() {
- this.stationEntries = {};
- this._prevStationEntry = null;
- },
- setStationEntry: function(sNo) {
- var timeIn = this.getTimeInAt(sNo),
- timeOut = this.getTimeOutAt(sNo),
- stationEntry = (timeIn || timeOut) ? {timeIn : timeIn, timeOut: timeOut, prevStationEntry: this._prevStationEntry} : null;
- if (stationEntry) {
- this.stationEntries[sNo] = this._prevStationEntry = stationEntry;
- }
- },
- getStationEntry: function(sNo) {
- return this.stationEntries && this.stationEntries[sNo];
- },
- isFallingBehind: function(sNo, scale) {
- var t = this.getPositionDelta(sNo);
- if (!t || t == "N/A") { return; }
- var d = t / scale;
- if (d < 0) {
- return 0;
- } else if (d > 10) {
- return 10;
- } else {
- return Math.floor(d);
- }
- },
- getPace: function(sNo) {
- if (this.positionData && this.positionData[sNo]) {
- var pos = this.positionData[sNo].timeDelta;
- var hour = pos / 1000 / 60 / 60;
- var dist = this.positionData[sNo].distDelta;
- if (pos || pos === 0 && dist) {
- return Math.floor(dist / hour * 10) / 10;
- } else {
- return null;
- }
- }
- return null;
- },
- getTimeDelta: function(sNo) {
- if (this.positionData && this.positionData[sNo]) {
- var d = this.positionData[sNo].timeDelta;
- if (d || d === 0) {
- return d;
- } else {
- return null;
- }
- }
- return null;
- },
- getFormattedTimeDelta: function(sNo) {
- var td = this.getTimeDelta(sNo);
- if (td === null) { return ""; }
- var seconds = td / 1000;
- var hours = Math.floor(seconds / 60 / 60);
- var minutes = Math.floor(seconds / 60) % 60;
- seconds = Math.floor(seconds % 60);
- hours += "";
- minutes += "";
- seconds += "";
- if (minutes.length == 1) { minutes = '0' + minutes; }
- if (seconds.length == 1) { seconds = '0' + seconds; }
- return hours + ":" + minutes + ":" + seconds;
- }
- }, {
- fetchById: function(event_id, id) {
- return $.when(Participants.fetchAll(event_id)).pipe(function(list) {
- return list.get(id);
- });
- },
- swap: function(eventId, source, dest) {
- var data = {
- source: source, dest: dest
- };
- var params = {
- "url": "/newAPI/events/" + eventId + "/participants/swap",
- "data": data,
- "type": "POST"
- };
- return $.ajax(params);
- }
- });
- Participants = Backbone.Collection.extend({
- model: Participant,
- url: function() {
- return "/newAPI/events/" + this.eventId + "/participants";
- },
- calculateResults: function() {
- var self = this;
- self.each(function(r) {
- r.clearPositionDelta();
- r.clearStationEntries();
- });
- // Be sure this never gets called before RaceEvent has finished loading
- var raceEvent = this.raceEvent;
- var startStation = raceEvent.get("startStation");
- var finishStation = raceEvent.get("finishStation");
- if ((!startStation && startStation !== 0) ||
- (!finishStation && finishStation !== 0)) {
- return new $.Deferred().reject("No start/finish station"); // Can't calculate race results without a start and finish station
- }
- return $.when(Station.list.fetchAll(self.eventId)).pipe(function(stations) {
- // Now we need to loop over the stations and calculate positions
- var results = {};
- stations.each(function(station) {
- var sNo = station.id;
- function sortFunc(ao, bo) {
- var a = ao.getRaceTime(startStation, sNo);
- var b = bo.getRaceTime(startStation, sNo);
- if (a == b) { return 0; }
- else { return a > b ? 1 : -1; }
- }
- results[sNo] = self.toArray();
- results[sNo].sort(sortFunc);
- });
- _.each(results, function(sortedList, sNo) {
- if (sNo == startStation) {
- return;
- }
- var last = TIME_MISSING, place = 1, nextplace = 0;
- _.each(sortedList, function(runner, position) {
- var rtime = runner.getRaceTime(startStation, sNo);
- if (runner.isDroppedBy(sNo)) {
- runner.setPosition(sNo, "DNF", stations.get(sNo).get('distance'));
- } else {
- if (last == rtime) {
- ++nextplace;
- } else {
- ++nextplace;
- place = nextplace;
- }
- runner.setStationEntry(sNo);
- runner.setPosition(sNo, place, stations.get(sNo).get('distance'), true);
- last = rtime;
- }
- if (sNo == finishStation) {
- runner.time = rtime;
- runner.place = runner.getPosition(sNo);
- }
- });
- });
- });
- },
- calculateResults_old: function() {
- var self = this;
- // Be sure this never gets called before RaceEvent has finished loading
- var raceEvent = this.raceEvent;
- var startStation = raceEvent.get("startStation");
- var finishStation = raceEvent.get("finishStation");
- if ((!startStation && startStation !== 0) ||
- (!finishStation && finishStation !== 0)) {
- return; // Can't calculate race results without a start and finish station
- }
- self.each(function(runner) {
- if (!runner.has("entrys")) {
- runner.time = TIME_MISSING;
- } else {
- var start_time = runner.getTimeOutAt(startStation);
- var end_time = runner.getTimeInAt(finishStation);
- if (!start_time || !end_time) {
- runner.time = TIME_MISSING;
- } else {
- runner.time = end_time - start_time;
- }
- }
- });
- var presort = self.toArray();
- function sortFunc(ao, bo) {
- var a = ao.time, b = bo.time;
- if (a == b) { return 0; }
- else { return a > b ? 1 : -1; }
- }
- var last = TIME_MISSING, place = 1, nextplace = 0;
- presort.sort(sortFunc);
- _.each(presort, function(runner) {
- if (runner.isDropped()) {
- runner.place = "DNF";
- } else if (runner.time == TIME_MISSING) {
- runner.place = null;
- } else {
- if (last == runner.time) {
- ++nextplace;
- } else {
- ++nextplace;
- place = nextplace;
- }
- runner.place = place;
- last = runner.time;
- }
- });
- return true;
- },
- getSortedList: function(sortFunc) {
- var arr = this.toArray();
- arr.sort(sortFunc);
- return arr;
- },
- getEntrys: function() {
- var self=this, ids = self.pluck("bibNumber"), numComplete = 0;
- if (self.getEntrysDfd) {
- return self.getEntrysDfd;
- }
- function downloadEntryList(elist) {
- var Entry = require("models/Entry");
- var params = {
- "url": "/newAPI/events/" + self.eventId + "/entrys/range",
- "contentType": "application/json",
- "data": JSON.stringify(elist),
- "type": "POST"
- };
- return $.ajax(params).pipe(function(res) {
- var map = {};
- numComplete += elist.length;
- _.each(res, function(entry) {
- if (!map[entry.participant_bibNumber]) { map[entry.participant_bibNumber] = []; }
- map[entry.participant_bibNumber].push(entry);
- });
- _.each(map, function(list, bibNumber) {
- var nl = new (Entry.list)(Entry.list.parse(list));
- nl.runnerNo = bibNumber;
- nl.eventId = self.eventId;
- self.get(bibNumber).set({entrys: nl});
- });
- self.trigger("entryDownload", numComplete, self.length);
- return true;
- });
- }
- var i,j,dfdResults=[],temparray,chunk = 25;
- for (i=0,j=ids.length; i<j; i+=chunk) {
- temparray = ids.slice(i,i+chunk);
- dfdResults.push(downloadEntryList(temparray));
- }
- self.getEntrysDfd = $.when.apply(null, dfdResults);
- return self.getEntrysDfd;
- }
- }, {
- list: null,
- fetchAll: function(eventId) {
- var self = this, cache;
- if (!self.list) { self.list = {}; }
- cache = self.list[eventId];
- if (cache) {
- return cache;
- }
- var tmp = new self();
- tmp.eventId = eventId;
- cache = self.list[eventId] = tmp.fetch().pipe(function() {
- // We end up needing the RaceEvent object often enough it's worth cacheing here
- var RaceEvent = require("models/RaceEvent");
- self.list[eventId] = tmp;
- return RaceEvent.obj.fetchById(eventId).pipe(function(raceEvent) {
- tmp.raceEvent = raceEvent;
- return tmp;
- });
- });
- return cache;
- }
- });
- return {
- obj: Participant,
- list: Participants
- };
- });