Backbone.sync = function(method, model, success, error){ success(); } //add a function to underscore to get index with a truth function _.mixin({ indexOfElement : function(array,filter){ return array.reduce(function(a,e,i){ if ((a<0) && filter(e)) return i; return a; },-1); } }); function DataSetPoint(latitude, longitude, time, name, speed, altitude, address, iconName, hasVoice, hasText, isInCall ){ this.id = name; this.latitude = latitude; this.longitude = longitude; this.time = time; this.name = name; this.speed = speed; this.altitude = altitude; this.address = address; this.icon = iconName; this.hasVoice = hasVoice; this.hasText = hasText; this.isInCall = isInCall; DataSetPoint.prototype.merge = function(map){ _.extend(this,map); return this; } } var MapProperties = Backbone.Model.extend({ defaults:{ dimmensions: [300,300], isReset:false, mapType : 'terrain', openedMarker:'', center : [44.423361, 26.093608], zoom : 20, console: false, traffic:false, polygonToGet : {name : ''}, //this is an object so you can repeatedly ask for the same polygon name //the following are not used yet polyPointCallback : function(point){}, getPolygonCallback : function(points){} } }); var DataSet = Backbone.Model.extend({ defaults:{ id:null, points:[], isVisible:false, isAutosize:true, lastUpdated:new Date(), isShowLabels : true }, initialize: function(){ console.log('initializing dataset '+this.get('id')); }, updatePoints : function(newPoints){ var self = this; newPoints.forEach(function(p){ var index = _(self.get('points')).indexOfElement(function(elem){ return (elem.id == p.id); }); if (index >= 0) self.get("points").splice(index,1); p.dataset = self.get('id'); self.get("points").push(p); // console.dir(p); }); this.set("lastUpdated", new Date()); // console.log('updated points on dataset:'+this.get("id") + ". There are " + this.get('points').length + " points."); }, removePoint : function(pointName){ var point = _(this.get('points')).findWhere({name:pointName}); var pointIndex = this.get('points') .reduce(function(a,e,i){ if (!a && e.id === pointName) return i; return a},null); this.get('points').splice(pointIndex,1); // console.log('Removed id = '+pointName+' from index = '+pointIndex); // console.dir(this.get('points')); this.set("lastUpdated", new Date()); } }); var Polyline = DataSet.extend({ defaults : { color: "blue", thickness : 0.4, isPolyline : true } }); var Polygon = DataSet.extend({ defaults : { isNew: false, isEditing : false, isPolyline : false }, initialize : function(){ this.set('isPolyline',false); } }); var PolygonCollection = Backbone.Collection.extend({ model : Polygon }); var DataSetCollection = Backbone.Collection.extend({ model:DataSet }); /**************************************************************/ /* I n f o W i n d o w V I E W a n d M O D E L */ /**************************************************************/ var InfoWindowModel = Backbone.Model.extend({ defaults : { id:"info point", latitude : 23.4332, longitude : -32.234, time : new Date(), name : 'abcs', speed : '23kph', altitude : '3 m', address : 'bld. Marasesti, Bucuresti, sector 4', hasStreetView : false, hasVoice : 0, hasText : 0, isInCall : false }, mergePoint : function(dataSetPoint){ console.log("InfoWindowModel: Merge point "+dataSetPoint.id+" with incall="+dataSetPoint.isInCall); Object.keys(dataSetPoint).forEach(function(p){ this.set(p,dataSetPoint[p]); }, this); } }); var InfoWindowView = Backbone.View.extend({ // el : '#info', elId : '#info', // template : _.template($('#info').html()), //no params at template events : { "mousedown .name img" : "computePttState", "mouseup .name img" : "computePttState", "dragend .name img" : "computePttState", "keypress input" : "onKeyPress" }, pttState : 'neutral', //see the diagram in docs/PTT_states.png setPttState : function(state){ if (state == 'ask-ptt') this.ptt(true); if (state == 'call-done') this.ptt(false); var states = { 'neutral' : "r_ptt.png", //blue 'ask-ptt' : "r_ptt_over.png", //green 'in-call' : "r_ptt_over.png", //green, + dynamic 'call-failed': "r_ptt_end.png", //red 'call-done' : "r_ptt_hang.png", //yellow 'called' : "r_ptt_incoming.png" //gray +dynamic } var imgState = states[state]; this.$el.find(".name img[alt='PTT']").attr('src','radio/'+imgState); this.animatePTT((['in-call','called'].indexOf(state)>-1)); this.pttState = state; // console.log("Set PTT state to:"+state); }, ptt : function(isOn){ // if (!isOn) this.model.attributes.isInCall = false; var state = isOn?"on":"off"; this.options.fastCommandCallback && this.options.fastCommandCallback('ptt', this.model.get('id'),state); }, onKeyPress : function(event) { if (event.keyCode === 13) { this.options.fastCommandCallback && this.options.fastCommandCallback('text',this.model.get('id'),event.target.value); event.target.value = ''; return false; }else if (this.$el.find('form input').val().length >= this.model.get('hasText') && !(event.keyCode in [46, 8, 39, 37])) //delete, backspace, left and right arrows return false; }, isOpened : function(){return this.$el.is(":visible");}, initialize : function(options){ // this.$el.html(this.template()); this.$el.html($(this.elId).html()); this.$el.addClass('info'); this.options = options; this.model.isOpened = this.isOpened.bind(this); this.model.on("change", this.update, this); // this.update(); this.$el.hide(); //reset PTT state if InfoWindow is closing //TODO: clear it when state is neutral var self = this; setInterval(function(){ if (!self.isOpened()) self.setPttState('neutral'); },1000); }, animatePTT : function(start){ var element = this.$el.find(".name .ptt-anim"); if (start){ this.animatePTT.interval && clearInterval(this.animatePTT.interval); this.animatePTT.interval = setInterval(function(){ var pos = /_(\d{2})/.exec(element.attr('src'))[1]; pos = parseInt(pos); pos = pos>12?1:pos+1; var nextId = (pos>9?"":"0")+pos; element.attr('src','radio/white_r_radial_spectrum_'+nextId+'.png'); },200); }else{ this.animatePTT.interval && clearInterval(this.animatePTT.interval); element.attr('src','radio/r_radial_spectrum_00.png'); } }, update : function(){ var changedAttrs = Object.keys(this.model.changedAttributes()); var changedProp = changedAttrs.length && changedAttrs[0]; // console.log("---------------------Prop that changed = " + changedProp) this.$el.show(); var p = this.model; var latlng = "["+p.get('latitude')+","+p.get('longitude')+"]"; this.$el.find("#name").text(p.get('id').toUpperCase()); this.$el.find(".position").text(latlng); var mom = moment(p.get('time')); var time = mom.fromNow() + "   ["+mom.format('hh:mm:ss')+"]"; var speed = p.get('speed') && p.get('speed').match(/\d+|\w+/g).join(' '); this.$el.find("#time span").html(time); this.$el.find("#speed span").text(speed); var altitude = p.get('altitude')+""; var altElement = this.$el.find('#altitude'); if (!altitude.length || altitude === '0') altElement.hide(); else altElement.find('span').text(altitude); var adrEl = this.$el.find('#address'); if (p.get('address').length == 0) p.attributes.address = latlng; adrEl.find('a').remove(); var imgSource = 'images/i_street.jpg'; if (!p.get('hasStreetView')){ adrEl.find('span').text(p.get('address')); imgSource = 'images/i_street_d.jpg' }else{ adrEl.find('span').text(''); adrEl.find('span').append($('',{href :"javascript:google.maps.openStreetView()",text:p.get('address')})); } adrEl.find('img').attr('src',imgSource); //hide voice and/of text if necessary [{".name img[alt='PTT']" : "hasVoice"},{"form" : "hasText"}].forEach(function(o){ var selector = Object.keys(o)[0]; var method = this.model.get(o[selector])?"show":"hide"; this.$el.find(selector)[method](); },this); if (changedProp==='isInCall') this.computePttState(); }, computePttState : function(event){ // console.log("========update with isInCall = "+this.model.get('isInCall') + ' for '+this.model.get('id') + " ptt state = " + this.pttState + " event= "+(event && event.type)); var setPttState = this.setPttState.bind(this); var type = (event && event.type) || "none"; if (this.pttState == 'neutral'){ if (this.model.get('isInCall')) setPttState('called'); else setPttState('neutral'); } if ((this.pttState == 'called' && !this.model.get('isInCall')) || ((this.pttState == 'in-call' || this.pttState == 'ask-ptt') && (type =='mouseup' || type == 'dragend'))){ setPttState('call-done'); this.computePttState.askPttTimeout && clearTimeout(this.computePttState.askPttTimeout); this.computePttState.callDoneTimeout = setTimeout(function(){setPttState('neutral'); },2000); return; } var callFailed = function(){setPttState('call-failed'); setTimeout(function(){setPttState('neutral');}, 2000);} if (this.pttState == 'neutral' && type=='mousedown') { setPttState('ask-ptt'); this.computePttState.askPttTimeout = setTimeout(function(){ callFailed(); },this.model.get('hasVoice')*1000); return; } if (this.pttState == 'ask-ptt' ){ this.computePttState.askPttTimeout && clearTimeout(this.computePttState.askPttTimeout); if (this.model.get('isInCall')) setPttState('in-call'); else callFailed(); return; } if (this.pttState == 'call-done' && this.model.get('isInCall')){ this.computePttState.callDoneTimeout && clearTimeout(this.computePttState.callDoneTimeout); setPttState('called'); return; } } });