"HTML Live tracker"
Bootstrap 3.3.0 Snippet by rphlo

<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css"> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script> <script src="//code.jquery.com/jquery-1.11.1.min.js"></script> <!------ Include the above in your HEAD tag ----------> <div class="container"> <div class="row"> <h2>Live Tracker</h2> <p> <a role="button" class="btn btn-default" data-toggle="modal" data-target="#settings-modal" id="settings-btn">Settings</a> </p> <hr/> <p> <a role="button" class="btn btn-primary" id="select-competitor-btn" data-toggle="modal" data-target="#select-competitor-modal" > Select Competitor </a> </p> <div class="alert" id="competitor-info"></div> <p> <a role="button" class="btn btn-danger disabled" id="start-btn">Start</a> <a role="button" class="btn btn-danger disabled" id="schedule-start-btn" data-toggle="modal" data-target="#schedule-start-modal">Schedule Start</a> </p> <hr/> <div id="app-status" class="alert" role="alert"></div> </div> </div> <div class="modal fade" id="settings-modal" tabindex="-1" role="dialog" aria-labelledby="settings-modal-label" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="settings-modal-label">Settings</h4> </div> <div class="modal-body"> <div class="alert alert-warning error-message" id="settings-errors"></div> <div class="form-group"> <label for="server-address-input">Server address</label> <input type="text" class="form-control" id="server-address-input" placeholder="http://127.0.0.1:8000/"> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> <button type="button" class="btn btn-primary" id="save-settings-btn">Save</button> </div> </div> </div> </div> <div class="modal fade" id="schedule-start-modal" tabindex="-1" role="dialog" aria-labelledby="schedule-start-modal-label" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="schedule-start-modal-label">Schedule start</h4> </div> <div class="modal-body"> <div class="alert alert-warning error-message" id="schedule-start-errors"></div> <div class="form-group"> <label for="server-address-input">Minutes before start</label> <input type="integer" class="form-control" id="start-delay-input" placeholder="5" value="5"> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> <button type="button" class="btn btn-primary" id="save-schedule-start-btn">Save</button> </div> </div> </div> </div> <div class="modal fade" id="streaming-modal" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="static" data-keyboard="false"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h4 class="modal-title" id="settings-modal-label">Streaming Position</h4> </div> <div class="modal-body"> <div id="streamin-info-div"></div> </div> <div class="modal-footer"> <button type="button" class="btn btn-danger" id="stop-streaming-btn">Stop</button> </div> </div> </div> </div> <div class="modal fade" id="select-competitor-modal" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="select-competitor-pane" id="select-competitor-pane-1"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="select-competitor-modal-label">Select Competition</h4> <button class="btn btn-default" id="refresh-competition-list-btn">Refresh</button> </div> <div class="modal-body" id="competition-list"> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> </div> </div> <div class="select-competitor-pane" id="select-competitor-pane-2"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="select-competitor-modal-label">Select Competitor</h4> <button class="btn btn-default" id="refresh-competitor-list-btn">Refresh</button> </div> <div class="modal-body" id="competitor-list"> </div> <div class="modal-footer"> <button type="button" class="btn btn-default back-pane-btn" id="back-pane-btn-1">Back</button> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> </div> </div> <div class="select-competitor-pane" id="select-competitor-pane-3"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="select-competitor-modal-label">Enter Access code</h4> </div> <div class="modal-body" id="access-code-form"> <div class="alert alert-warning error-message" id="access-code-errors"></div> <div class="form-group"> <label for="access-code-input">Access Code</label> <input type="password" class="form-control" id="access-code-input" placeholder="12345"> </div> <div> <button type="button" class="btn btn-primary" id="validate-access-code-btn">Submit</button> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default back-pane-btn" id="back-pane-btn-2">Back</button> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> </div> </div> </div> </div> </div> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jStorage/0.4.12/jstorage.min.js"></script>
var DEFAULT_SERVER_ADDRESS = 'http://127.0.0.1:8000/'; var tmp_competition = null; var tmp_competitor = null; var watchPositionId = null; var isGpsSwitchedOn = false; var setStatus = function(message, type){ $("#app-status").text(message); if(type != "success" && type != "info" && type != "warning" && type != "danger"){ type = "info"; } $("#app-status") .removeClass("alert-success alert-info alert-warning alert-danger") .addClass("alert-"+type); }; var onServerUrlChange = function(){ fetchServerTime(); fetchCompetitionList(); }; var getServerUrl = function(){ return $.jStorage.get('server-address', DEFAULT_SERVER_ADDRESS); }; var setServerUrl = function(url){ $.jStorage.set('server-address', url); }; var getServerDrift = function(){ return $.jStorage.get('server-drift', 0); }; var setServerDrift = function(timeRequest, timeServer, timeResponse){ var drift = timeServer - (timeResponse + timeRequest) / 2; $.jStorage.set('server-drift', drift); }; var getServerTime = function(){ return +(new Date())+getServerDrift(); }; var setCompetitionList = function(data){ $.jStorage.set('competition-list', data); }; var getCompetitionList = function(){ return $.jStorage.get('competition-list', []); }; var setCompetitorList = function(data){ $.jStorage.set('competitor-list', data); }; var getCompetitorList = function(){ return $.jStorage.get('competitor-list', []); }; var setCompetitionDetail = function(competition){ $.jStorage.set('competition-id', competition.id); $.jStorage.set('competition-name', competition.name); $.jStorage.set('competition-live_delay', competition.live_delay); }; var getCompetitionDetail = function(){ return { id: $.jStorage.get('competition-id', null), name: $.jStorage.get('competition-name', null), live_delay: $.jStorage.get('competition-live_delay', 30) }; }; var setCompetitorDetail = function(competitor){ $.jStorage.set('competitor-id', competitor.id); $.jStorage.set('competitor-name', competitor.name); $.jStorage.set('competitor-token', competitor.token); }; var getCompetitorDetail = function(){ return { id: $.jStorage.get('competitor-id', null), name: $.jStorage.get('competitor-name', null), token: $.jStorage.get('competitor-token', null) } }; var saveSettings = function(){ errors = []; var url = $('#server-address-input').val(); if(!url){ url = getServerUrl(); } addressRe = /^https?:\/\/.*\/?$/i; if(!url.match(addressRe)){ errors.push("Invalid Server Address"); } else { if(url[url.length-1] != "/"){ url = url + "/"; } } if(errors.length > 0){ $('#settings-errors').html('<ul><li>'+errors.join('</li><li>')+"</li></ul>"); $('#settings-errors').show(); } else { $('#settings-errors').hide(); $('#settings-modal').modal('hide'); if(url != getServerUrl()){ setServerUrl(url); onServerUrlChange(); } } }; var fetchServerTime = function(){ setStatus('Fetch server time'); var url = getServerUrl()+"api/time"; var timeReq = +(new Date()); $.ajax({ url: url }) .success(function(response){ var timeServer = response.time*1e3; var timeResp = +(new Date()); setServerDrift(timeReq, timeServer, timeResp); var now = new Date(getServerTime()); setStatus('Server time fetched '+now, 'success'); }) .error(function(response){ setStatus('Error fetching server time', 'danger'); }); }; var fetchCompetitionList = function(url){ setStatus('Fetch competition List'); url = url || getServerUrl()+"api/competition"; $.ajax({ url: url, data: { status: ['live', 'upcoming'], reverse_order: 'true', result_per_page: 1000 } }).success(function(response){ var results; if(response.previous === null){ results = [] }else{ results = getCompetitionList() } results = results.concat(response.results) setCompetitionList(results); if(response.next===null){ setStatus('Competition list fetched', 'success'); displayCompetitionList(); } else { fetchCompetitionList(response.next); } }).error(function(){ setStatus('Error fetching the competition list', 'danger'); }); }; var fetchCompetitorList = function(url){ setStatus('Fetch competitor List'); competition = tmp_competition; url = url || getServerUrl()+"api/competitor"; $.ajax({ url: url, data: { competition_id: competition.id, result_per_page: 1000 } }).success(function(response){ var results; if(response.previous === null){ results = [] }else{ results = getCompetitorList() } results = results.concat(response.results) setCompetitorList(results); if(response.next===null){ setStatus('Competitor list fetched', 'success'); displayCompetitorList(); } else { fetchCompetitorList(response.next); } }).error(function(){ setStatus('Error fetching the competitor list', 'danger'); }); }; var displayCompetitionList = function(){ var competitions = getCompetitionList(); $('#competition-list').html('') if(competitions.length===0){ $('#competition-list').append($('<p class="alert alert-warning">No competitions are available</p>')); return; } $('#competition-list').append('<table class="table table-striped"/>') $.each(competitions, function(ii, competition){ var link = $('<td><a href="#">'+competition.name+'</a></td>'); link.on('click', function(){ tmp_competition = competition; fetchCompetitorList(); $('.select-competitor-pane').hide(); $('#select-competitor-pane-2').show(); }) $('#competition-list > table').append($('<tr/>').append(link)); }); }; var displayCompetitorList = function(){ var competitors = getCompetitorList(); $('#competitor-list').html('') if(competitors.length===0){ $('#competitor-list').append($('<p class="alert alert-warning">No competitors are available</p>')); return; } $('#competitor-list').append('<table class="table table-striped"/>') $.each(competitors, function(ii, competitor){ var link = $('<td><a href="#">'+competitor.name+'</a></td>'); link.on('click', function(){ tmp_competitor = competitor; $('.select-competitor-pane').hide(); $('.error-message').hide(); $('#access-code-input').val(''); $('#select-competitor-pane-3').show(); }) $('#competitor-list > table').append($('<tr/>').append(link)); }); }; var isCompetitorSet = function(){ var competition = getCompetitionDetail(); var competitor = getCompetitorDetail(); return (competition.id !== null || competitor.id !== null); }; var displayCompetitor = function(){ if(!isCompetitorSet()){ $("#competitor-info").text("No competitor selected") .removeClass('alert-success') .addClass('alert-warning'); return; } var competition = getCompetitionDetail(); var competitor = getCompetitorDetail(); $("#competitor-info") .text('"'+ competitor.name +'" in competition "'+ competition.name + '"') .removeClass('alert-warning') .addClass('alert-success'); $('#start-btn').removeClass('disabled'); $('#schedule-start-btn').removeClass('disabled'); }; var validateAccessCode = function(){ var accessCode = $('#access-code-input').val(); if(accessCode==""){ $('#access-code-errors') .text('Access code can not be blank.') .show(); return } var competitorId = tmp_competitor.id; var url = getServerUrl() + 'api/competitor_token/obtain'; setStatus('Fetching competitor token'); $.ajax({ url: url, data: { competitor: competitorId, access_code: accessCode }, type: 'POST' }).success(function(response){ setStatus('Competitor token fetched', 'success'); tmp_competitor.token = response.token; setCompetitorDetail(tmp_competitor); setCompetitionDetail(tmp_competition); displayCompetitor(); $('#select-competitor-modal').modal('hide'); $('.select-competitor-pane').hide(); $('#select-competitor-pane-1').show(); }).error(function(){ setStatus('Failed to fetch competitor token', 'danger'); $('#access-code-errors') .text('Access code did not match selected competitor') .show(); }); }; var onPressStart = function(){ if(!isCompetitorSet()){ return; } console.log('start'); $('#streaming-modal').modal('show'); startStreaming(); }; var startStreaming = function(){ isGpsSwitchedOn = true; watchPositionId = navigator.geolocation.watchPosition( onPositionUpdate, onPositionError, { enableHighAccuracy:true, timeout:999, maximumAge:1000 } ); (function positionRequestor(){ navigator.geolocation.getCurrentPosition( onPositionUpdate, onPositionError, { enableHighAccuracy:true, timeout:999, maximumAge:1000 } ); if(isGpsSwitchedOn){ setTimeout(positionRequestor, 1e3); } })(); (function positionArchivePusher(){ pushPositionArchive(); if(isGpsSwitchedOn){ setTimeout(positionArchivePusher, 1e3); } })(); }; var pushPositionArchive = function(force){ force = force || false; // Push data when Archive max age == competition delay -5s }; var stopStreaming = function(){ isGpsSwitchedOn = false; pushPositionArchive(true); navigator.geolocation.clearWatch(watchPositionId); $('#streaming-modal').modal('hide'); } var onPositionUpdate = function(position){ console.log(position) } var onPositionError = function(){ console.log("position erro") } $(function() { $('.error-message').hide(); setInterval(fetchServerTime, 5*60*1e3); fetchServerTime(); fetchCompetitionList(); $('#select-competitor-btn').on('click', function(){ $('.select-competitor-pane').hide(); $('#select-competitor-pane-1').show(); }); $('#back-pane-btn-1').on('click', function(){ $('.select-competitor-pane').hide(); $('#select-competitor-pane-1').show(); }); $('#back-pane-btn-2').on('click', function(){ $('.select-competitor-pane').hide(); $('.error-message').hide(); $('#select-competitor-pane-2').show(); }); $('#validate-access-code-btn').on('click', validateAccessCode); $('#save-settings-btn').on('click', saveSettings); $('#start-btn').on('click', onPressStart); displayCompetitor(); $('#refresh-competition-list-btn').on('click', fetchCompetitionList); $('#refresh-competitor-list-btn').on('click', fetchCompetitorList); $('#stop-streaming-btn').on('click', stopStreaming); });

Related: See More


Questions / Comments: