Ext.ns("Ext.ux.grid");
Ext.ux.grid.GridMouseEvents = (function() {
function onMouseOver(e) {
processMouseOver.call(this, 'mouseenter', e);
}
function onMouseOut(e) {
processMouseOver.call(this, 'mouseleave', e);
}
function processMouseOver(name, e){
var t = e.getTarget(),
r = e.getRelatedTarget(),
v = this.view,
header = v.findHeaderIndex(t),
hdEl, row, rowEl, cell, fromCell, col;
if(header !== false){
hdEl = Ext.get(v.getHeaderCell(header));
if (!((hdEl.dom === r) || hdEl.contains(r))) {
this.fireEvent('header' + name, this, header, e, hdEl);
}
}else{
row = v.findRowIndex(t);
if(row !== false) {
rowEl = Ext.get(v.getRow(row));
if (!((rowEl.dom === r) || rowEl.contains(r))) {
this.fireEvent('row' + name, this, row, e, rowEl);
}
cell = v.findCellIndex(t);
if(cell !== false){
cellEl = Ext.get(v.getCell(row, cell));
if (!((cellEl.dom === r) || cellEl.contains(r))){
this.fireEvent('cell' + name, this, row, cell, e, cellEl);
}
if (cell != v.findCellIndex(r)) {
col = this.colModel.getColumnAt(cell);
if (col.processEvent) {
col.processEvent(name, e, this, row, cell);
}
this.fireEvent('column' + name, this, cell, e);
}
}
}
}
}
function onGridRender() {
this.mon(this.getGridEl(), {
scope: this,
mouseover: onMouseOver,
mouseout: onMouseOut
});
}
return {
init: function(g) {
g.onRender = g.onRender.createSequence(onGridRender);
}
};
})();
/* Overlay and window dialog fix (unneccessary horizontal scrollbar)
* TODO: This bug affects Ext 3.2.0. This fix may be merged into head in a
* later release and then this file can be removed.
*
* */
Ext.lib.Dom.getViewWidth = function(){
var width = self.innerWidth; // Safari
var mode = document.compatMode;
if (mode || Ext.isIE) { // IE, Gecko, Opera
width = (mode == "CSS1Compat") ? document.documentElement.clientWidth : // Standards
document.body.clientWidth; // Quirks
}
return width;
}
Ext.ns('KZ.util');
/* This cannot be used to test an uninitialised variable, only fields of an object */
KZ.util.isSet = function(variable) {
return (typeof (variable)) != 'undefined';
}
KZ.util.hideVBoxLayoutItem = function(item) {
item.hide();
//item.flex = 0;
//item.setHeight(0);
}
KZ.util.showVBoxLayoutItem = function(item) {
item.show();
//item.flex = 1;
//item.setHeight(200);
}
KZ.util.isHidden = function(vBoxLayoutItem) {
return vBoxLayoutItem.getHeight() == 0
}
KZ.util.isDigit = function(num) {
if (num == undefined || num.length>1){return false;}
if (num == ""){return false;}
var string="1234567890";
if (string.indexOf(num)!=-1){return true;}
return false;
}
KZ.util.charCmp = function(a, b) {
return (b < a) - (a < b);
}
KZ.preloadedImagesArray = new Array();
KZ.util.preloadImages = function(imageSrcArray) {
for (var i = 0; i < imageSrcArray.length; i++){
KZ.preloadedImagesArray[i]= new Image();
KZ.preloadedImagesArray[i].src = imageSrcArray[i];
}
}
KZ.util.isEmptyObject = function (obj) {
for(var prop in obj) {
if(obj.hasOwnProperty(prop))
return false;
}
return true;
}
KZ.util.isArray = function (arr) {
if(arr != undefined && layoutNameArr instanceof Array) {
return true;
} else {
return false;
}
}
KZ.util.evalToJSON = function (str) {
try {
var evaluated = eval('(' + str + ')');
return evaluated;
} catch(err) {
KZ.log('Could not evaluate string to JSON. Reason: ' + err);
return {};
}
}
KZ.util.isStore = function(obj) {
if(obj && obj instanceof Ext.data.Store) {
return true;
} else {
return false;
}
}
KZ.util.isMarkerStore = function(obj) {
if(obj && obj instanceof KZ.MarkerHashMap) {
return true;
} else {
return false;
}
}
KZ.util.isFunction = function(obj) {
var toString = Object.prototype.toString;
return toString.apply(obj) === '[object Function]';
}
KZ.util.inArray = function( arr, elem ) {
for(var i = 0; i < arr.length; ++i) {
if(arr[i] == elem) {
return true;
}
}
return false;
}
String.prototype.bind = function() {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] !== 'undefined' ? args[number] : '{' + number + '}';
});
};
/** this file contains our ExtJs extensions **/
/* Available extensions (more details in the comments of the extension):
* - Ext.menu.DateTimeMenu
*/
/*
return {
panel : panelCalendar,
time : timeField,
date : datePicker
}
*/
var prepareCalendarFormPanel = function(parent, okCallback, nowCallback) {
var datePicker, panelCalendar, timeField;
panelCalendar = new Ext.FormPanel( {
labelWidth : 70,
width: 178,
ctCls : 'x-menu-date-item',
// bodyStyle:'padding:5px;',
items : [ datePicker = new Ext.DatePicker( {
hideLabel : true,
minDate : new Date(),
showToday : false
}),
timeField = new Ext.form.TimeField({
plain : true,
width : 100,
style : 'text-align:center',
fieldLabel : 'Time',
minValue : '0:00',
maxValue : '23:59',
format : 'H:i',
editable : false,
increment : 30,
value : new Date()
}) ],
buttons : [ {
text : 'OK',
handler : okCallback,
scope : parent
}, {
text : 'Now',
handler : nowCallback,
scope : parent
} ]
});
return {
panel : panelCalendar,
time : timeField,
date : datePicker
};
}; // prepare prepareCalendarFormPanel
/**
* config:
* void handler(pickedDate, isNowSet) - method invoked when OK clicked
*
* public methods:
* Date getPickedDateTime(); - last picked date time
* Date getSelectedDateTime(); - last selected date time (not necessarily is the same as in getPickedDateTime)
*/
Ext.menu.DateTimeMenu = Ext.extend(Ext.menu.Menu, {
now : true,
pickedDate : new Date(),
plain : true,
enableScrolling : false,
cls : 'dateTimePickerPanel',
initComponent : function() {
this.formPanel = prepareCalendarFormPanel(this, this.okButtonHandler,
this.nowButtonHandler);
this.on('beforeshow', this.onBeforeShow, this);
if (this.strict = (Ext.isIE7 && Ext.isStrict)) {
this.on('show', this.onShow, this, {
single : true,
delay : 20
});
}
Ext.apply(this, {
plain : true,
showToday : false,
showSeparator : false,
hideOnClick : false,
items : [ this.formPanel.panel ]
});
this.formPanel.date.purgeListeners();
Ext.menu.DateTimeMenu.superclass.initComponent.call(this);
this.relayEvents(this.formPanel.date, [ 'select' ]);
this.relayEvents(this.formPanel.time, [ 'select' ]);
this.on('show', this.formPanel.date.focus, this.formPanel.date);
this.on('beforehide', this.menuBeforeHide, this);
this.on('select', this.selectHandler, this);
},
refreshTimeForm : function(date) {
var day = date.clearTime(true);
if (day.getElapsed(new Date().clearTime()) === 0) {
var now = new Date();
var nearest = now.clearTime(true).add(Date.HOUR, now.format('H'));
// nearest min
var incr = this.formPanel.time.increment;
var nearestMinute = Math.ceil(now.format('i') / incr) * incr;
this.formPanel.time.setMinValue(nearest.add(Date.MINUTE, nearestMinute));
} else {
this.formPanel.time.setMinValue("0");
}
},
// private, checks if is date in the future
isValidDate : function(date) {
return date.getTime() > new Date().getTime();
},
// private, invoked when selected date or time
selectHandler : function() {
this.now = false;
var lastDate = this.getSelectedDateTime();
if (!this.isValidDate(lastDate)) {
this.toggleDate();
this.refreshTimeForm(new Date());
} else {
this.refreshTimeForm(lastDate);
}
},
// private
okButtonHandler : function() {
this.hide();
this.pickedDate = this.getSelectedDateTime();
if (this.handler) {
this.handler.call(this.scope || this, this.now, this.pickedDate
.clone());
}
},
// private
nowButtonHandler : function() {
this.formPanel.date.hideMonthPicker(false);
this.toggleDate();
},
// private, change date. when no param defined 'now' is set
toggleDate : function(d) {
if (!d) {
d = new Date();
this.now = true;
} else {
this.now = false;
}
this.formPanel.time.setValue(d);
this.formPanel.date.setValue(d);
},
menuBeforeHide : function(e) {
if (this.formPanel.time.isExpanded()) {
// do not hide date menu
return false;
}
},
onBeforeShow : function() {
if (this.formPanel.date) {
// reset defaults
this.formPanel.date.hideMonthPicker(true);
if (this.now || !this.isValidDate(this.pickedDate)) {
this.refreshTimeForm(new Date());
this.toggleDate();
} else {
this.refreshTimeForm(this.pickedDate);
this.toggleDate(this.pickedDate);
}
}
},
// private
onShow : function() {
var el = this.formPanel.date.getEl();
el.setWidth(el.getWidth());
},
// public
getSelectedDateTime : function() {
var day = this.formPanel.date.getValue().clearTime(true);
var time = this.formPanel.time.getValue().split(":");
return day.add(Date.HOUR, time[0]).add(Date.MINUTE, time[1]);
},
// public
getPickedDateTime : function() {
return this.pickedDate;
}
});
Ext.reg('datetimemenu', Ext.menu.DateTimeMenu);
Ext.ns('KZ');
KZ.log = function(message) {
_gaq.push(['_trackEvent', 'Error Log', message]);
if (typeof console == 'object' && console.warn) {
console.warn('KZDEBUG: ' + message);
} else if(KZ.Config.isProduction == false) {
alert(message);
}
}
KZ.error = function(message) {
_gaq.push(['_trackEvent', 'Critical error Log', message]);
if (typeof console == 'object' && console.error) {
console.error('KZDEBUG: [ERROR] ' + message);
}
if(KZ.Config.isProduction == false) {
alert(message);
}
}
Ext.ns('KZ');
// This is a lazily initialised singleton, not to be instantiated
KZ.InitialSearchPanel = new function() {
this.extComponent = undefined;
var searchField;
var initialSearchPanelHolder;
this.isInitialised = function() {
return this.extComponent != undefined;
}
this.isVisible = function() {
return this.isInitialised() && this.extComponent.isVisible();
}
this.performSearchAndHideInitialSearchPanel = function(searchTerm) {
KZ.searchBoxPanel.performSearch(searchTerm);
this.hideInitialSearchPanelAndShowMap();
}
this.hideInitialSearchPanelAndShowMapCentered = function() {
this.hideInitialSearchPanelAndShowMap();
KZ.mapPanel.getMap().setCenter(KZ.Config.initialMapLatitude, KZ.Config.initialMapLongitude, KZ.Config.initialMapZoom);
}
this.hideInitialSearchPanelAndShowMap = function() {
Ext.History.add("#map");
this.extComponent.hide();
KZ.viewport.showTravelMap();
searchField.blur();// IE bug: Prevent blinking cursor in field even though it's hidden
KZ.mapPanel.getMap().checkResize();//Func.defer(300);
KZ.mapPanel.onZoomEnd.defer(350);
KZ.leftColumnContainer.doLayout();// IE 7.0 bug, leftColumnContainr didn't show search field.
initialSearchPanelHolder.hide();
}
this.showInitialSearchPanelAndHideMap = function() {
KZ.viewport.hideTravelMap();
this.extComponent.show();
initialSearchPanelHolder.show();
// KZ.mapPanel.onZoomEnd.defer(2000);
}
this.processUserStopClick = function(event, element, store) {
var selectedStopIndex = element.getAttribute('id').split('-storeIndex-')[1];
var busStopRecord = store.getAt(selectedStopIndex);
if(busStopRecord != undefined) {
this.hideInitialSearchPanelAndShowMap();
this.extComponent.fireEvent("stopSelectedEvent", busStopRecord);
}
}
this.show = function() {
if(this.extComponent == undefined) {
searchField = new Ext.form.TextField({
id: 'initialSearchField',
allowBlank: true,
flex: 0,
autoCreate: {tag: "input", type: "text", size: "35", autocomplete: "off", spellcheck: "false"},
listeners: {
scope: this,
valid: function(textField) {
//this.hideSearchError();
},
invalid: function(textField) {
//this.setSearchError("Please enter a search term");
},
specialkey: function(field, e){
if (e.getKey() == e.RETURN || e.getKey() == e.ENTER) {
KZ.InitialSearchPanel.performSearchAndHideInitialSearchPanel(field.getValue());
}
}
},
margins: '0'
});
var searchFieldAndSearchButton = new Ext.Panel({
id: 'initialSearchFieldAndSearchButtonHolder',
layout:'hbox',
plain: true,
border:false,
bodyBorder:false,
items: [searchField,
{xtype: 'button',
id: 'initialSearchButton',
text: 'Search',
handler: function(evt, el, o) {
this.performSearchAndHideInitialSearchPanel(searchField.getValue());
_gaq.push(['_trackEvent', 'InitialSearchMethod', 'SearchUsingSearchTerm']);
},
scope:this
}
]
});
var searchUsingMapTextAndButton = new Ext.Panel({
id:'searchUsingMapTextAndButton',
hidden: !KZ.Config.enableSearchUsingMapButton,
layout:'hbox',
plain: true,
border:false,
bodyBorder:false,
items: [
{
id: 'orTextLabel',
html: ' Alternatively, you can ',
bodyBorder:false
},
{xtype: 'button',
id: 'initialSearchUsingMapButton',
text: 'Search using the Map',
handler: function(evt, el, o) {
this.hideInitialSearchPanelAndShowMapCentered();
_gaq.push(['_trackEvent', 'InitialSearchMethod', 'SearchUsingMap']);
},
scope:this
}
]
});
var favouriteStops = KZ.userStopsStore.getFavouriteStops();
var recentStops = KZ.userStopsStore.getRecentStops();
var t = new Ext.Template(
'
{name}',
{
compiled:true
}
);
Ext.each(recentStops, function(recentStop, index) {
t.append('initial-recent-stop-list', {storeIndex: index, stopType:'recent-stop', name: recentStop.data.name});
});
Ext.each(favouriteStops, function(favouriteStop, index) {
t.append('initial-favourite-stop-list', {storeIndex: index, stopType:'favourite-stop', name: favouriteStop.data.name});
});
if(recentStops.length == 0) {
Ext.DomHelper.insertHtml('beforeEnd', Ext.getDom('initial-recent-stop-list'), Ext.getDom('no-recent-stops-message').innerHTML);
}
if(favouriteStops.length == 0) {
Ext.DomHelper.insertHtml('beforeEnd', Ext.getDom('initial-favourite-stop-list'), Ext.getDom('no-favourite-stops-message').innerHTML);
}
Ext.get('initial-recent-stop-list').on({
'click' : {
fn: function(event, element, o) {
this.processUserStopClick(event, element, KZ.userStopsStore.recentStopsStore);
},
scope: this,
delegate: 'span'
}
});
Ext.get('initial-favourite-stop-list').on({
'click' : {
fn: function(event, element, o) {
this.processUserStopClick(event, element, KZ.userStopsStore.favouriteStopsStore);
},
scope: this,
delegate: 'span'
}
});
var hideUserStops = false;
if(favouriteStops.length == 0 && recentStops.length == 0) {
hideUserStops = true;
}
this.extComponent = new Ext.Panel({
id: 'initialSearchPanel',
renderTo: 'initial-search-panel-holder',
layout:'auto',
plain: true,
//border:false,
bodyBorder:true,
padding:'12px',
hidden: KZ.Config.showMapPageInsteadOfHomePage,
items: [{contentEl: 'initialSearchPanelTitle', border:false},
{contentEl: 'initialSearchPanelPreInputBoxText', border:false},
searchFieldAndSearchButton,
{contentEl: 'initialSearchPanelPostInputBoxText', border:false},
searchUsingMapTextAndButton,
{id: 'tip', contentEl: 'tipbox', border:false},
{id:'initial-user-stop-lists-holder-panel', contentEl: 'initial-user-stop-lists-holder', border:false, hidden:hideUserStops}
]
});
initialSearchPanelHolder = Ext.get("initial-search-panel-holder");
initialSearchPanelHolder.enableDisplayMode();
}
}
}
/* http://www.movable-type.co.uk/scripts/latlong-gridref.html */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Convert latitude/longitude <=> OS National Grid Reference points (c) Chris Veness 2002-2010 */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
* convert geodesic co-ordinates to OS grid reference
* see www.gps.gov.uk/guidecontents.asp Annex C
*/
function LatLongToOSGrid(p) {
var lat = p.lat.toRad(), lon = p.lon.toRad();
var a = 6377563.396, b = 6356256.910; // Airy 1830 major & minor semi-axes
var F0 = 0.9996012717; // NatGrid scale factor on central meridian
var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin
var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
var e2 = 1 - (b*b)/(a*a); // eccentricity squared
var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n;
var cosLat = Math.cos(lat), sinLat = Math.sin(lat);
var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature
var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature
var eta2 = nu/rho-1;
var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0);
var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0);
var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0));
var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0));
var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
var cos3lat = cosLat*cosLat*cosLat;
var cos5lat = cos3lat*cosLat*cosLat;
var tan2lat = Math.tan(lat)*Math.tan(lat);
var tan4lat = tan2lat*tan2lat;
var I = M + N0;
var II = (nu/2)*sinLat*cosLat;
var III = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2);
var IIIA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat);
var IV = nu*cosLat;
var V = (nu/6)*cos3lat*(nu/rho-tan2lat);
var VI = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2);
var dLon = lon-lon0;
var dLon2 = dLon*dLon, dLon3 = dLon2*dLon, dLon4 = dLon3*dLon, dLon5 = dLon4*dLon, dLon6 = dLon5*dLon;
var N = I + II*dLon2 + III*dLon4 + IIIA*dLon6;
var E = E0 + IV*dLon + V*dLon3 + VI*dLon5;
//return gridrefNumToLet(E, N, 8);
return [E, N];
}
/*
* convert OS grid reference to geodesic co-ordinates
*/
function OSGridToLatLong(gridRef) {
var gr = gridrefLetToNum(gridRef);
var E = gr[0], N = gr[1];
var a = 6377563.396, b = 6356256.910; // Airy 1830 major & minor semi-axes
var F0 = 0.9996012717; // NatGrid scale factor on central meridian
var lat0 = 49*Math.PI/180, lon0 = -2*Math.PI/180; // NatGrid true origin
var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
var e2 = 1 - (b*b)/(a*a); // eccentricity squared
var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n;
var lat=lat0, M=0;
do {
lat = (N-N0-M)/(a*F0) + lat;
var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0);
var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0);
var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0));
var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0));
M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
} while (N-N0-M >= 0.00001); // ie until < 0.01mm
var cosLat = Math.cos(lat), sinLat = Math.sin(lat);
var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature
var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature
var eta2 = nu/rho-1;
var tanLat = Math.tan(lat);
var tan2lat = tanLat*tanLat, tan4lat = tan2lat*tan2lat, tan6lat = tan4lat*tan2lat;
var secLat = 1/cosLat;
var nu3 = nu*nu*nu, nu5 = nu3*nu*nu, nu7 = nu5*nu*nu;
var VII = tanLat/(2*rho*nu);
var VIII = tanLat/(24*rho*nu3)*(5+3*tan2lat+eta2-9*tan2lat*eta2);
var IX = tanLat/(720*rho*nu5)*(61+90*tan2lat+45*tan4lat);
var X = secLat/nu;
var XI = secLat/(6*nu3)*(nu/rho+2*tan2lat);
var XII = secLat/(120*nu5)*(5+28*tan2lat+24*tan4lat);
var XIIA = secLat/(5040*nu7)*(61+662*tan2lat+1320*tan4lat+720*tan6lat);
var dE = (E-E0), dE2 = dE*dE, dE3 = dE2*dE, dE4 = dE2*dE2, dE5 = dE3*dE2, dE6 = dE4*dE2, dE7 = dE5*dE2;
lat = lat - VII*dE2 + VIII*dE4 - IX*dE6;
var lon = lon0 + X*dE - XI*dE3 + XII*dE5 - XIIA*dE7;
return new LatLon(lat.toDeg(), lon.toDeg());
}
/*
* convert standard grid reference ('SU387148') to fully numeric ref ([438700,114800])
* returned co-ordinates are in metres, centred on grid square for conversion to lat/long
*
* note that northern-most grid squares will give 7-digit northings
* no error-checking is done on gridref (bad input will give bad results or NaN)
*/
function gridrefLetToNum(gridref) {
// get numeric values of letter references, mapping A->0, B->1, C->2, etc:
var l1 = gridref.toUpperCase().charCodeAt(0) - 'A'.charCodeAt(0);
var l2 = gridref.toUpperCase().charCodeAt(1) - 'A'.charCodeAt(0);
// shuffle down letters after 'I' since 'I' is not used in grid:
if (l1 > 7) l1--;
if (l2 > 7) l2--;
// convert grid letters into 100km-square indexes from false origin (grid square SV):
var e = ((l1-2)%5)*5 + (l2%5);
var n = (19-Math.floor(l1/5)*5) - Math.floor(l2/5);
// skip grid letters to get numeric part of ref, stripping any spaces:
gridref = gridref.slice(2).replace(/ /g,'');
// append numeric part of references to grid index:
e += gridref.slice(0, gridref.length/2);
n += gridref.slice(gridref.length/2);
// normalise to 1m grid, rounding up to centre of grid square:
switch (gridref.length) {
case 6: e += '50'; n += '50'; break;
case 8: e += '5'; n += '5'; break;
// 10-digit refs are already 1m
}
return [e, n];
}
/*
* convert numeric grid reference (in metres) to standard-form grid ref
*/
function gridrefNumToLet(e, n, digits) {
// get the 100km-grid indices
var e100k = Math.floor(e/100000), n100k = Math.floor(n/100000);
if (e100k<0 || e100k>6 || n100k<0 || n100k>12) return '';
// translate those into numeric equivalents of the grid letters
var l1 = (19-n100k) - (19-n100k)%5 + Math.floor((e100k+10)/5);
var l2 = (19-n100k)*5%25 + e100k%5;
// compensate for skipped 'I' and build grid letter-pairs
if (l1 > 7) l1++;
if (l2 > 7) l2++;
var letPair = String.fromCharCode(l1+'A'.charCodeAt(0), l2+'A'.charCodeAt(0));
// strip 100km-grid indices from easting & northing, and reduce precision
e = Math.floor((e%100000)/Math.pow(10,5-digits/2));
n = Math.floor((n%100000)/Math.pow(10,5-digits/2));
// note use of floor, as ref is bottom-left of relevant square!
var gridRef = letPair + ' ' + e.padLZ(digits/2) + ' ' + n.padLZ(digits/2);
return gridRef;
}
/*
* pad a number with sufficient leading zeros to make it w chars wide
*/
Number.prototype.padLZ = function(width) {
var num = this.toString(), len = num.length;
for (var i=0; i precision) {
nu = a / Math.sqrt(1 - eSq*Math.sin(phi)*Math.sin(phi));
phiP = phi;
phi = Math.atan2(z2 + eSq*nu*Math.sin(phi), p);
}
var lambda = Math.atan2(y2, x2);
H = p/Math.cos(phi) - nu;
return new LatLon(phi.toDeg(), lambda.toDeg(), H);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* -------------------- the following are duplicated from LatLong.html -------------------- */
/*
* construct a LatLon object: arguments in numeric degrees
*
* note all LatLong methods expect & return numeric degrees (for lat/long & for bearings)
*/
function LatLon(lat, lon, height) {
if (arguments.length < 3) height = 0;
this.lat = lat;
this.lon = lon;
this.height = height;
}
// extend String object with method for parsing degrees or lat/long values to numeric degrees
//
// this is very flexible on formats, allowing signed decimal degrees, or deg-min-sec suffixed by
// compass direction (NSEW). A variety of separators are accepted (eg 3? 37' 09"W) or fixed-width
// format without separators (eg 0033709W). Seconds and minutes may be omitted. (Minimal validation
// is done).
String.prototype.parseDeg = function() {
if (!isNaN(this)) return Number(this); // signed decimal degrees without NSEW
var degLL = this.replace(/^-/,'').replace(/[NSEW]/i,''); // strip off any sign or compass dir'n
var dms = degLL.split(/[^0-9.]+/); // split out separate d/m/s
for (var i in dms) if (dms[i]=='') dms.splice(i,1); // remove empty elements (see note below)
switch (dms.length) { // convert to decimal degrees...
case 3: // interpret 3-part result as d/m/s
var deg = dms[0]/1 + dms[1]/60 + dms[2]/3600; break;
case 2: // interpret 2-part result as d/m
var deg = dms[0]/1 + dms[1]/60; break;
case 1: // decimal or non-separated dddmmss
if (/[NS]/i.test(this)) degLL = '0' + degLL; // - normalise N/S to 3-digit degrees
var deg = dms[0].slice(0,3)/1 + dms[0].slice(3,5)/60 + dms[0].slice(5)/3600; break;
default: return NaN;
}
if (/^-/.test(this) || /[WS]/i.test(this)) deg = -deg; // take '-', west and south as -ve
return deg;
}
// note: whitespace at start/end will split() into empty elements (except in IE)
// extend Number object with methods for converting degrees/radians
Number.prototype.toRad = function() { // convert degrees to radians
return this * Math.PI / 180;
}
Number.prototype.toDeg = function() { // convert radians to degrees (signed)
return this * 180 / Math.PI;
}
// extend Number object with methods for presenting bearings & lat/longs
Number.prototype.toDMS = function(dp) { // convert numeric degrees to deg/min/sec
if (arguments.length < 1) dp = 0; // if no decimal places argument, round to int seconds
var d = Math.abs(this); // (unsigned result ready for appending compass dir'n)
var deg = Math.floor(d);
var min = Math.floor((d-deg)*60);
var sec = ((d-deg-min/60)*3600).toFixed(dp);
// fix any nonsensical rounding-up
if (sec==60) { sec = (0).toFixed(dp); min++; }
if (min==60) { min = 0; deg++; }
if (deg==360) deg = 0;
// add leading zeros if required
if (deg<100) deg = '0' + deg; if (deg<10) deg = '0' + deg;
if (min<10) min = '0' + min;
if (sec<10) sec = '0' + sec;
return deg + '\u00B0' + min + '\u2032' + sec + '\u2033';
}
Number.prototype.toDMS2 = function(dp) { // convert numeric degrees to deg/min/sec
if (arguments.length < 1) dp = 0; // if no decimal places argument, round to int seconds
var d = Math.abs(this); // (unsigned result ready for appending compass dir'n)
// convert degrees to seconds & round as appropriate
var seconds = (d*3600).toFixed(dp);
// now get component deg/min/sec
var deg = Math.floor(seconds / 3600);
var min = Math.floor(seconds/60) % 60;
var sec = (seconds % 60).toFixed(dp);
// format leading zeros
if (deg<100) deg = '0' + deg; if (deg<10) deg = '0' + deg;
if (min<10) min = '0' + min;
if (sec<10) sec = '0' + sec;
return deg + '\u00B0' + min + '\u2032' + sec + '\u2033';
}
Number.prototype.toLat = function(dp) { // convert numeric degrees to deg/min/sec latitude
return this.toDMS(dp).slice(1) + (this<0 ? 'S' : 'N'); // knock off initial '0' for lat!
}
Number.prototype.toLon = function(dp) { // convert numeric degrees to deg/min/sec longitude
return this.toDMS(dp) + (this>0 ? 'E' : 'W');
}
/*
* represent point {lat, lon} in standard representation
*/
LatLon.prototype.toString = function() {
return this.lat.toLat() + ', ' + this.lon.toLon();
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
function WGS84LL2OSGB36EN(latitude, longitude) {
var WGS84LL = new LatLon(latitude, longitude);
var OSGB36LL = convertWGS84toOSGB36(WGS84LL);
var gridref = LatLongToOSGrid(OSGB36LL);
return { easting: Math.round(gridref[0]), northing: Math.round(gridref[1]) };
}
Ext.ns('KZ');
// XXX: hack for IE 9 and ExtJS 3.2
// http://stackoverflow.com/questions/5375616/extjs4-ie9-object-doesnt-support-property-or-method-createcontextualfragmen
if (/msie 9/i.test(navigator.userAgent) && typeof Range.prototype.createContextualFragment === 'undefined') {
Range.prototype.createContextualFragment = function(html) {
var doc = this.startContainer.ownerDocument;
var container = doc.createElement('div');
container.innerHTML = html;
var frag = doc.createDocumentFragment(), n;
while (n = container.firstChild) {
frag.appendChild(n);
}
return frag;
};
}
// This is a lazily initialised singleton, not to be instatiated
KZ.HeaderMenuDialog = new function() {
var extComponent;
this.isInitialised = function() {
return extComponent != undefined;
}
this.isVisible = function() {
return this.isInitialised() && extComponent.isVisible();
}
this.getDialog = function() {
if(extComponent == undefined) {
extComponent = new Ext.Window({
title: 'Notices',
iconCls: 'notices-icon',
layout:'fit',
modal:true,
autoScroll:true,
height: 300,
width: 400,
padding:5,
draggable:false,
resizable:false,
closable:true,
closeAction:'hide',
html: ' ',
plain: true,
listeners: {
show: function() {
Ext.select('.ext-el-mask').addListener('click', function() {
extComponent.hide();
});
}
},
buttons: [{
text:'OK',
scope: this,
handler: function(){
extComponent.hide();
}
}]
});
}
return extComponent;
}
this.show = function(dialogTitle, contentUrl) {
var dialog = this.getDialog();
dialog.setTitle(dialogTitle);
dialog.show();
dialog.center();
//dialog.update("test");
dialog.load({
url: contentUrl,
nocache: false,
text: 'Loading...',
timeout: 30,
scripts: false
});
}
// Attach click handler when the dom is ready
Ext.onReady(function(){
if(Ext.get('right-menu') != undefined) {
Ext.get('right-menu').on({
'click' : {
fn: function(event, element) {
if(element.className.indexOf('dialog-popup') >= 0) {
this.show(element.innerHTML, element.getAttribute('href'));
_gaq.push(['_trackEvent', 'InformationDialogWindowPopup', element.innerHTML]);
event.stopEvent();
}
},
scope: this,
delegate: 'a'
}
});
}
}, this);
}
Ext.ns('KZ');
KZ.RouteSearchResultsContainer = function() {
this.currentRouteResults = undefined;
this.directionIndex = -1;
this.isShowingBusRouteDirections = false;
var selectedCallingPatternResultsStore = new Ext.data.JsonStore({
autoDestroy: false,
storeId: 'selectedCallingPatternResultsStore',
idProperty: 'id',
fields: ['id', 'smsCode', 'name', 'stopIndicator', 'towards', 'direction', 'lat', 'lng', 'routes', 'domRef' /* , 'originType', 'segmentName' */]
});
Ext.StoreMgr.lookup('selectedCallingPatternResultsStore').on({
'clear' : {
fn: function() {
KZ.layerManager.getLayer('bus').cleanSearchResults(); // clear
}
}
});
var routeResultsStore = new Ext.data.JsonStore({
autoDestroy: false,
storeId: 'routeResultsStore',
idProperty: 'name',
fields: ['name', 'operatorName']
});
var routeDirectionResultsStore = new Ext.data.JsonStore({
autoDestroy: false,
storeId: 'routeDirectionResultsStore',
idProperty: 'name',
fields: ['name', 'routeGeometry', 'segments']
});
// TODO: This should just be a custom grid render, rather than a simple template (now that we are using a store for the calling pattern).
var cpItemTpl = new Ext.Template([
'{stopName}'
]);
cpItemTpl.compile();
var cpSegmentHeaderTpl = new Ext.Template([
'',
'

',
'
',
'
'
]);
cpSegmentHeaderTpl.compile();
var towardsLinkTpl = new Ext.Template([
'Towards {towards}'
]);
towardsLinkTpl.compile();
var resutsStatusTpl = 'Bus route {0} to {1}' +
'
Switch direction
';
var callingPatternResultsDiv = Ext.get('calling-pattern-results');
this.getContainer = function() {
return callingPatternResultsDiv;
}
this.initDisplay = function(busRoutesJson) {
this.currentRouteResults = busRoutesJson;
//KZ.util.showVBoxLayoutItem(KZ.routeSearchResultsContainer.extComponent);
KZ.searchResultsPanel.showRouteResult();
this.isShowingBusRouteDirections = false;
};
this.isShowingBusRouteDirectionHyperlinks = function() {
return this.isShowingBusRouteDirections;
}
this.displayBusRouteDirectionHyperlinks = function(busRoutesJson) {
this.initDisplay(busRoutesJson);
this.isShowingBusRouteDirections = true;
var towardsUl = Ext.DomHelper.overwrite(callingPatternResultsDiv, {tag: 'ul', id: 'calling-pattern-towards-links'});
KZ.searchResultsPanel.setResultStatus(String.format('Route {0}
Select a route direction:', this.currentRouteResults.name));
KZ.searchResultsPanel.showResultsStatusPanel();
Ext.each(this.currentRouteResults.directions, function(journey, index) {
towardsLinkTpl.append(towardsUl, {
id: index,
towards: journey.direction
});
});
KZ.searchPanel.doLayout();
this.extComponent.fireEvent("newSearchBoundingBoxEvent", busRoutesJson.swLatBB, busRoutesJson.swLngBB, busRoutesJson.neLatBB, busRoutesJson.neLngBB);
callingPatternResultsDiv.select('span').on({
'click' : {
fn: function(event, htmlElement) {
var directionIndex = htmlElement.getAttribute('rel');
this.displayBusRouteResults(this.currentRouteResults, directionIndex);
return false;
},
scope: this
}
});
};
this.displayBusRouteResults = function(busRoutesJson, directionIndex) {
this.initDisplay(busRoutesJson);
routeResultsStore.loadData(this.currentRouteResults, false);
var segmentsDiv = Ext.DomHelper.overwrite(callingPatternResultsDiv, [{id : 'calling-pattern-segments'}]);
var selectedDirectionIndex = 0;
if(directionIndex != undefined) {
selectedDirectionIndex = directionIndex;
}
var callingPattern = this.currentRouteResults.directions[selectedDirectionIndex];
KZ.searchResultsPanel.setResultStatus(String.format(resutsStatusTpl, this.currentRouteResults.name, callingPattern.direction));
KZ.searchResultsPanel.showResultsStatusPanel();
Ext.each(callingPattern.segments, function(segment, index) {
cpSegmentHeaderTpl.append(segmentsDiv, {
segmentIndex: index,
origin: segment.originType,
segmentName: segment.name
});
Ext.each(segment.markers, function(stop) {
cpItemTpl.append('calling-pattern-items-' + index, {
stopId: stop.id,
stopName: stop.name
});
});
});
KZ.searchPanel.doLayout();
callingPatternResultsDiv.select('a.calling-pattern-item').on({
'mouseenter' : {
fn: function(event, htmlElement) {
var busStopId = htmlElement.getAttribute('rel');
var busStopRecord = Ext.StoreMgr.lookup('selectedCallingPatternResultsStore').getById(busStopId);
KZ.markerManager.setMarkerState(busStopRecord, 'hover-state');
},
scope: this
}
});
Ext.get('switch-direction').on({
'click' : {
fn: function(event, htmlElement) {
KZ.routeSearchResultsContainer.displayBusRouteResults(busRoutesJson, selectedDirectionIndex == 0 ? 1 : 0);
},
scope: this
}
});
callingPatternResultsDiv.select('#calling-pattern-segments').on({
'click' : {
fn: this.showStopInformation,
scope: this,
stopEvent: true,
delegate: 'a'
}
});
this.extComponent.fireEvent("newSearchBoundingBoxEvent", busRoutesJson.swLatBB, busRoutesJson.swLngBB, busRoutesJson.neLatBB, busRoutesJson.neLngBB);
this.displayCallingPatternOnMap(selectedDirectionIndex);
Ext.get('route-search-container').scrollTo('top', -Ext.get('route-search-container').getHeight());
Ext.get('route-search-container').dom.scrollTop = 0;
this.displaySwitchDirection(this.currentRouteResults.directions.length > 1);
}
this.displaySwitchDirection = function(on) {
if(on) {
Ext.get("switch-direction").dom.style.display = 'block';
} else {
Ext.get("switch-direction").dom.style.display = 'none';
}
}
this.displayCallingPatternOnMap = function(directionIndex) {
this.directionIndex = directionIndex;
selectedCallingPatternResultsStore.removeAll();
routeDirectionResultsStore.removeAll();
this.extComponent.fireEvent("newSearchEvent");
this.loadMarkersData(this.currentRouteResults.directions[directionIndex].segments);
routeDirectionResultsStore.loadData(this.currentRouteResults.directions[directionIndex], false);
}
this.loadMarkersData = function(segments) {
var markers = [];
var i;
Ext.each(segments, function(segment) {
var markerArr = segment.markers;
// grid grouping
// Ext.each(markerArr, function(marker){
// marker.originType = segment.originType;
// marker.segmentName = segment.name;
// });
markers = markers.concat(markerArr);
}, this);
KZ.layerManager.getLayer('bus').loadSearchResultMarkers(markers); // load & crete
selectedCallingPatternResultsStore.loadData(markers, false);
}
this.showStopInformation = function(event, element) {
var busStopId = element.getAttribute('rel');
var busStopRecord = Ext.StoreMgr.lookup('selectedCallingPatternResultsStore').getById(busStopId);
KZ.routeSearchResultsContainer.extComponent.fireEvent("stopSelectedEvent", busStopRecord);
}
this.extComponent = new Ext.Panel({
id: 'route-search-container',
contentEl: 'route-search',
headerCssClass: 'hidden-header',
autoScroll: true,
border: false,
autoScroll: true,
hidden: true,
flex:1
});
// Ext.get('search-results-message').select('.switch-direction a').on({
// 'click' : {
// fn: function(event, htmlElement) {
// this.displayBusRouteResults(busRoutesJson, selectedDirectionIndex == 0 ? 1 : 0)
// },
// scope: this
// }
//
// });
// refactor this
this.decorateRecordArrayWithAccessorFields = function(store, records, options) {
Ext.each(records, function(record) {
this.decorateRecordWithAccessorFields(record);
}, this);
}
// FIXME delete after migration
this.decorateRecordWithAccessorFields = function(record, domRef) {
if(record instanceof Ext.data.Record) {
record.lat = record.get('lat');
record.lng = record.get('lng');
record.domRef = domRef;
record.set('domRef', domRef);
}
}
}
Ext.ns('KZ');
KZ.RouteFilterDialog = new function() {
// private fields
var stopId, checkboxItems, allCheckbox, okButton;
this.stopInformationPanel;
this.show = function(stopInformationPanel) {
this.stopInformationPanel = stopInformationPanel;
var stopRecord = stopInformationPanel.getCurrentBusStopRecord();
// FIXME migration workaround, delete when finish it
if(stopRecord instanceof Ext.data.Record) {
stopRecord = stopRecord.data;
}
stopId = stopRecord.id;
var allChecked = !KZ.userStopsStore.hasFilters(stopId);
allCheckbox = new Ext.form.Checkbox({
boxLabel: tr('showallroutes'),
name: "allCheckbox",
checked: allChecked,
scope:this,
listeners: { check: this.allCheckboxChanged }
});
checkboxItems = [allCheckbox];
Ext.each(stopRecord.routes, function(route) {
var filterOut = KZ.userStopsStore.getFilters(stopId);
var boxChecked = !filterOut || (filterOut && filterOut.indexOf(route.id) == -1);
var checkbox = new Ext.form.Checkbox({
boxLabel: route.name,
checked: boxChecked,
name: route.id,
scope:this,
listeners: { check: this.routeCheckboxChanged }
});
checkboxItems.push(checkbox);
}, this);
var checkboxGroup = new Ext.form.CheckboxGroup({
autoHeight: true,
layout: 'form',
border:false,
fieldLabel: 'Auto Layout',
columns: 1,
items: checkboxItems
});
var window = new Ext.Window({
title: tr('filterroutes'),
iconCls: 'filter-icon',
layout:'fit',
modal:true,
width: 200,
padding:'10 0 10 20',
draggable:false,
resizable:false,
closable:false,
closeAction:'hide',
plain: true,
items: [checkboxGroup],
listeners: {
show: function() {
Ext.select('.ext-el-mask').addListener('click', function() {
window.hide();
});
}
},
buttons: [{
text:'Ok',
scope: this,
handler: function(){
window.hide();
this.processFilter();
var filterOut = KZ.userStopsStore.getFilters(stopId);
var arrivals = stopInformationPanel.arrivalsGrid;
arrivals.applyArrivalFilter(filterOut);
stopInformationPanel.toolbar.setFilterToolBarText();
arrivals.extComponent.getView().refresh();
try {
arrivals.extComponent.doLayout();
} catch (e) {
KZ.log("Error laying out Arrivals Grid " + e);
}
arrivals.store.each(arrivals.filterRecordsEstimatedWaitPresentation, arrivals);
}
},{
text: 'Cancel',
handler: function(){
window.hide();
}
}]
});
window.show();
okButton = window.getFooterToolbar().get(0);
}
var changeButtonState = function(button, enabled) {
if(button == undefined) {
return;
}
if(enabled) {
button.enable();
} else {
button.disable();
}
}
this.allCheckboxChanged = function(checkbox, checked) {
Ext.each(checkboxItems, function(checkbox) {
checkbox.suspendEvents(false); // to prevent recursive event triggering
checkbox.setValue(checked);
checkbox.resumeEvents();
});
changeButtonState(okButton, checked);
//console.info('allCheckboxChanged = ' + checked);
}
this.routeCheckboxChanged = function(checkbox, checked) {
var allCheckboxValue = true;
var allRouteCheckboxesUnchecked = true;
Ext.each(checkboxItems, function(checkbox) {
if(checkbox.getName() != 'allCheckbox') {
if (!checkbox.getValue()) {
// unchecked
allCheckboxValue = false;
} else {
// checked
allRouteCheckboxesUnchecked = false;
}
}
if(!allCheckboxValue && !allRouteCheckboxesUnchecked) {
return false; // break from ext loop
}
});
changeButtonState(okButton, !allRouteCheckboxesUnchecked);
allCheckbox.suspendEvents(false);
allCheckbox.setValue(allCheckboxValue);
allCheckbox.resumeEvents();
}
this.processFilter = function() {
var stopRecord = KZ.userStopsStore.getStop(stopId);
var filterOutArray = [];
Ext.each(checkboxItems, function(checkbox) {
if(checkbox.getName() != 'allCheckbox' && !checkbox.getValue()) {
var routeId = checkbox.getName();
if(routeId) {
filterOutArray.push(routeId);
}
}
}, this);
KZ.userStopsStore.updateFilters(stopId, filterOutArray);
KZ.userStopsStore.persistFilterOut(stopId, filterOutArray);
}
}
Ext.ns('KZ');
KZ.UserStopsStore = function() {
var userStopsConnection;
this.recentStopsStore = new Ext.data.JsonStore({
autoDestroy: false,
storeId: 'recentStopsStore',
root: 'recentStops',
idProperty: 'id',
fields: ['id', 'smsCode', 'name', 'stopIndicator', 'towards', 'direction', 'lat', 'lng', 'mode', 'routes', 'filterOut', 'staticMapUrl', 'arrivalsBoard', 'objectType', { name: 'temporaryFilter', type: 'boolean', defaultValue: false} ]
});
this.favouriteStopsStore = new Ext.data.JsonStore({
autoDestroy: false,
storeId: 'favouriteStopsStore',
root: 'myStops',
idProperty: 'id',
fields: ['id', 'smsCode', 'name', 'stopIndicator', 'towards', 'direction', 'lat', 'lng', 'mode', 'routes', 'filterOut', 'staticMapUrl', 'arrivalsBoard', 'objectType', 'stops']
});
function safe(objType){
if(objType == undefined || objType == ''){
return 'STOP';
}
return objType;
}
if(!KZ.util.isEmptyObject(KZ.Config.recentStopsJson)) {
this.recentStopsStore.loadData(KZ.Config.recentStopsJson, false);
}
if(!KZ.util.isEmptyObject(KZ.Config.favouriteStopsJson)) {
this.favouriteStopsStore.loadData(KZ.Config.favouriteStopsJson, false);
}
this.loadRecentStops = function() {
loadStopsToLayer(KZ.Config.recentStopsJson.recentStops);
}
this.loadFavouriteStops = function() {
loadStopsToLayer(KZ.Config.favouriteStopsJson.myStops);
}
var loadStopsToLayer = function(store) {
for(var i = 0; i < store.length; ++i) {
var stop = store[i];
var layer = KZ.layerManager.getLayer(stop.layerName)
if( layer ) {
// FIXME it's not efficient loading it for every marker, group markers by layer and then load them
layer.loadFetchedMarkers([ stop ], true);
}
}
}
this.getRecentStops = function() {
return this.recentStopsStore.getRange();
}
this.getFavouriteStops = function() {
return this.favouriteStopsStore.getRange();
}
this.getStop = function(stopId) {
return this.recentStopsStore.getById(stopId);
}
this.addRecentStop = function(busStopRecord) {
// FIXME migration workaround, delete when finish it
if(busStopRecord instanceof Ext.data.Record) {
busStopRecord = busStopRecord.data;
}
var stopId = busStopRecord.id;
var existingRecord = this.recentStopsStore.getById(stopId);
var stopCount = this.recentStopsStore.getCount();
// remove the last recent stop that is not a favourite
if(stopCount + 1 > KZ.Config.maxRecentStops && existingRecord == undefined) {
this.recentStopsStore.removeAt(stopCount - 1);
}
if(existingRecord == undefined) {
var record = new this.recentStopsStore.recordType({
id : stopId,
name : busStopRecord.name,
stopIndicator:busStopRecord.stopIndicator,
towards : busStopRecord.towards,
direction : busStopRecord.direction,
lat : busStopRecord.lat,
lng : busStopRecord.lng,
mode : busStopRecord.mode,
routes : busStopRecord.routes,
objectType : busStopRecord.objectType,
filterOut : [] }, stopId);
record.commit();
this.recentStopsStore.insert(0, record);
}
persistRecentStop(stopId, busStopRecord.objectType); // This will move it back to the top of the list on the server if it already exists
}
this.isFavouritesMaxLimitReached = function() {
var favouritesCount = this.favouriteStopsStore.getCount();
if(KZ.Config.maxFavouriteStops > favouritesCount) {
return false;
} else {
return true;
}
}
this.addFavouriteStop = function(busStopRecord) {
// FIXME migration workaround, delete when finish it
if(busStopRecord instanceof Ext.data.Record) {
busStopRecord = busStopRecord.data;
}
var stopId = busStopRecord.id;
var filterOutArray = this.recentStopsStore.getById(stopId).get("filterOut");
var record = new this.favouriteStopsStore.recordType({
id : stopId,
name : busStopRecord.name,
stopIndicator : busStopRecord.stopIndicator,
towards : busStopRecord.towards,
direction : busStopRecord.direction,
lat : busStopRecord.lat,
lng : busStopRecord.lng,
mode : busStopRecord.mode,
routes : busStopRecord.routes,
objectType : busStopRecord.objectType,
filterOut : filterOutArray }, stopId);
record.commit();
this.favouriteStopsStore.insert(0, record);
this.persistFavouriteStop(stopId, 0, busStopRecord.objectType);
_gaq.push(['_trackEvent', 'My Stops', stopId, busStopRecord.name]);
}
var getUserStopsConnection = function() {
if(userStopsConnection == undefined) {
userStopsConnection = new Ext.data.Connection({
timeout: 8000,
autoAbort: false
});
}
return userStopsConnection;
}
var persistRecentStop = function(stopId, objType) {
getUserStopsConnection().request({
url: '/recentStops/stopId/' + stopId + '/' + safe(objType),
// params: { objectType: objType, },
method: 'PUT',
scope: this
});
}
this.persistFavouriteStop = function(stopId, listPosition, objType) {
getUserStopsConnection().request({
url: '/myStops/stopId/' + stopId + '/listPosition/' + listPosition + '/' + safe(objType),
// params: { objectType: objType, },
method: 'PUT',
scope: this
});
}
this.persistFilterOut = function(stopId, filterOut) {
getUserStopsConnection().request({
url: '/stopFilter/stopId/' + stopId + '/filterOut/' + filterOut +'/' ,
method: 'PUT',
scope: this
});
}
this.deleteFavouriteStop = function(stopId, objType) {
getUserStopsConnection().request({
url: '/myStops/stopId/' + stopId + '/' + safe(objType),
method: 'DELETE',
scope: this
});
var record = this.favouriteStopsStore.getById(stopId);
this.favouriteStopsStore.remove(record);
}
this.overrideFavouriteStop = function(prevStopId, newStopRecord) {
// FIXME migration workaround, delete when finish it
if(newStopRecord instanceof Ext.data.Record) {
newStopRecord = newStopRecord.data;
}
var newStopId = newStopRecord.id;
if(this.isFavourite(prevStopId) && !this.isFavourite(newStopId)) {
var prevStopRecord = this.favouriteStopsStore.getById(prevStopId);
var prevStopFavIndex = this.favouriteStopsStore.indexOf(prevStopRecord);
var filterOutArray = this.recentStopsStore.getById(newStopId).get("filterOut");
var record = new this.favouriteStopsStore.recordType({
id : newStopId,
name : newStopRecord.name,
stopIndicator: newStopRecord.stopIndicator,
towards : newStopRecord.towards,
direction : newStopRecord.direction,
lat : newStopRecord.lat,
lng : newStopRecord.lng,
mode : newStopRecord.mode,
routes : newStopRecord.routes,
filterOut : filterOutArray }, newStopId);
record.commit();
this.favouriteStopsStore.insert(prevStopFavIndex, record);
this.favouriteStopsStore.remove(prevStopRecord);
getUserStopsConnection().request({
url: '/myStops/prevStopId/'+prevStopId+'/newStopId/'+newStopId+'/',
method: 'PUT',
scope: this
});
_gaq.push(['_trackEvent', 'My Stops', newStopId, newStopRecord.name]);
return true;
} else {
return false;
}
}
this.isFavourite = function(stopId) {
var record = this.favouriteStopsStore.getById(stopId);
return record != undefined;
}
this.updateFilters = function(stopId, filterOutArray) {
var recentStopRecord = this.recentStopsStore.getById(stopId);
var favouriteStopRecord = this.favouriteStopsStore.getById(stopId);
if(recentStopRecord != undefined) {
recentStopRecord.set('filterOut', filterOutArray);
}
if(favouriteStopRecord != undefined) {
favouriteStopRecord.set('filterOut', filterOutArray);
}
}
this.isTemporaryFilterEnabled = function(stopId) {
var recentStopRecord = this.recentStopsStore.getById(stopId);
if(recentStopRecord != undefined) {
return recentStopRecord.get('temporaryFilter');
} else {
return false;
}
}
this.setTemporaryFilterEnabled = function(stopId, enabled) {
var recentStopRecord = this.recentStopsStore.getById(stopId);
if(recentStopRecord != undefined) {
recentStopRecord.set('temporaryFilter', enabled);
}
}
this.getFilters = function(stopId) {
var favouriteStopsRecord = this.favouriteStopsStore.getById(stopId);
if(favouriteStopsRecord != undefined) {
return favouriteStopsRecord.get('filterOut');
}
var recentStopsRecord = this.recentStopsStore.getById(stopId);
if(recentStopsRecord != undefined) {
return recentStopsRecord.get('filterOut');
}
return [];
}
this.hasFilters = function(stopId) {
var recentStopsRecord = this.recentStopsStore.getById(stopId);
var favouriteStopsRecord = this.favouriteStopsStore.getById(stopId);
return ((recentStopsRecord != undefined && recentStopsRecord.get('filterOut').length > 0) ||
(favouriteStopsRecord != undefined && favouriteStopsRecord.get('filterOut').length > 0));
}
}
Ext.ns('KZ');
KZ.SearchBoxPanel = function() {
var performingSearchMask = undefined; // use getter method for access
var searchConnection = undefined; // use getter method for access
this.currentSearchTerm = "";
var standardMessage = tr('searchhint');
var invalidMessage = "{0}
{1}".bind(tr('pleasetypesth'), standardMessage);
/* ********************************************************************************************* */
this.setSearchError = function(htmlStatus) {
Ext.get('search-error').update(htmlStatus).removeClass('x-hidden').show();
KZ.searchPanel.doLayout();
}
this.hideSearchError = function() {
Ext.get('search-error').enableDisplayMode().hide();
KZ.searchPanel.doLayout();
}
this.getSearchConnection = function() {
if(!searchConnection) {
searchConnection = new Ext.data.Connection({
timeout: 8000,
autoAbort: true
});
}
return searchConnection;
}
this.getPerformingSearchMask = function() {
if(!performingSearchMask) {
performingSearchMask = new Ext.LoadMask(KZ.searchPanel.body, {msg:tr('searching')});
}
return performingSearchMask;
}
this.extComponent = new Ext.form.TriggerField({
id: 'mapSearchField',
allowBlank: true,
//flex: 0,
region: 'north',
triggerClass: 'x-form-search-trigger',
autoCreate: {tag: "input", type: "text", autocomplete: "off", spellcheck: "false"},
listeners: {
scope: this,
valid: function(textField) {
if(textField.getValue() != '' && !KZ.searchResultsPanel.hasResults()) {
KZ.searchResultsPanel.setMessage(standardMessage);
}
},
invalid: function(textField) {
KZ.searchResultsPanel.showMessage(invalidMessage);
},
specialkey: function(field, e){
if (e.getKey() == e.RETURN || e.getKey() == e.ENTER) {
this.performSearch(field.getValue());
}
}
},
margins: '5'
});
// Or you could create a delegate function
this.extComponent.onTriggerClick = function(event) {
KZ.searchBoxPanel.performSearch(this.getValue());
}
this.performSearch = function(searchTerm, newRequest) {
_gaq.push(['_trackEvent', 'SearchTerm', searchTerm]);
var fromTheMainPage = false;
if(KZ.InitialSearchPanel.isVisible() == true || KZ.Config.showMapPageInsteadOfHomePage){
KZ.InitialSearchPanel.hideInitialSearchPanelAndShowMap();
fromTheMainPage = true;
}
this.extComponent.fireEvent("closeStopBoard");
if(searchTerm.trim() == '') {
this.extComponent.markInvalid();
return;
}
if(searchTerm != this.extComponent.getValue()) {
this.extComponent.setValue(searchTerm);
}
KZ.historyManager.addNewSearchHistory(searchTerm, true);
if((newRequest != undefined && newRequest == true) || newRequest==undefined) {
Ext.StoreMgr.lookup('selectedCallingPatternResultsStore').removeAll();
}
if(fromTheMainPage || newRequest== undefined || newRequest==true) { //prevent showing loading dialog no refresh is needed
this.getPerformingSearchMask().show();
KZ.searchResultsPanel.extComponent.hide();
}
this.extComponent.fireEvent("newSearchEvent");
this.currentSearchTerm = searchTerm;
var urlEncodedSearchTerm = encodeURIComponent(searchTerm);
if(newRequest != undefined && newRequest == false) {
KZ.searchResultsManager.handleSearchResults(KZ.searchResultsManager.getCurrentSearchResults());
} else {
try {
this.getSearchConnection().request({
url: "/search?searchTerm=" + urlEncodedSearchTerm,
headers: {Accept: 'application/json'},
timeout: 8000,
scope: this,
success: function(resp, opt) {
this.getPerformingSearchMask().hide();
var searchResults = KZ.util.evalToJSON( resp.responseText );
KZ.searchResultsManager.handleSearchResults(searchResults);
},
failure: function(response, opts) {
this.getPerformingSearchMask().hide();
KZ.log('server-side failure with status code ' + response.status);
var emptyResults = {};
KZ.searchResultsManager.handleSearchResults(emptyResults);
}
});
} catch(e) {
KZ.log(e);
}
}
}
}
Ext.ns('KZ');
// This is a lazily initialised singleton, not to be instatiated
KZ.AutoRefreshArrivalsDialog = new function() {
var extComponent;
this.isInitialised = function() {
return extComponent != undefined;
}
this.isVisible = function() {
return this.isInitialised() && extComponent.isVisible();
}
this.show = function() {
if(extComponent == undefined) {
extComponent = new Ext.Window({
title: 'Auto-refresh',
iconCls: 'auto-refresh-icon',
layout:'fit',
modal:true,
width: 330,
padding:5,
draggable:false,
resizable:false,
closable:true,
closeAction:'hide',
plain: true,
html: 'Your arrival board will be refreshed automatically every ' + KZ.Config.autoRefreshInterval + ' seconds for ' + Math.floor(KZ.Config.autoRefreshDuration/60) + ' minutes.',
//contentEl: 'auto-refresh-message',
listeners: {
show: function() {
Ext.select('.ext-el-mask').addListener('click', function() {
extComponent.hide();
});
}
},
buttons: [{
text:'OK',
scope: this,
handler: function(){
extComponent.hide();
}
}]
});
}
extComponent.show();
extComponent.center();
}
this.hide = function () {
if(extComponent != undefined) {
extComponent.hide();
}
}
}
Ext.ns('KZ');
KZ.OverrideFavouriteStopDialog = new function() {
var window;
var radioGroup;
var radioFieldSet;
var checkedStop;
var changeLabelTextWeight = function(radio) {
if(radio.checked) {
radio.wrap.select('.x-form-cb-label').addClass('bold');
} else {
radio.wrap.select('.x-form-cb-label').removeClass('bold');
}
}
this.prepare = function() {
var radioChecked = true;
var radioItems = [];
var favouriteStops = KZ.userStopsStore.getFavouriteStops();
Ext.each(favouriteStops, function(favouriteStop) {
var stop = favouriteStop.data;
var stopIndicator = stop.stopIndicator ? " (" + stop.stopIndicator + ") " : "";
var radioLabel = stop.name + stopIndicator;
var radio = new Ext.form.Radio({
boxLabel: radioLabel,
cls: 'bold',
checked: radioChecked,
name: 'overridedFavouriteStop',
value: stop.id,
scope:this
});
radio.on('afterrender', changeLabelTextWeight, this)
radio.on('check', changeLabelTextWeight, this);
if(radioChecked) {
checkedStop = stop.id;
radioChecked = false;
}
radioItems.push(radio);
}, this);
radioGroup = new Ext.form.RadioGroup({
hideLabel: true,
columns: 1,
items: radioItems
});
radioFieldSet = new Ext.form.FieldSet({
title: 'My Stops',
autoHeight: true,
border: true,
items: [radioGroup]
});
}
this.show = function(info) {
var message = new Ext.BoxComponent({
autoEl: {
html: 'The My Stops page is full. Please select a stop to replace, or press cancel.'
},
autoHeight: true,
style: {
marginBottom: '12px'
}
});
this.prepare();
window = new Ext.Window({
title: 'Confirm override',
iconCls: 'are-you-sure-icon',
layout:'fit',
modal:true,
padding: '5px 5px 5px 5px',
draggable:false,
resizable:false,
closable:true,
closeAction:'close',
plain: true,
items: [message, radioFieldSet],
listeners: {
show: function() {
Ext.select('.ext-el-mask').addListener('click', function() {
window.hide();
});
}
},
buttons: [{
text:'Ok',
scope: this,
handler: function(){
window.hide();
var prevStopId = radioGroup.getValue().value;
var newStopRecord = info.panel.getCurrentBusStopRecord();
KZ.userStopsStore.overrideFavouriteStop(prevStopId, newStopRecord);
info.toolbar.setFavouriteToolBarIcon();
window.close();
}
},{
text: 'Cancel',
handler: function(){
window.close();
}
}]
});
window.show();
}
}
Ext.ns('KZ');
/* Creates general notification dialog
* Object "properties" may contain properties:
* title - dialog title (optional)
* icon - the icon (optional)
* message - notification message (required)
*/
KZ.GeneralNotificationDialog = new function() {
this.show = function(properties) {
if(properties == undefined) {
return;
}
if(properties.title == undefined) {
properties.title = 'General notification';
}
if(properties.icon == undefined) {
properties.icon = 'notices-icon';
}
if(extComponent == undefined) {
var extComponent = new Ext.Window({
title: properties.title,
iconCls: properties.icon,
layout:'fit',
modal:true,
width: 330,
padding:5,
draggable:false,
resizable:false,
closable:true,
plain: true,
html: properties.message,
//contentEl: 'auto-refresh-message',
listeners: {
show: function() {
Ext.select('.ext-el-mask').addListener('click', function() {
extComponent.close();
});
}
},
buttons: [{
text:'OK',
scope: this,
handler: function(){
extComponent.close();
}
}]
});
}
extComponent.show();
}
}
Ext.ns('KZ');
// This is a lazily initialised singleton, not to be instatiated
KZ.NoticesDialog = new function() {
var extComponent;
this.isInitialised = function() {
return extComponent != undefined;
}
this.isVisible = function() {
return this.isInitialised() && extComponent.isVisible();
}
this.getDialog = function() {
if(extComponent == undefined) {
extComponent = new Ext.Window({
title: 'Notices',
iconCls: 'notices-icon',
layout:'fit',
modal:true,
autoScroll:true,
height: 300,
width: 400,
padding:5,
draggable:false,
resizable:false,
closable:true,
closeAction:'hide',
html: ' ',
plain: true,
listeners: {
show: function() {
Ext.select('.ext-el-mask').addListener('click', function() {
extComponent.hide();
});
}
},
buttons: [{
text:'OK',
scope: this,
handler: function(){
extComponent.hide();
}
}]
});
}
return extComponent;
}
this.show = function(stopInformationPanel) {
var noticesContent = this.getNoticesContent(stopInformationPanel);
var dialog = this.getDialog();
dialog.setTitle(stopInformationPanel.getCurrentBusStopRecord().name + ' Notices');
dialog.show();
dialog.update(noticesContent);
}
this.getNoticesContent = function(stopInformationPanel) {
var currentBusStopServiceDisruptions = stopInformationPanel.getCurrentBusStopServiceDisruptions();
if(stopInformationPanel.arrivalsGrid.getStopBoardConnection() != undefined && stopInformationPanel.arrivalsGrid.getStopBoardConnection().isLoading()) {
return "Loading...";
}
else if(currentBusStopServiceDisruptions.infoMessages.length == 0 &&
currentBusStopServiceDisruptions.importantMessages.length == 0) {
return "There are no notices at present";
} else {
var noticesContent = '';
Ext.each(currentBusStopServiceDisruptions.importantMessages, function(msg) {
noticesContent += '- ' + msg + '
';
});
Ext.each(currentBusStopServiceDisruptions.infoMessages, function(msg) {
noticesContent += '- ' + msg + '
';
});
return noticesContent + '
';
}
}
}
Ext.ns('KZ');
KZ.ArrivalsGrid = function(stopInformationPanel, connectionToUse, stopSelectedEventHandler) {
var arrivalsGridScope = this;
var refreshingArrivalsMask, stopObjectType, lastUpdated;
var numberOfMinutesVersus24HourClockThreshold = KZ.Config.numberOfMinutesVersus24HourClockOnArrivalBoardThreshold;
var connection = connectionToUse;
// stopDetails holds data needed to display Traveline Cymru timetable links
var stopDetails;
this.stopInformationPanel = stopInformationPanel;
this.queryType; // 'departures' or 'arrivals'
this.store = new Ext.data.JsonStore({
// store configs
autoDestroy: false,
// reader configs
root: 'arrivals',
idProperty: 'atcoCode',
fields: ['routeName', 'routeId', 'origin', 'destination',
// ------- time fields -------
'scheduledTime', 'estimatedTime', // these are fields with times, suitable as a sorting key
'estimatedTimeDisplay', // like '11:32' or 'On time'
'estimatedWait', // like '2 mins' or 'due'
'timeCombinedDisplay', // like '11:32*' or '2 mins'
// ------- -------
'isRealTime', 'stopName', 'stopCode', 'operatorName',
'stopLat', 'stopLng' // optional, currently used just for poi departures
],
sortData : this.sortCriteria
});
this.containsCriticalMessage = false;
this.store.setDefaultSort('estimatedWait');
this.naturalOrderField = 'estimatedWait';
this.direction = undefined;
this.store.arrivalsGridScope = this;
// this.filterRecordsOutsideArrivalsWindow = function(record, id) { //TODO: This isn't called
// return record.get('estimatedWait') <= (numberOfMinutesVersus24HourClockThreshold * 60);
// }
function formatPlateName( currentCellValue ) {
return "" + currentCellValue + ""
}
function stopNameLinkRenderer( currentCellValue ) {
return "" + currentCellValue + ""
}
var columns = new Ext.grid.ColumnModel({
defaults: {
sortable:true
},
columns: [
{id: 'routeNameColumn', header: tr('route'), width: 50, dataIndex: 'routeName'},
{id: 'originColumn', header: tr('from'), width: 194, dataIndex: 'origin'},
{id: 'destinationColumn', header: tr('to'), width: 194, dataIndex: 'destination'},
// two below columns are used only for rail
{id: 'scheduledTimeColumn', header: tr('timetable'), width: 135, dataIndex: 'scheduledTime'},
{id: 'estimatedTimeColumn', header: tr('expected'), width: 135, dataIndex: 'estimatedTimeDisplay'},
// this time column is used for every mode of transport except for rail
{id: 'timeCombinedDisplayColumn', header: tr('time'), width: 135, dataIndex: 'timeCombinedDisplay'},
{id: 'plateNameColumn', header: tr('stand'), width: 190, dataIndex: 'stopName', renderer: formatPlateName },
{id: 'operatorNameColumn', header: tr('operator'), width: 135, dataIndex: 'operatorName'},
{id: 'stopNameLinkColumn', header: tr('stop'), width: 300, dataIndex: 'stopName', renderer: stopNameLinkRenderer }
]
})
this.extComponent = new Ext.grid.GridPanel({
//id: 'arrivals-grid', // according to Ext doc this id must be uniqe, and if it's a fixed string, this will not be unique as there may be many ArrivalGrids
itemId: 'arrivals-grid',
store: this.store,
colModel: columns,
hideMode: 'offsets',
border: false,
stripeRows: true,
stateful: true,
enableColumnHide: false,
enableColumnMove: false,
enableHdMenu: true,
disableSelection: true,
viewConfig: {
emptyText: KZ.Config.noPredictionsAvailableMessage,
emptyTextPriority: 0 /* priority of current empty text value. Prevents overriding custom messages in the grid.
* 0 - very low (no predictions)
* 1 - low (suspended)
* 2 - medium (connection error)
* 3 - high (critical messages)
* 4 - ultra-high (end of the universe)
*/
},
bbar: {
cls: 'x-panel-footer filter-footer clickable',
html: tr('filteringon')
},
fbar: {
ctCls: 'arrivals-grid-footer-container',
cls: 'arrivals-grid-footer',
items: []
},
listeners: {
scope: this,
cellclick:function(grid, rowIndex, columnIndex, e) {
var columnId = grid.getColumnModel().getColumnId(columnIndex);
if (columnId == 'plateNameColumn') {
var record = grid.getStore().getAt(rowIndex);
var data = record.get('stopCode');
this.applyStopNameFilter(data);
this.presentFiltering(true, false);
// get smsCode from clicked stop name;
Ext.each(stopInformationPanel.getCurrentBusStopRecord().stops, function(stop){
if(stop.name == data){
this.stopInformationPanel.setDetailedTitle(lastUpdated, stop.smsCode, stop.name);
}
}, this);
}
if (columnId == 'stopNameLinkColumn') {
var record = grid.getStore().getAt(rowIndex);
stopSelectedEventHandler(record.data.stopCode);
}
}
}
});
// Add footer onclick event once it has been rendered
this.extComponent.on('render', function(gridPanel) {
this.extComponent.bbar.setVisibilityMode(Ext.Element.DISPLAY);
stopInformationPanel.arrivalsGrid.extComponent.bbar.on('click', function(evt, el, cfg) {
if(stopObjectType == 'GROUP'){
this.applyStopNameFilter(undefined);
this.presentFiltering(false, false);
this.stopInformationPanel.setTitle(lastUpdated);
} else {
KZ.RouteFilterDialog.show(stopInformationPanel);
}
}, this);
}, this);
if (KZ.Config.busRouteLinkTemplate) {
var columnModel = this.extComponent.getColumnModel();
var routeNameColumnIndex = columnModel.getIndexById('routeNameColumn');
this.defaultRouteNameColumnRenderer = columnModel.getRenderer(routeNameColumnIndex);
var tpl = new Ext.Template('{route}
');
tpl.compile();
this.linkRouteNameColumnRenderer = function(value) {
return tpl.apply({route:value});
};
}
this.filterRecordsEstimatedWaitPresentation = function(record) {
var estimatedWait = record.get('estimatedWait');
var scheduledTime = record.get('scheduledTime');
var isRealTime = record.get('isRealTime');
var estimatedWaitColumnIndex = this.extComponent.getColumnModel().getIndexById('timeCombinedDisplayColumn');
var estimatedWaitColumnHeader = this.extComponent.getColumnModel().getColumnById('timeCombinedDisplayColumn').header;
if(!isRealTime && (estimatedWaitColumnHeader.indexOf('*') < 0)) {
this.extComponent.getColumnModel().setColumnHeader(estimatedWaitColumnIndex, tr('timetableinfo'));
}
if(KZ.Config.easyToReadDepartureBoardEnabled) {
if(compareArrivalTimes(estimatedWait, numberOfMinutesVersus24HourClockThreshold + ' min') <= 0){
record.data.timeCombinedDisplay = isRealTime ? estimatedWait : estimatedWait + ' *';
} else {
record.data.timeCombinedDisplay = isRealTime ? scheduledTime : scheduledTime + ' *';
}
} else {
record.data.timeCombinedDisplay = isRealTime ? estimatedWait : scheduledTime + ' *';
}
record.commit();
}
var addUrlParam = function(url, param) {
if(url){
url += "&";
}else {
url = "?";
}
return url + param;
}
this.refreshArrivals = function(keepCurrentSortCriteria, autoRefreshFlag, objType) {
var busStopId = stopInformationPanel.getCurrentBusStopRecord().id;
var futureStopBoard = stopInformationPanel.getFutureStopBoardDate();
var urlParams = autoRefreshFlag ? "?ar=1" : "";
urlParams = addUrlParam(urlParams, "objectType=" + objType);
if(futureStopBoard) {
urlParams = addUrlParam(urlParams, "future=" + futureStopBoard.getTime());
}
var message;
if (this.queryType === 'arrivals') {
message = tr('loadingarrivals');
urlParams += '&queryType=arrivals';
} else {
message = tr('loadingdepartures');
}
var url;
if (objType === 'POI') {
url = "/poiDepartures/" + busStopId;
} else {
url = "/stopBoard/" + busStopId + "/" + urlParams;
}
if(!refreshingArrivalsMask) {
refreshingArrivalsMask = new Ext.LoadMask(this.extComponent.ownerCt.el);
}
refreshingArrivalsMask.msg = message;
refreshingArrivalsMask.show();
stopDetails = {};
connection.request({
url: url,
timeout: 60000,
headers: {Accept: 'application/json'},
success: function(resp, opt) {
if(stopInformationPanel.isStopInformationPanelVisible()) {
var stopBoardResults = eval('(' + resp.responseText + ')');
// For testing
//stopBoardResults.serviceDisruptions.criticalMessages = ['A fire at Liverpool Street is causing major delays to all bus routes from this stop.', 'Passengers for Chancery Lane should take the 8 from Blackfriars Bridge.', 'Here is an extra long message to check that wrapping is working properly. Well is it working properly or not, hello hello mdfg msd gnfdg nafdgnfjdgn fjdgnafdjg nfdg.'];
//stopBoardResults.serviceDisruptions.importantMessages = ['This is a very important message.'];
//stopBoardResults.serviceDisruptions.infoMessages = ['There was a problem cos of weather.', 'The 88 will be running a reduced service over Easter weekend.', 'TemporaryLonger message is adLonger message is a dklfmmsdk gfmdgmfdgmsdgmg dgm gm mfd gmfd gkmsfd gmsfd gmsfd gmsfd gkmsfdgk msfd g gkmgkmgmfdgmsfd d bus shelter on some street.', 'Longer message is a longer message hello there why and then there was a cat and that. Good bye.'];
if(stopBoardResults.objectType == 'STOP'){
var filterOutArray = stopBoardResults.filterOut;
if(filterOutArray != undefined && filterOutArray.length > 0 && !KZ.userStopsStore.isTemporaryFilterEnabled(busStopId)) {
KZ.userStopsStore.updateFilters(busStopId, filterOutArray);
}
}
arrivalsGridScope.presentStopBoardResults(keepCurrentSortCriteria, stopBoardResults);
stopDetails.departuresAvailable = true;
if (stopDetails.stopDetailsAvailable === true) {
arrivalsGridScope.addTravelineCymruTimetableLinks();
}
}
},
failure: function(response, opts) {
var failureResp = {};
failureResp.stopBoardMessage = 'noPredictionsDueToSystemError';
failureResp.autoRefreshResponse = autoRefreshFlag ? autoRefreshFlag : false;
arrivalsGridScope.presentStopBoardResults(keepCurrentSortCriteria, failureResp);
KZ.log('server-side failure with status code ' + response.status);
}
});
var isStop = objType === 'STOP';
if (KZ.Config.enableTravelineCymruTimetables && isStop) {
Ext.Ajax.request({
url: '/stopDetails/' + this.stopInformationPanel.getCurrentBusStopRecord().id,
timeout: 10000, // 10 seconds
success: function(response) {
if (this.prepareTravelineCymruTimetableLinks(response) === true) {
stopDetails.stopDetailsAvailable = true;
if (stopDetails.departuresAvailable === true) {
this.addTravelineCymruTimetableLinks();
}
}
},
scope: this
});
}
}
this.presentFiltering = function(filtering, filterForRoute){
this.extComponent.getBottomToolbar().doLayout();
if(filtering){
this.extComponent.getBottomToolbar().show();
}else{
this.extComponent.getBottomToolbar().hide();
}
this.extComponent.syncHeight();
this.extComponent.doLayout();
}
this.presentServiceDisruptions = function(stopBoardResults) {
// no notifications, hide notices button if was previously visible
if(this.stopInformationPanel.toolbar.isNoticesButtonVisible()) {
this.stopInformationPanel.toolbar.hideNoticesButton();
}
if(stopBoardResults.serviceDisruptions != undefined) {
this.stopInformationPanel.setCurrentBusStopServiceDisruptions(stopBoardResults.serviceDisruptions);
if (stopBoardResults.serviceDisruptions
&& ( (stopBoardResults.serviceDisruptions.importantMessages && stopBoardResults.serviceDisruptions.importantMessages.length > 0)
|| (stopBoardResults.serviceDisruptions.infoMessages && stopBoardResults.serviceDisruptions.infoMessages.length > 0)
)) {
this.stopInformationPanel.toolbar.showNoticesButton();
}
if (stopBoardResults.serviceDisruptions.criticalMessages.length >= 1) {
this.containsCriticalMessage = true;
this.store.removeAll();
var criticalMessagesHtml = '';
Ext.each(stopBoardResults.serviceDisruptions.criticalMessages,
function(msg) {
criticalMessagesHtml += '
' + msg + '
';
});
this.setGridMessage(criticalMessagesHtml + '
', 3);
this.extComponent.getView().el.select('.x-grid3-header').setStyle(
'display', 'none');
return true;
}
}
return false;
}
this.presentStopBoardMessages = function(stopBoardResults) {
if (stopBoardResults.stopBoardMessage != undefined) {
this.store.removeAll();
if ((stopBoardResults.stopBoardMessage == 'isSuspended')
|| (stopBoardResults.stopBoardMessage == 'isClosedPermanently')
|| (stopBoardResults.stopBoardMessage == 'isClosedTemporarily')) {
this.setGridMessage(KZ.Config.suspendedStopMessage, 1);
} else if (stopBoardResults.stopBoardMessage == 'noPredictionsDueToSystemError') {
this.setGridMessage(KZ.Config.noPredictionsDueToSystemError, 2);
}
this.extComponent.getView().el.select('.x-grid3-header').setStyle(
'display', 'none');
// failure resistance
if (stopBoardResults.autoRefreshResponse) {
this.stopInformationPanel.cancelAutoRefreshTask(true);
}
return true;
}
return false;
}
this.presentStopBoardResults = function (keepCurrentSortCriteria, stopBoardResults) {
stopObjectType = stopBoardResults.objectType;
lastUpdated = stopBoardResults.lastUpdated;
this.setGridMessage('');
// if there wasn't critical message and stop bard messages
if( !this.presentServiceDisruptions(stopBoardResults) && !this.presentStopBoardMessages(stopBoardResults) ) {
if(keepCurrentSortCriteria) {
var sortState = this.store.getSortState();
this.store.setDefaultSort(sortState.field, sortState.direction);
} else {
this.store.setDefaultSort('estimatedWait',"ASC");
}
if(!stopBoardResults.arrivals || stopBoardResults.arrivals.length == 0) {
this.setGridMessage(KZ.Config.noPredictionsAvailableMessage, 0);
this.extComponent.getView().el.select('.x-grid3-header').setStyle('display', 'none');
this.store.removeAll();
} else {
this.extComponent.getView().el.select('.x-grid3-header').setStyle('display', 'block');
this.store.loadData(stopBoardResults);
}
var stopId = this.stopInformationPanel.getCurrentBusStopRecord().id;
var filters = KZ.userStopsStore.getFilters(stopId);
this.applyArrivalFilter(filters);
}
this.showHideColumnsAfterDepartureBoardRequest();
// this.store.filterBy(this.filterRecordsOutsideArrivalsWindow, this);
var busStopId = this.stopInformationPanel.getCurrentBusStopRecord().id;
if (KZ.userStopsStore.hasFilters(busStopId)) {
this.presentFiltering(true, true);
} else {
this.presentFiltering(false, true);
}
this.stopInformationPanel.setTitle(stopBoardResults.lastUpdated);
if (KZ.Config.busRouteLinkTemplate) {
var columnModel = this.extComponent.getColumnModel();
var routeNameColumnIndex = columnModel.getIndexById('routeNameColumn');
var record = this.stopInformationPanel.getCurrentBusStopRecord();
if (record.mode === 'BUS' || record.objectType === 'POI' || (record.mode === 'TRAM' && this.metroLinksEnabled())) {
columnModel.setRenderer(routeNameColumnIndex, this.linkRouteNameColumnRenderer);
} else {
columnModel.setRenderer(routeNameColumnIndex, this.defaultRouteNameColumnRenderer);
}
}
this.extComponent.show();
this.extComponent.getView().refresh();
try {
this.extComponent.doLayout();
} catch (e) {
KZ.log("Error laying out Arrivals Grid " + e);
}
if (refreshingArrivalsMask != undefined) {
refreshingArrivalsMask.hide();
}
this.store.each(this.filterRecordsEstimatedWaitPresentation, this);
this.extComponent.show();
// this.extComponent.getView().refresh();
// this.extComponent.doLayout();
}
this.abortLoadingData = function() {
if (connection) {
connection.abort();
}
if (refreshingArrivalsMask !== undefined) {
refreshingArrivalsMask.hide();
}
}
this.metroLinksEnabled = function() {
return (KZ.Config.metroRouteLinkTemplate && KZ.Config.metroRouteLinkTemplate != "");
}
this.showHideColumnsAfterDepartureBoardRequest = function() {
if (this.queryType === 'arrivals') {
showColumn('originColumn');
hideColumn('destinationColumn');
}
if (this.queryType === 'departures') {
showColumn('destinationColumn');
hideColumn('originColumn');
}
}
this.prepareTravelineCymruTimetableLinks = function(response) {
var data = Ext.decode(response.responseText);
if (data.stopDetails === undefined || data.stopDetails.serviceDetails === undefined) {
return false;
}
stopDetails.map = {};
var items = data.stopDetails.serviceDetails;
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (item.fullTimetableUrl) {
stopDetails.map[item.number] = item.fullTimetableUrl;
}
}
return true;
}
this.addTravelineCymruTimetableLinks = function() {
var tpl = new Ext.Template('{number}
'.bind(tr('timetablelinktitle')));
tpl.compile();
function addTravelineCymruTimetableLink(record) {
var number = record.get('routeName');
if (stopDetails.map.hasOwnProperty(number)) {
var link = tpl.apply({
number: number,
url: stopDetails.map[number]
});
record.set('routeName', link);
record.commit();
}
}
this.store.each(addTravelineCymruTimetableLink);
}
this.setGridMessage = function(message, priority, validate) {
if(validate != undefined && validate && !this.canUpdateMessage(priority)) {
return;
}
this.extComponent.getView().emptyText = message;
if(priority == undefined) {
priority = 0;
}
this.extComponent.getView().emptyTextPriority = priority;
}
this.canUpdateMessage = function(msgPriority) {
if(this.extComponent.getView().emptyText <= msgPriority) {
return true;
} else {
return false;
}
}
function hideColumn(columnId) {
setColumnHidden(columnId, true);
}
function showColumn(columnId) {
setColumnHidden(columnId, false);
}
function setColumnHidden(columnId, hidden) {
columns.setHidden(columns.getIndexById(columnId), hidden);
}
function setColumnHeader(columnId, header) {
columns.setColumnHeader(columns.getIndexById(columnId), header);
}
function setColumnWidth(columnId, width) {
columns.setColumnWidth(columns.getIndexById(columnId), width);
}
this.showFooter = function(show, text) {
var grid = this.extComponent;
var toolbar = grid.getFooterToolbar();
toolbar.update(text);
if (show) {
toolbar.show();
} else {
toolbar.hide();
}
grid.syncHeight();
grid.doLayout();
}
this.modeChanged = function(mode, type) {
// set headers and footers
if (mode == 'TRAIN' || mode == 'TRAM') {
setColumnHeader('plateNameColumn', tr('platform'));
} else if (type === 'POI') {
setColumnHeader('plateNameColumn', 'Stop');
} else {
setColumnHeader('plateNameColumn', tr('stand'));
}
if (mode === 'TRAIN' || mode === 'TRAM' || mode === 'BUS') {
// here we show only a number, so the column can be narrow
setColumnWidth('plateNameColumn', 70);
} else {
setColumnWidth('plateNameColumn', 190);
}
if (KZ.Config.layerRailEnabled) {
if (mode == 'TRAIN') {
var footer = tr('nreattribution').bind('
', 'www.nationalrail.co.uk
');
this.showFooter(true, footer);
} else {
this.showFooter(false, '');
}
}
// show / hide columns
if (type == 'GROUP') {
showColumn('plateNameColumn');
} else {
hideColumn('plateNameColumn');
if(mode === 'BUS') {
setColumnWidth('destinationColumn', 400);
setColumnWidth('originColumn', 400);
}
}
if (type == 'POI') {
showColumn('stopNameLinkColumn');
} else {
hideColumn('stopNameLinkColumn');
}
if (mode == 'RIVER') {
hideColumn('plateNameColumn');
}
if (mode == 'TRAIN') {
showColumn('operatorNameColumn');
hideColumn('routeNameColumn');
showColumn('scheduledTimeColumn');
showColumn('estimatedTimeColumn');
hideColumn('timeCombinedDisplayColumn');
} else {
hideColumn('operatorNameColumn');
showColumn('routeNameColumn');
hideColumn('scheduledTimeColumn');
hideColumn('estimatedTimeColumn');
showColumn('timeCombinedDisplayColumn');
}
}
}
KZ.ArrivalsGrid.prototype.sortCriteria = function() {
var arrivalsGrid = this.arrivalsGridScope; // switch the scope
var columnName = arrivalsGrid.extComponent.store.sortInfo.field;
var direction = arrivalsGrid.extComponent.store.sortInfo.direction;
arrivalsGrid.direction = direction || 'ASC';
switch(columnName){
case 'routeName' :
arrivalsGrid.store.data.sort(arrivalsGrid.direction, arrivalsGrid.routeSort);
break;
case 'destination' :
arrivalsGrid.naturalOrderField = 'destination';
arrivalsGrid.store.data.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
case 'origin' :
arrivalsGrid.naturalOrderField = 'origin';
arrivalsGrid.store.data.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
case 'stopName' :
arrivalsGrid.naturalOrderField = 'stopName';
arrivalsGrid.store.data.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
case 'scheduledTime' :
arrivalsGrid.naturalOrderField = 'scheduledTime';
arrivalsGrid.store.data.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
case 'estimatedTimeDisplay' :
arrivalsGrid.naturalOrderField = 'estimatedTime';
arrivalsGrid.store.data.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
case 'operatorName' :
arrivalsGrid.naturalOrderField = 'operatorName';
arrivalsGrid.store.data.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
default :
arrivalsGrid.naturalOrderField = 'estimatedWait';
arrivalsGrid.store.data.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
}
if(arrivalsGrid.store.snapshot && arrivalsGrid.store.snapshot != arrivalsGrid.store.data){
switch(columnName){
case 'routeName' :
arrivalsGrid.store.snapshot.sort(arrivalsGrid.direction, arrivalsGrid.routeSort);
break;
case 'destination' :
arrivalsGrid.naturalOrderField = 'destination';
arrivalsGrid.store.snapshot.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
case 'stopName' :
arrivalsGrid.naturalOrderField = 'stopName';
arrivalsGrid.store.snapshot.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
case 'scheduledTime' :
arrivalsGrid.naturalOrderField = 'scheduledTime';
arrivalsGrid.store.snapshot.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
case 'estimatedTimeDisplay' :
arrivalsGrid.naturalOrderField = 'estimatedTime';
arrivalsGrid.store.snapshot.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
default :
arrivalsGrid.naturalOrderField = 'estimatedWait';
arrivalsGrid.store.snapshot.sort(arrivalsGrid.direction, arrivalsGrid.naturalOrderSort);
break;
}
}
}
KZ.ArrivalsGrid.prototype.routeSort = function(r1, r2){
r1 = r1.get('routeName');
r2 = r2.get('routeName');
var result = 0;
if (!r1 && !r2) { //if both are undefined
return 0;
} else if (!r2) {
return 1;
} else if (!r1) {
return -1;
}
var totalLength = Math.max(r1.length, r2.length) + 1;
var length = 0;
for (var i = 0; result == 0 && i < totalLength; i++) {
if (KZ.util.isDigit(r1.charAt(i)) && KZ.util.isDigit(r2.charAt(i))) {
length++;
}
else if (KZ.util.isDigit(r1.charAt(i))) {
result = (length == 0 ? -1 : 1);
}
else if (KZ.util.isDigit(r2.charAt(i))) {
result = (length == 0 ? 1 : -1);
}
else {
for (var j = i - length; result == 0 && j <= i; j++) {
result = KZ.util.charCmp((r1.charAt(j)).toLowerCase(), (r2.charAt(j)).toLowerCase());
}
length = 0;
}
}
return result;
};
// static function
KZ.ArrivalsGrid.arrivalTimeRenderer = function(val) {
return (val =='due') ? '0 min' : val;
}
KZ.ArrivalsGrid.prototype.naturalOrderSort = function(r1, r2){
var arrivalsGrid = r1.store.arrivalsGridScope; // switch the scope
r1OrderingField = r1.get(arrivalsGrid.naturalOrderField);
r2OrderingField = r2.get(arrivalsGrid.naturalOrderField);
var result = undefined;
if(arrivalsGrid.naturalOrderField == 'estimatedWait'){
r1OrderingField = KZ.ArrivalsGrid.arrivalTimeRenderer(r1OrderingField);
r2OrderingField = KZ.ArrivalsGrid.arrivalTimeRenderer(r2OrderingField);
result = compareArrivalTimes(r1OrderingField, r2OrderingField);
} else {
result = (r1OrderingField > r2OrderingField) ? 1 : ((r1OrderingField < r2OrderingField) ? -1 : 0);
}
if(result == 0){ //In case of equals ordering by a second field
if(arrivalsGrid.naturalOrderField == 'estimatedWait'){
result = arrivalsGrid.routeSort(r1,r2);
} else if(arrivalsGrid.naturalOrderField == 'destination'){
arrivalsGrid.naturalOrderField = 'estimatedWait';
result = arrivalsGrid.naturalOrderSort(r1,r2);
arrivalsGrid.naturalOrderField = 'destination';
}
if(arrivalsGrid.direction == 'DESC'){ //Changing the direction of the second criteria too.
result = -result;
}
}
return result;
};
KZ.ArrivalsGrid.prototype.applyStopNameFilter = function(stopCode) {
if(!this.containsCriticalMessage) {
this.store.filterBy(function(record, id) {
if (!KZ.util.isSet(stopCode)) {
return true;
}
if(stopCode == record.get('stopCode')){
return true;
}else{
return false;
}
}, this);
if(this.store.getCount() == 0) {
this.setGridMessage(KZ.Config.noPredictionsAvailableMessage, 0, true);
}
}
}
KZ.ArrivalsGrid.prototype.applyArrivalFilter = function(filterOutArray) {
if(!this.containsCriticalMessage) {
this.store.filterBy(function(record, id) {
if (!KZ.util.isSet(filterOutArray)) {
return true;
}
if(filterOutArray.indexOf(record.get('routeId')) == -1){
return true;
}else{
return false;
}
}, this);
if(this.store.getCount() == 0) {
this.setGridMessage(KZ.Config.noPredictionsAvailableMessage, 0, true);
}
}
}
this.compareArrivalTimes = function(time1, time2) {
if(time1.length == time2.length) {
return (time1 > time2) ? 1 : ((time1 < time2) ? -1 : 0);
} else if(time1.length < time2.length) {
return -1;
} else {
return 1;
}
}
Ext.ns('KZ');
KZ.TaxisPanel = function(connection) {
var loadMask;
// Example data:
//
//
//
var store = new Ext.data.XmlStore({
// reader configs
record: 'Taxi',
idPath: '@phoneNumber',
fields: [
{name: 'name', mapping: '@name'},
{name: 'phoneNumber', mapping: '@phoneNumber'},
{name: 'milesFromStation', mapping: '@milesFromStation',
sortType: function(miles) {
return parseFloat(miles);
}
}
]
});
var extComponent = new Ext.grid.GridPanel({
itemId: 'taxis-panel',
border: false,
store: store,
stripeRows: true,
enableColumnHide: false,
enableColumnMove: false,
disableSelection: true,
colModel: new Ext.grid.ColumnModel({
defaults: {
sortable: true
},
columns: [
{header: 'Operator', dataIndex: 'name', width: 150},
{header: 'Telephone', dataIndex: 'phoneNumber', width: 100}
]
})
});
this.getExtComponent = function() {
return extComponent;
}
var setEmptyText = function(text) {
extComponent.getView().emptyText = text;
}
var mask = function() {
if (loadMask === undefined) {
loadMask = new Ext.LoadMask(extComponent.ownerCt.el, {msg:'Loading taxis...'});
}
loadMask.show();
extComponent.hide();
}
var unmask = function() {
extComponent.show();
loadMask.hide();
}
this.loadData = function(lat, lng) {
mask();
connection.request({
url: '/taxis',
method: 'GET',
params: {
lat: lat,
lng: lng
},
success: function(response, opts) {
setEmptyText('There is no taxi information for this location');
store.loadData(response.responseXML);
keepJustFiveNearestTaxiRecords();
unmask();
},
failure: function(response, opts) {
setEmptyText('A problem occurred while loading taxi information, please try again later');
store.loadData({});
unmask();
}
});
}
var keepJustFiveNearestTaxiRecords = function() {
store.sort('milesFromStation', 'ASC');
// --- keep at most 5 first records ---
var count = store.getTotalCount();
for (var i = count-1; i >=5 ; i--) {
store.removeAt(i);
}
store.commitChanges();
}
}
Ext.ns('KZ');
KZ.TimetablesPanel = function(connection) {
var loadMask;
// Content is like:
// "timetables" : [{
// "timetable" : {
// "label" : "Road 1",
// "url" : "http://example.com"
// },
// "timetable" : {
// "label" : "Road 2",
// "url" : "http://example2.com"
// }]
// }
// The 'timetable' element might seem to be excessible, but this is useful while displaying the timetable link - there is only one column
// that contains a link ( element), and it needs both 'label' and 'url' - it's easier to have them both in one object (see column renderer).
var store = new Ext.data.JsonStore({
// store configs
autoDestroy: false,
// reader configs
root: 'timetables',
fields: ['timetable']
});
var createTimetableColumnRenderer = function() {
var template = new Ext.Template('{label}');
template.compile();
return function(value) {
return template.apply(value);
// 'value' is a single element in store, and is like:
// {
// "label" : "Road 1",
// "url" : "http://example.com"
// }
};
}
var extComponent = new Ext.grid.GridPanel({
itemId: 'timetables-panel',
border: false,
store: store,
stripeRows: true,
enableColumnHide: false,
enableColumnMove: false,
disableSelection: true,
colModel: new Ext.grid.ColumnModel({
defaults: {
sortable: false
},
columns: [
{id: 'timetable', header: 'Route', dataIndex: 'timetable', width: 400, renderer: createTimetableColumnRenderer()}
]
}),
autoExpandColumn: 'timetable'
});
this.getExtComponent = function() {
return extComponent;
}
var setEmptyText = function(text) {
extComponent.getView().emptyText = text;
}
var mask = function() {
if (loadMask === undefined) {
loadMask = new Ext.LoadMask(extComponent.ownerCt.el, {msg:'Loading timetables...'});
}
loadMask.show();
extComponent.hide();
}
var unmask = function() {
extComponent.show();
loadMask.hide();
}
this.loadData = function(stopCode) {
mask();
connection.request({
url: '/timetables/' + stopCode,
headers: {Accept: 'application/json'},
success: function(response, opts) {
setEmptyText('There is no timetable information for this location');
var timetables = Ext.decode(response.responseText);
store.loadData(timetables);
unmask();
},
failure: function(response, opts) {
setEmptyText('A problem occurred while loading timetable information, please try again later');
store.loadData({timetables:[]});
unmask();
}
});
}
}
Ext.ns('KZ');
KZ.StationFacilitiesPanel = function(connection) {
var loadMask;
var reader = new Ext.data.JsonReader({
fields: ['type', 'details', 'category']
});
var store = new Ext.data.GroupingStore({
autoDestroy: false,
reader: reader,
groupField:'category'
});
var createI18nBoldColumnRenderer = function() {
var template = new Ext.Template('{type}');
template.compile();
return function(value) {
return template.apply({'type':tr(value)});
};
}
var createDetailsColumnRenderer = function() {
var template = new Ext.XTemplate(
'n/a',
'{.}
',
{
isEmpty: function(arr) {
console.info(arr.length);
return arr.length == 0;
}
});
template.compile();
return function(value) {
return template.apply({'details' : value});
};
}
var groupingView = new Ext.grid.GroupingView({
forceFit:true,
showGroupName: false
});
// ugly hack to disable collasble groupingView - won't work above Ext.JS > 3.2
groupingView.interceptMouse = Ext.emptyFn;
var extComponent = new Ext.grid.GridPanel({
itemId: 'facilities-panel',
id: 'facilities-panel',
border: false,
store: store,
stripeRows: true,
enableColumnHide: false,
enableColumnMove: false,
disableSelection: true,
colModel: new Ext.grid.ColumnModel({
defaults: {
sortable: false
},
columns: [
{id: 'type', header: 'Type', dataIndex: 'type', width: 100, renderer: createI18nBoldColumnRenderer()},
{id: 'details', header: 'Details', dataIndex: 'details', width: 400, renderer: createDetailsColumnRenderer()},
{id: 'category', header: 'Category', dataIndex: 'category', hidden: true, renderer: tr}
]
}),
autoExpandColumn: 'details',
listeners: {
render: function(grid) {
grid.getView().el.select('.x-grid3-header').setStyle('display', 'none');
}
},
view: groupingView
});
this.getExtComponent = function() {
return extComponent;
}
var setEmptyText = function(text) {
extComponent.getView().emptyText = text;
}
var mask = function() {
if (loadMask === undefined) {
loadMask = new Ext.LoadMask(extComponent.ownerCt.el, {msg:'Loading station facilities...'});
}
loadMask.show();
extComponent.hide();
}
var unmask = function() {
extComponent.show();
loadMask.hide();
}
this.loadData = function(stopCode) {
mask();
connection.request({
url: '/facilities/' + stopCode,
headers: {Accept: 'application/json'},
success: function(response, opts) {
setEmptyText('There are no facilities for this station.');
var facilities = Ext.decode(response.responseText);
store.loadData(facilities);
unmask();
},
failure: function(response, opts) {
setEmptyText('A problem occurred while loading station facilities, please try again later.');
store.loadData([]);
unmask();
}
});
}
}
Ext.ns('KZ');
// I'm responsible for enabling and disabling stop information toolbar items.
KZ.ToolbarFeatureManager = function(toolbar) {
// type: POI/STOP/GROUP
// mode: BUS/TRAIN/TRAM/FERRY
// record: "bus stop record" or poi record
// tab: e.g. departures/arrivals/taxis/timetables for GROUP/TRAIN
this.updateToolbar = function(type, mode, record, tab) {
toolbar.hideAllItems();
if (type === 'POI') {
toolbar.showInformationDeparturesToggle();
if (tab === 'departures') {
toolbar.showRefreshButton();
toolbar.showFutureStopBoardDatePicker();
}
toolbar.showTransportDirectJp();
toolbar.showWalkit();
toolbar.showNexusJp();
}
if (type === 'STOP') {
toolbar.showFutureStopBoardDatePicker();
toolbar.showFavouriteButton();
toolbar.showRefreshButton();
toolbar.showFilterRoutesButton();
toolbar.showMobileAndSmsButton();
toolbar.updateAfterRefreshButtonSeparator();
toolbar.showTransportDirectJp();
toolbar.showTravelineCymruJp();
toolbar.showNexusJp();
toolbar.showWalkit();
toolbar.updateInterchangeMapButton(record.id);
toolbar.showPdfButton(record.id);
toolbar.setFilterToolBarText(record.id);
}
if (type === 'GROUP') {
toolbar.showFavouriteButton();
if (tab === 'arrivals' || tab === 'departures') {
toolbar.showFutureStopBoardDatePicker();
toolbar.showRefreshButton();
}
switch (mode) {
case 'TRAIN':
toolbar.showRailToggle();
toolbar.showTravelineCymruJp();
toolbar.showNexusJp();
toolbar.showTransportDirectJp();
toolbar.showWalkit();
break;
case 'TRAM':
toolbar.showMetroToggle();
toolbar.showTransportDirectJp();
toolbar.showNexusJp();
toolbar.showWalkit();
break;
case 'BUS':
toolbar.showBusStationToggle();
toolbar.showTransportDirectJp();
toolbar.showNexusJp();
toolbar.showWalkit();
break;
case 'FERRY':
toolbar.showNexusJp();
break;
}
}
toolbar.setFavouriteToolBarIcon();
toolbar.coalesceSeparators();
if (toolbar.allItemsAreHidden()) {
toolbar.hide();
} else {
toolbar.show();
}
}
}
Ext.ns('KZ');
/* This adds "More" to the overflow toolbar button as opposed to the default >> icon */
Ext.override(Ext.layout.ToolbarLayout, {
initMore : function(){
if(!this.more){
this.moreMenu = new Ext.menu.Menu({
ownerCt : this.container,
listeners: {
beforeshow: this.beforeMoreShow,
scope: this
}
});
this.more = new Ext.Button({
text: 'More',
menu : this.moreMenu,
ownerCt : this.container
});
var td = this.insertCell(this.more, this.extrasTr, 100);
this.more.render(td);
}
}
});
var mobileAndSmsTitle = undefined;
var disableMobileAndSmsButton = false;
var timetableConnection = undefined;
if(KZ.Config.enableMobile && KZ.Config.enableSMS){
mobileAndSmsTitle = 'Mobile & ' + KZ.Config.String.SmsServiceTitle;
} else if (KZ.Config.enableMobile) {
mobileAndSmsTitle = 'Mobile';
} else if (KZ.Config.enableSMS){
mobileAndSmsTitle = KZ.Config.String.SmsServiceTitle;
} else {
disableMobileAndSmsButton = true;
}
KZ.StopInformationToolbar = function(stopInformationPanel, showMyStopsButton) {
var ignoreBoardUpdateRequest = false;
if(showMyStopsButton == undefined) {
showMyStopsButton = false;
}
this.taskMgr = new Ext.util.TaskRunner();
this.show = function() {
this.extComponent.show();
}
this.hide = function() {
this.extComponent.hide();
}
this.hideAllItems = function() {
this.extComponent.items.each(function(item) {
item.hide();
});
}
this.allItemsAreHidden = function() {
var firstVisibleItem = this.extComponent.items.find(function(item) {
return item.hidden === false;
});
return firstVisibleItem === null;
}
this.coalesceSeparators = function() {
var previousItem = {};
this.extComponent.items.each(function(item) {
if (item.hidden) {
return;
}
if (item.xtype === 'tbseparator' && previousItem.xtype === 'tbseparator') {
previousItem.hide();
}
previousItem = item;
});
}
this.setFavouriteToolBarIcon = function() {
var btn = this.extComponent.getComponent('add-remove-favourite-toolbar-button');
if(btn.hidden) return;
if(KZ.userStopsStore.isFavourite(stopInformationPanel.getCurrentBusStopRecord().id)) { // TODO combine 4 lines into a method
btn.setIconClass('remove-favourite-icon');
btn.setText(tr('removefrommystops'));
} else {
btn.setIconClass('add-favourite-icon');
btn.setText(tr('addtomystops'));
}
this.extComponent.doLayout();
}
this.showFavouriteButton = function() {
this.extComponent.getComponent('add-remove-favourite-toolbar-button').show();
}
this.setFilterToolBarText = function(busStopId) {
var btn = this.extComponent.getComponent('filter-toolbar-button');
if (KZ.userStopsStore.hasFilters(busStopId)) {
btn.setText('Filter routes is on');
} else {
btn.setText(tr('filterroutes'));
}
}
var getTimetableConnection = function() {
if(timetableConnection == undefined) {
timetableConnection = new Ext.data.Connection({
timeout: 8000,
autoAbort: false
});
}
return timetableConnection;
}
this.initInterchangeMapButton = function() {
this.extComponent.add({
xtype: 'tbseparator',
itemId: 'interchange-map-button-seperator',
hidden: true
}, {
itemId: 'interchange-map-button',
hidden: true,
text: 'Interchange map',
scope: this,
iconCls: 'pdf-icon',
handler: function(button) {
var stopCode = stopInformationPanel.getCurrentBusStopRecord().id;
_gaq.push(['_trackEvent', 'Stop Information Toolbar', 'Download Interchange Map Button Pressed', stopCode]);
window.open(this.interchangeMapUrl, '_blank');
}
});
}
this.updateInterchangeMapButton = function(stopCode) {
if (!KZ.Config.enableInterchangeMaps) {
return;
}
getTimetableConnection().request({
url: '/interchangeMap/' + stopCode,
method: 'GET',
scope: this,
success: function(response, options) {
var data = Ext.decode(response.responseText);
if (data.url) {
this.interchangeMapUrl = data.url;
this.showInterchangeMapButton();
}
}
});
}
this.showInterchangeMapButton = function() {
this.extComponent.getComponent('interchange-map-button-seperator').show();
this.extComponent.getComponent('interchange-map-button').show();
this.extComponent.doLayout();
}
this.showPdfButton = function(stopId) {
if (KZ.Config.enableTimetablePdfButton === false) {
return;
}
getTimetableConnection().request({
url: "/timetableUrl/"+ stopId,
method: 'GET',
scope: this,
success: function(resp, opt) {
var urlResults = eval('(' + resp.responseText + ')');
if(urlResults.url){
btn = this.extComponent.getComponent('download-button');
btnSep = this.extComponent.getComponent('download-button-seperator');
btn.show();
btnSep.show();
this.urlLink = urlResults.url;
this.extComponent.doLayout();
} else {
this.hidePdfButton();
}
},
failure: function(response, opts) {
this.hidePdfButton();
}
});
}
this.hidePdfButton = function() {
var btn = this.extComponent.getComponent('download-button');
btn.hide();
btn = this.extComponent.getComponent('download-button-seperator');
btn.hide();
this.extComponent.doLayout();
}
this.hideNoticesButton = function() {
var btn = this.extComponent.getComponent('notices-button');
btn.hide();
btn = this.extComponent.getComponent('notices-button-seperator');
btn.hide();
this.extComponent.doLayout();
}
this.showNoticesButton = function() {
var btn = this.extComponent.getComponent('notices-button');
btn.show();
btn.el.fadeIn({duration:4, useDisplay:true});
btn = this.extComponent.getComponent('notices-button-seperator');
btn.show();
this.extComponent.doLayout();
}
this.isNoticesButtonVisible = function() {
return this.extComponent.getComponent('notices-button').isVisible();
}
this.cancelIgnoreBoardUpdateRequest = function() {
ignoreBoardUpdateRequest = false;
}
this.autoRefreshTask = {
autoRefreshRepeat : Math.floor(KZ.Config.autoRefreshDuration/KZ.Config.autoRefreshInterval),
running : false,
run : function(invocationCount) {
if(!this.running) {
if(invocationCount != 1) {
return true;
} else {
this.running = true;
}
}
stopInformationPanel.arrivalsGrid.refreshArrivals(true, true);
stopInformationPanel.toolbar.extComponent.getComponent('refresh-button').setText(tr('autorefreshing'));
stopInformationPanel.toolbar.extComponent.getComponent('refresh-button').menu.items.first().setText(tr('disableautorefresh'));
if(invocationCount > this.autoRefreshRepeat) {
stopInformationPanel.cancelAutoRefreshTask(false, this.autoRefreshTask);
this.running = false;
return false; // task termination
}
},
interval : KZ.Config.autoRefreshInterval * 1000,
scope : this.autoRefreshTask
};
this.createRefreshMenu = function(visible) {
if(visible) {
return new Ext.menu.Menu({
autoDestroy: true,
items: [
{
text: tr('autorefresh'),
itemId: 'auto-refresh-button',
iconCls: 'auto-refresh-icon',
hidden: !KZ.Config.enableAutoRefresh,
scope:this,
handler: function() {
if (!this.autoRefreshTask.running || this.autoRefreshTask.running == false){
Ext.TaskMgr.start(this.autoRefreshTask);
KZ.AutoRefreshArrivalsDialog.show();
}
else if (this.autoRefreshTask.running == true) {
stopInformationPanel.disableAutoRefreshTask(this.autoRefreshTask);
}
_gaq.push(['_trackEvent', 'Stop Information Toolbar', 'Auto-Refresh Button Pressed', stopInformationPanel.getCurrentBusStopRecord().id]);
}
}
]
});
} else {
return null;
}
}
var openUrl = function(template, options) {
var busStop = stopInformationPanel.getCurrentBusStopRecord();
var url = template.apply(options.parameters(busStop));
window.open(url, '_blank');
}
this.addTransportDirectJp = function() {
this.addFromToLinks({
id: 'transport-direct-jp',
callback: openUrl,
parameters: function(busStop) {
var c = WGS84LL2OSGB36EN(busStop.lat, busStop.lng);
return {name: encodeURIComponent(busStop.name), easting: encodeURIComponent(c.easting), northing: encodeURIComponent(c.northing)};
},
label: 'Plan a journey',
fromArgument: new Ext.Template(KZ.Config.transportDirectJpFromUrlTemplate, {compiled:true}),
toArgument: new Ext.Template(KZ.Config.transportDirectJpToUrlTemplate, {compiled:true})
});
}
this.showTransportDirectJp = function() {
this.showJp(KZ.Config.enableTransportDirectJp, 'transport-direct-jp');
}
this.addWalkit = function() {
this.addFromToLinks({
id: 'walkit',
callback: openUrl,
parameters: function(busStop) {
return {name: encodeURIComponent(busStop.name)};
},
label: 'Plan your walking route',
fromArgument: new Ext.Template(KZ.Config.walkitFromUrlTemplate, {compiled:true}),
toArgument: new Ext.Template(KZ.Config.walkitToUrlTemplate, {compiled:true})
});
}
this.showWalkit = function() {
if (!KZ.Config.enableWalkit) {
return;
}
this.extComponent.getComponent('walkit-separator').show();
this.extComponent.getComponent('walkit-menu').show();
}
function postData(url, data, newWindow) {
var form = document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', url);
if(newWindow) {
form.setAttribute('target', '_blank');
}
for (var key in data) {
if (data.hasOwnProperty(key)) {
var field = document.createElement('input');
field.setAttribute('type', 'hidden');
field.setAttribute('name', key);
field.setAttribute('value', data[key]);
form.appendChild(field);
}
}
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
}
function addZ(n) {
return n < 10 ? '0' + n : '' + n;
}
function getDdMmYyyy(date) {
return addZ(date.getDate()) + '/' + addZ(date.getMonth() + 1) + '/' + date.getFullYear();
}
function getHhMm(date) {
return addZ(date.getHours()) + ':' + addZ(date.getMinutes());
}
var openTravelineCymruJp = function(type, options) {
var now = new Date(),
stop = stopInformationPanel.getCurrentBusStopRecord();
if (stop.mode === 'TRAIN') {
stop = stop.stops[0];
}
postData(KZ.Config.travelineCymruJp.url, {
confirmedLocationName: stop.name,
confirmedLocationNaptan: stop.id,
departureTimeFlag: '1',
date: getDdMmYyyy(now),
time: getHhMm(now),
searchType: type
}, true);
}
var openNexusJp = function(type, options) {
var now = new Date(),
stop = stopInformationPanel.getCurrentBusStopRecord();
if (stop.mode === 'TRAIN') {
stop = stop.stops[0];
}
var atTimeMinute = (Math.ceil(now.getMinutes()/15) * 15) % 60;
var params = {
form_id : 'jplanner_display_form',
'at_time[hour]' : now.getHours(),
// valid minutes are 00,15,30,45
'at_time[minute]' : atTimeMinute ? atTimeMinute : '00',
'on_date[date]' : getDdMmYyyy(now),
m : 'd', // valid: a (arrival by), d (leaving at)
step : 2
};
params[type] = stop.name;
postData(KZ.Config.nexusJp.url, params, false);
}
this.addTravelineCymruJp = function() {
this.addFromToLinks({
id: 'traveline-cymru-jp',
callback: openTravelineCymruJp,
label: tr('planajourney'),
fromArgument: 'From',
toArgument: 'To'
});
}
this.addNexusJp = function() {
this.addFromToLinks({
id: 'nexus-jp',
callback: openNexusJp,
label: tr('planajourney'),
fromArgument: 'ab',
toArgument: 'de'
});
}
this.showTravelineCymruJp = function() {
this.showJp(KZ.Config.travelineCymruJp.enabled, 'traveline-cymru-jp');
}
this.showNexusJp = function() {
this.showJp(KZ.Config.nexusJp.enabled, 'nexus-jp');
}
this.showJp = function(enabled, id) {
if (!enabled) {
return;
}
this.extComponent.getComponent(id + '-separator').show();
this.extComponent.getComponent(id + '-menu').show();
}
this.addFromToLinks = function(options) {
var menu = new Ext.menu.Menu({
items: [
{text:'{0}
'.bind(tr('fromthisstop')),handler:function(){options.callback(options.fromArgument, options)}},
{text:'{0}
'.bind(tr('tothisstop')),handler:function(){options.callback(options.toArgument, options)}}
]
});
this.extComponent.add(
{itemId:options.id+'-separator',xtype:'tbseparator',hidden:true},
{itemId:options.id+'-menu',text:options.label,iconCls:'plan-journey-icon',menu:menu,hidden:true}
);
this.extComponent.doLayout();
}
this.addGroupToggle = function() {
var idx = 1;
// unique toggleGroup name for each StopInormationToolbar
var toggleGroup = 'unique-toggle-id-' + Math.random();
function refresh(queryType) {
stopInformationPanel.arrivalsGrid.queryType = queryType;
stopInformationPanel.arrivalsGrid.refreshArrivals(false, false, stopInformationPanel.getCurrentBusStopRecord().objectType);
stopInformationPanel.activateArrivalsGrid();
}
var updateToolbar = function(tab) {
var record = stopInformationPanel.getCurrentBusStopRecord();
stopInformationPanel.toolbarFeatureManager.updateToolbar('GROUP', record.mode, record, tab);
}
var departuresButton = new Ext.Button({
xtype: 'button',
text: tr('departures'),
itemId: 'departures-button',
enableToggle: true,
allowDepress: false,
toggleGroup: toggleGroup,
pressed: true,
toggleHandler: function(button, state) {
if (state === false) {
return;
}
updateToolbar('departures');
refresh('departures');
}
});
this.extComponent.insert(idx++, departuresButton);
var arrivalsButton = new Ext.Button({
xtype: 'button',
text: tr('arrivals'),
itemId: 'arrivals-button',
enableToggle: true,
allowDepress: false,
toggleGroup: toggleGroup,
toggleHandler: function(button, state) {
if (state === false) {
return;
}
updateToolbar('arrivals');
refresh('arrivals');
}
});
this.extComponent.insert(idx++, arrivalsButton);
var facilitiesButton = new Ext.Button({
xtype: 'button',
text: 'Facilities',
itemId: 'facilities-button',
enableToggle: true,
allowDepress: false,
toggleGroup: toggleGroup,
toggleHandler: function(button, state) {
if (state === false) {
return;
}
updateToolbar('facilities');
stopInformationPanel.activateStationFacilitiesPanel();
}
});
this.extComponent.insert(idx++, facilitiesButton);
if (KZ.Config.rail.timetablesEnabled) {
var timetablesButton = new Ext.Button({
xtype: 'button',
text: 'Timetables',
itemId: 'timetables-button',
enableToggle: true,
allowDepress: false,
toggleGroup: toggleGroup,
toggleHandler: function(button, state) {
if (state === false) {
return;
}
updateToolbar('timetables');
stopInformationPanel.activateTimetablesPanel();
}
});
this.extComponent.insert(idx++, timetablesButton);
}
if (KZ.Config.rail.taxisEnabled) {
var taxisButton = new Ext.Button({
xtype: 'button',
text: 'Taxis',
itemId: 'taxis-button',
enableToggle: true,
allowDepress: false,
toggleGroup: toggleGroup,
toggleHandler: function(button, state) {
if (state === false) {
return;
}
updateToolbar('taxis');
stopInformationPanel.activateTaxisPanel();
}
});
this.extComponent.insert(idx++, taxisButton);
}
this.extComponent.insert(idx++, {
xtype: 'tbseparator',
itemId: 'departures-arrivals-separator'
});
} // addGroupToggle
this.showGroupToggle = function(layer, enabled) {
if (KZ.Config[layer].facilitiesEnabled) {
this.extComponent.getComponent('facilities-button').show();
enabled = true;
}
if (KZ.Config[layer].taxisEnabled) {
this.extComponent.getComponent('taxis-button').show();
enabled = true;
}
if (KZ.Config[layer].timetablesEnabled) {
this.extComponent.getComponent('timetables-button').show();
enabled = true;
}
if(enabled) {
this.extComponent.getComponent('departures-button').show();
this.extComponent.getComponent('departures-arrivals-separator').show();
}
} // showGroupToggle
this.showMetroToggle = function() {
if (!KZ.Config.layerMetroEnabled) {
return;
}
this.showGroupToggle('metro', false);
} // showMetroToggle
this.showBusStationToggle = function() {
this.showGroupToggle('busStation', false);
}
this.showRailToggle = function() {
if (!KZ.Config.layerRailEnabled) {
return;
}
this.extComponent.getComponent('arrivals-button').show();
this.showGroupToggle('rail', true);
}
/*this.showRailToggle = function() {
if (!KZ.Config.layerRailEnabled) {
return;
}
this.extComponent.getComponent('departures-button').show();
this.extComponent.getComponent('arrivals-button').show();
if (KZ.Config.rail.facilitiesEnabled) {
this.extComponent.getComponent('facilities-button').show();
}
if (KZ.Config.rail.taxisEnabled) {
this.extComponent.getComponent('taxis-button').show();
}
if (KZ.Config.rail.timetablesEnabled) {
this.extComponent.getComponent('timetables-button').show();
}
this.extComponent.getComponent('departures-arrivals-separator').show();
} // showRailToggle
*/
this.resetGroupToggle = function() {
// if (!KZ.Config.layerRailEnabled) {
// return;
// }
this.extComponent.getComponent('departures-button').toggle(true);
}
this.showFutureStopBoardDatePicker = function(layer) {
if (KZ.Config.futureTimesEnabled) {
this.extComponent.getComponent('datetime-picker').show();
}
}
this.addInformationDeparturesToggle = function() {
if (KZ.Config.poi.showDepartures === false) {
return;
}
function abortPreviousConnections() {
stopInformationPanel.arrivalsGrid.abortLoadingData();
}
var updateToolbar = function(tab) {
stopInformationPanel.toolbarFeatureManager.updateToolbar('POI', null, stopInformationPanel.getCurrentBusStopRecord(), tab);
}
var toggleGroup = 'unique-poiTab-id-' + Math.random(); // unique toggleGroup for each StopInformationToolbar
var informationButton = new Ext.Button({
xtype: 'button',
text: 'Information',
itemId: 'information-button',
enableToggle: true,
allowDepress: false,
toggleGroup: toggleGroup,
toggleHandler : function(button, state) {
if (state === false) {
return;
}
abortPreviousConnections();
updateToolbar('information');
stopInformationPanel.activatePoiPanel();
}
});
var departuresButton = new Ext.Button({
xtype: 'button',
text: tr('departures'),
itemId: 'poi-departures-button',
enableToggle: true,
allowDepress: false,
toggleGroup: toggleGroup,
pressed: true,
toggleHandler: function(button, state) {
if (state === false) {
return;
}
updateToolbar('departures');
stopInformationPanel.arrivalsGrid.queryType = 'departures';
stopInformationPanel.arrivalsGrid.refreshArrivals(false, false, 'POI');
stopInformationPanel.arrivalsGrid.modeChanged('BUS', 'POI');
stopInformationPanel.activateArrivalsGrid();
}
});
this.extComponent.insert(0, informationButton);
this.extComponent.insert(1, departuresButton);
this.extComponent.insert(2, {
xtype: 'tbseparator',
itemId: 'information-departures-separator'
});
}
this.showInformationDeparturesToggle = function() {
if (KZ.Config.poi.showDepartures === false) {
return;
}
this.extComponent.getComponent('information-button').show();
this.extComponent.getComponent('poi-departures-button').show();
this.extComponent.getComponent('information-departures-separator').show();
}
this.resetInformationDeparturesToggle = function() {
if (KZ.Config.poi.showDepartures === false) {
return;
}
this.extComponent.getComponent('information-button').toggle(true);
}
this.showFilterRoutesButton = function() {
if (!KZ.Config.enableFilterRoutesButton) {
return;
}
this.extComponent.getComponent('filter-toolbar-button').show();
}
this.hideFilterRoutesButton = function() {
this.extComponent.getComponent('filter-toolbar-button').hide();
}
this.showMobileAndSmsButton = function() {
if (disableMobileAndSmsButton) {
return;
}
this.extComponent.getComponent('mobile-and-sms-button').show();
}
this.hideMobileAndSmsButton = function() {
this.extComponent.getComponent('mobile-and-sms-button').hide();
}
this.showRefreshButton = function() {
this.extComponent.getComponent('refresh-button').show();
}
this.hideRefreshButton = function() {
this.extComponent.getComponent('refresh-button').hide();
}
this.updateAfterRefreshButtonSeparator = function() {
var toolbar = this.extComponent;
var filterRoutesButton = toolbar.getComponent('filter-toolbar-button');
var mobileAndSmsButton = toolbar.getComponent('mobile-and-sms-button');
var separator = toolbar.getComponent('after-refresh-button-separator');
if (filterRoutesButton.hidden && mobileAndSmsButton.hidden) {
separator.hide();
} else {
separator.show();
}
}
this.extComponent = new Ext.Toolbar({
cls: 'stop-information-toolbar',
enableOverflow: true,
layoutConfig: {triggerWidth:60},
items: [
new Ext.Button({
itemId: 'datetime-picker',
iconCls: 'date-icon',
menu: new Ext.menu.DateTimeMenu({
handler: function(isNowClicked, date) {
var queryType = stopInformationPanel.arrivalsGrid.queryType;
if(queryType == undefined) {
queryType = 'departures';
}
if(isNowClicked) {
date = undefined;
}
stopInformationPanel.arrivalsGrid.queryType = queryType;
stopInformationPanel.setFutureStopBoardDate(isNowClicked, date);
stopInformationPanel.arrivalsGrid.refreshArrivals(true, undefined, stopInformationPanel.getCurrentBusStopRecord().objectType);
}
})
}),
{
text: 'Notices',
itemId: 'notices-button',
iconCls: 'notices-icon',
scope: this,
handler: function() {
var noticesContent = KZ.NoticesDialog.show(stopInformationPanel);
_gaq.push(['_trackEvent', 'Stop Information Toolbar', 'Notices Button Pressed', stopInformationPanel.getCurrentBusStopRecord().id]);
}
},
{
scope:this,
itemId: 'add-remove-favourite-toolbar-button',
text: 'Add to My Stops',
iconCls: 'add-favourite-icon',
hidden: !showMyStopsButton,
handler: function() {
var stopRecord = stopInformationPanel.getCurrentBusStopRecord();
var stopId = stopRecord.id;
if(KZ.userStopsStore.isFavourite(stopId)) {
var objType = stopInformationPanel.getCurrentBusStopRecord().objectType;
KZ.userStopsStore.deleteFavouriteStop(stopId, objType);
_gaq.push(['_trackEvent', 'Stop Information Toolbar', 'Remove From My Stops Button Pressed', stopId]);
} else {
if(KZ.userStopsStore.isFavouritesMaxLimitReached()) {
var stopInformation = {
panel: stopInformationPanel,
toolbar: this
};
KZ.OverrideFavouriteStopDialog.show(stopInformation);
} else {
KZ.userStopsStore.addFavouriteStop(stopRecord);
}
_gaq.push(['_trackEvent', 'Stop Information Toolbar', 'Add To My Stops Button Pressed', stopId]);
}
this.setFavouriteToolBarIcon();
}
},
{xtype: 'tbseparator', itemId: 'notices-button-seperator'},
{
xtype: (KZ.Config.enableAutoRefresh?'splitbutton':'button'),
text: tr('refresh'),
itemId: 'refresh-button',
iconCls: 'refresh-icon',
scope: this,
handler: function() {
if(ignoreBoardUpdateRequest == false) {
ignoreBoardUpdateRequest = true;
stopInformationPanel.arrivalsGrid.refreshArrivals(true, undefined, stopInformationPanel.getCurrentBusStopRecord().objectType);
this.cancelIgnoreBoardUpdateRequest.defer(10000);
_gaq.push(['_trackEvent', 'Stop Information Toolbar', 'Refresh Button Pressed', stopInformationPanel.getCurrentBusStopRecord().id]);
}
},
menu: this.createRefreshMenu(KZ.Config.enableAutoRefresh)
}, {
xtype: 'tbseparator',
itemId: 'after-refresh-button-separator',
hidden: KZ.Config.enableAutoRefresh
}, {
itemId: 'filter-toolbar-button',
text: tr('filterroutes'),
iconCls: 'filter-icon',
scope:this,
hidden:!KZ.Config.enableFilterRoutesButton,
handler: function() {
KZ.RouteFilterDialog.show(stopInformationPanel);
_gaq.push(['_trackEvent', 'Stop Information Toolbar', 'Filter Button Pressed', stopInformationPanel.getCurrentBusStopRecord().id]);
}
},
// {
//text: 'Plan Journey
',
//iconCls: 'plan-journey-icon',
//scope:this,
//handler: function() {
//var t = new Ext.Template([KZ.Config.JpUrlTemplate]);
//var url = t.apply({stopName: encodeURIComponent(stopInformationPanel.getCurrentBusStopRecord().name)});
//window.open(url, '_blank');
//},
//hidden:!KZ.Config.enableJp
// },
{
text: mobileAndSmsTitle,
itemId: 'mobile-and-sms-button',
iconCls: 'mobile-and-sms',
scope:this,
hidden:disableMobileAndSmsButton,
handler: function() {
var mobTpl = Ext.Template.from('mobileServicesTplHolder');
var mobContent = mobTpl.apply({stopName: stopInformationPanel.getCurrentBusStopRecord().name, smsCode: stopInformationPanel.getCurrentBusStopRecord().smsCode});
// Tried to use the tpl configuration, but failed. Now manually load the content tpl.
var window = new Ext.Window({
title: mobileAndSmsTitle,
iconCls: 'mobile-and-sms',
layout:'fit',
modal:true,
width: 400,
padding:5,
draggable:false,
resizable:false,
closable:true,
closeAction:'hide',
plain: true,
html: mobContent,
data: {stopName: 'INITIAL', smsCode: 'INITIAL111'},
listeners: {
show: function() {
Ext.select('.ext-el-mask').addListener('click', function() {
window.hide();
});
}
},
buttons: [{
text:'OK',
scope: this,
handler: function(){
window.hide();
}
}]
});
window.show();
_gaq.push(['_trackEvent', 'Stop Information Toolbar', 'Mobile and SMS Button Pressed', stopInformationPanel.getCurrentBusStopRecord().id]);
}
},
{xtype: 'tbseparator', itemId: 'download-button-seperator'},
{
itemId: 'download-button',
text: 'Timetable',
scope:this,
iconCls: 'pdf-icon',
handler : function(button) {
var stopRecord = stopInformationPanel.getCurrentBusStopRecord();
var stopId = stopRecord.id;
_gaq.push(['_trackEvent', 'Stop Information Toolbar', 'Download Departures Button Pressed', stopId]);
window.open(this.urlLink , '_blank');
}
}
]
});
}
Ext.ns('KZ');
// This panel is used on both the main map page and the My Stops page
KZ.StopInformationPanel = function(showStaticMap, showMyStopsButton, enablePartialCollapse, tools, draggable, initiallyHidden, poiPanel, stopSelectedEventHandler) {
var currentBusStopRecord;
var currentBusStopServiceDisruptions;
var futureStopBoard = undefined;
var hasFutureStopBoard = false;
var staticMapPanel;
var connection = new Ext.data.Connection({
timeout: 6000,
autoAbort: true
});
this.getFutureStopBoardDate = function() {
if(hasFutureStopBoard) {
return futureStopBoard;
} else {
return undefined;
}
};
this.setFutureStopBoardDate = function(isNow, date) {
hasFutureStopBoard = ! isNow;
futureStopBoard = date;
this.setTitle();
};
this.isStopInformationPanelVisible = function() {
return (currentBusStopRecord != undefined && this.extComponent.isVisible());
}
this.getCurrentBusStopRecord = function() {
return currentBusStopRecord;
}
this.getCurrentBusStopServiceDisruptions = function() {
return currentBusStopServiceDisruptions;
}
this.setCurrentBusStopServiceDisruptions = function(disruptions) {
currentBusStopServiceDisruptions = disruptions;
}
this.toolbar = new KZ.StopInformationToolbar(this, showMyStopsButton);
this.toolbarFeatureManager = new KZ.ToolbarFeatureManager(this.toolbar);
this.poiPanel = poiPanel; // this might be undefined on the My Stops page
this.arrivalsGrid = new KZ.ArrivalsGrid(this, connection, stopSelectedEventHandler);
// this panel holds main stop information panel content, like arrivals grid, poi panel, etc.
var extComponentHolder = new Ext.Panel({
flex: 1,
layout: 'card',
activeItem: 0,
plain: true,
border: false,
items: [this.arrivalsGrid.extComponent]
});
if (this.poiPanel !== undefined) {
extComponentHolder.add(this.poiPanel.extComponent);
}
if (KZ.Config.rail.taxisEnabled) {
this.taxisPanel = new KZ.TaxisPanel(connection);
extComponentHolder.add(this.taxisPanel.getExtComponent());
}
if (KZ.Config.rail.timetablesEnabled) {
this.timetablesPanel = new KZ.TimetablesPanel(connection);
extComponentHolder.add(this.timetablesPanel.getExtComponent());
}
if (KZ.Config.facilitiesEnabled) {
this.facilitiesPanel = new KZ.StationFacilitiesPanel(connection);
extComponentHolder.add(this.facilitiesPanel.getExtComponent());
}
var stopInformationPanelItems = [extComponentHolder];
if (KZ.Config.enableTransportDirectJp) {
this.toolbar.addTransportDirectJp();
}
if (KZ.Config.enableWalkit) {
this.toolbar.addWalkit();
}
if (KZ.Config.enableInterchangeMaps) {
this.toolbar.initInterchangeMapButton();
}
if (KZ.Config.travelineCymruJp.enabled) {
this.toolbar.addTravelineCymruJp();
}
if (KZ.Config.nexusJp.enabled) {
this.toolbar.addNexusJp();
}
this.toolbar.addGroupToggle();
this.toolbar.addInformationDeparturesToggle();
this.activateArrivalsGrid = function() {
extComponentHolder.getLayout().setActiveItem('arrivals-grid');
// setActiveItem shows given item, but we don't want it visible when the load mask is shown
this.arrivalsGrid.extComponent.hide();
}
this.activatePoiPanel = function() {
this.extComponent.setTitle(currentBusStopRecord.name, 'poi-details-icon');
extComponentHolder.getLayout().setActiveItem('poi-panel');
}
this.activateTaxisPanel = function() {
extComponentHolder.getLayout().setActiveItem('taxis-panel');
this.taxisPanel.loadData(currentBusStopRecord.lat, currentBusStopRecord.lng);
}
this.activateTimetablesPanel = function() {
extComponentHolder.getLayout().setActiveItem('timetables-panel');
this.timetablesPanel.loadData(currentBusStopRecord.id);
}
this.activateStationFacilitiesPanel = function() {
extComponentHolder.getLayout().setActiveItem('facilities-panel');
this.facilitiesPanel.loadData(currentBusStopRecord.id);
}
this.showPoi = function(poi) {
if(poiPanel != undefined) {
if(currentBusStopRecord != undefined && currentBusStopRecord.id != poi.id) {
this.toolbar.taskMgr.stopAll();
this.cancelAutoRefreshTask();
}
currentBusStopRecord = poi;
this.setFutureStopBoardDate(true);
this.toolbar.resetInformationDeparturesToggle();
this.toolbarFeatureManager.updateToolbar('POI', null, poi, 'information');
this.extComponent.show();
this.poiPanel.setPoi(poi);
this.activatePoiPanel();
this.setPageTitle(poi.name);
if(this.extComponent.ownerCt != undefined) {
this.extComponent.ownerCt.doLayout();
}
var newSearchPointEventFunction = function() {
this.extComponent.fireEvent("newSearchPointEvent", poi.lat, poi.lng);
}
newSearchPointEventFunction.defer(310, this); // This should be in step with the maps check resize function, which is a deferred function call.
if (KZ.stopInformationPanel.arrivalsGrid.refreshingArrivalsMask != undefined) {
KZ.stopInformationPanel.arrivalsGrid.refreshingArrivalsMask.hide();
}
}
}
this.cancelAutoRefreshTask = function(failure) {
if(this.toolbar.autoRefreshTask.running) {
this.toolbar.extComponent.getComponent('refresh-button').enable().setText("Refresh");
this.toolbar.extComponent.getComponent('refresh-button').menu.items.first().setText(tr('autorefresh'));
this.toolbar.taskMgr.stopAll();
if (failure) {
KZ.AutoRefreshArrivalsDialog.hide();
KZ.GeneralNotificationDialog.show({
title: 'Auto refresh failure',
message: 'Auto refresh has encountered a problem and has been disabled.'
});
}
}
}
this.disableAutoRefreshTask = function(task) {
Ext.TaskMgr.stop(task);
task.running = false;
this.toolbar.extComponent.getComponent('refresh-button').enable().setText("Refresh");
this.toolbar.extComponent.getComponent('refresh-button').menu.items.first().setText(tr('autorefresh'));
}
if(showStaticMap === true) {
var staticMapImgString = '';
staticMapPanel = new Ext.Panel({
cls: 'my-stops-map-panel',
width:200,
plain:true,
border: false,
html: staticMapImgString
});
stopInformationPanelItems.unshift(staticMapPanel);
}
this.extComponent = new Ext.Panel({
cls : 'x-portlet stop-information-panel',
region:'south',
iconCls:'arrival-board-icon',
frame:false,
tools: tools,
draggable: draggable,
split:true,
plain:true,
tbar: this.toolbar.extComponent,
collapsible:true,
hidden:initiallyHidden,
layout: 'hbox',
layoutConfig: {
align : 'stretch',
pack: 'start'
},
title: 'Arrivals',
listeners: {
scope:this,
'beforecollapse': function() {
document.title = document.title.split(" - ")[0];
if(enablePartialCollapse) {
return true;
} else {
this.extComponent.hide();
this.cancelAutoRefreshTask();
currentBusStopRecord = undefined;
if(this.extComponent.ownerCt != undefined) {
this.extComponent.ownerCt.doLayout();
}
this.extComponent.fireEvent("stopBoardClosed");
return false;
}
},
'bodyresize' : function() {
try {
KZ.mapPanel.getMap().fireResizeEvent();
} catch(e) {};
},
'hide' : function() {
try {
KZ.mapPanel.getMap().fireResizeEvent();
} catch(e) {};
}
},
items: stopInformationPanelItems
});
this.extComponent.on({
'closeStopBoard' : {
scope: this,
fn: function() {
this.extComponent.collapse();
}
}
});
// Stop board is optional
this.show = function(busStopRecord, stopBoard) {
// FIXME migration workaround, delete when finish it
if(busStopRecord instanceof Ext.data.Record) {
busStopRecord = busStopRecord.data;
}
if(showStaticMap === true) {
staticMapPanel.update('
');
}
if(currentBusStopRecord != undefined && currentBusStopRecord.id != busStopRecord.id) {
this.toolbar.taskMgr.stopAll();
this.cancelAutoRefreshTask();
}
currentBusStopRecord = busStopRecord;
this.setFutureStopBoardDate(true);
if (busStopRecord.mode === 'TRAIN' || busStopRecord.mode === 'TRAM' || (busStopRecord.mode === 'BUS' && busStopRecord.objectType === 'GROUP')) {
this.toolbar.resetGroupToggle();
}
this.toolbarFeatureManager.updateToolbar(busStopRecord.objectType, busStopRecord.mode, busStopRecord, 'departures');
this.activateArrivalsGrid();
this.arrivalsGrid.store.removeAll();
this.arrivalsGrid.modeChanged(busStopRecord.mode, busStopRecord.objectType);
this.setTitle();
this.extComponent.show();
if(this.extComponent.ownerCt != undefined) {
this.extComponent.ownerCt.doLayout();
}
var newSearchPointEventFunction = function() {
this.extComponent.fireEvent("newSearchPointEvent", busStopRecord.lat, busStopRecord.lng);
}
newSearchPointEventFunction.defer(310, this);// This should be in step with the maps check resize function, which is a deferred function call.
this.arrivalsGrid.queryType = 'departures';
if(stopBoard != undefined){
this.arrivalsGrid.presentStopBoardResults(undefined, stopBoard);
} else { //requesting the server to get the stopBoard
this.arrivalsGrid.refreshArrivals(undefined, undefined, busStopRecord.objectType);
}
}
this.setDetailedTitle = function(lastUpdated, smsCode, additionalName) {
// FIXME migration workaround, delete when finish it
if(currentBusStopRecord instanceof Ext.data.Record) {
currentBusStopRecord = currentBusStopRecord.data;
}
var stopIndicator = currentBusStopRecord.stopIndicator;
var nameAndFlagLabel = '' + currentBusStopRecord.name + (stopIndicator ? ' ('+ stopIndicator +')' : '');
if(additionalName != undefined){
nameAndFlagLabel += ' [' + additionalName + ']';
}
nameAndFlagLabel += '';
// For bookmarking purposes
if(Ext.get('myStopsPanel') == undefined) {
var htmlPageTitle = currentBusStopRecord.name + (stopIndicator != undefined ? ' ('+ stopIndicator +')' : '') + (smsCode != undefined ? ' :: ' + smsCode : '');
this.setPageTitle(htmlPageTitle);
}
var futureTime = '';
var stopCodeLabel = smsCode ? ' {0} {1}'.bind(tr('stopcode'), smsCode) : '';
var lastUpdatedLabel = ' @ ' + lastUpdated + '';
if(hasFutureStopBoard) {
futureTime = ' | ' + futureStopBoard.format('l, d-m-Y, H:i') + '';
}
if(lastUpdated) {
this.extComponent.setTitle(nameAndFlagLabel + lastUpdatedLabel + futureTime + stopCodeLabel, 'arrival-board-icon');
} else {
this.extComponent.setTitle(nameAndFlagLabel + futureTime + stopCodeLabel, 'arrival-board-icon');
}
}
this.setTitle = function(lastUpdated) {
// FIXME migration workaround, delete when finish it
if(currentBusStopRecord instanceof Ext.data.Record) {
currentBusStopRecord = currentBusStopRecord.data;
}
this.setDetailedTitle(lastUpdated, currentBusStopRecord.smsCode, undefined);
}
this.setPageTitle = function(title) {
document.title = document.title.split(' - ')[0] + ' - ' + title;
}
};
Ext.ns('KZ');
KZ.PoiInformationPanel = function(){
var left = new Ext.Panel({
width: 400,
baseCls: 'x-plain',
bodyBorder: false,
border: false,
layout: 'table',
layoutConfig: {
columns: 2
},
defaults: {
bodyBorder: false,
bodyStyle: 'padding:5px',
border: false,
cellCls: 'vertical-align-top'
}
});
var right = new Ext.Panel({
columnWidth: 1,
layout: 'anchor',
anchor: '100%',
border: false
});
this.extComponent = new Ext.Panel({
id: 'poi-panel',
layout: 'column',
border: false,
items: [left, right]
});
this.setPoi = function(data) {
this.poi = data;
right.removeAll();
if (this.poi.additionalData['image']) {
right.add({html:'
',border:false,cls:'poi-image-container'});
}
left.removeAll();
if(this.poi.additionalData['description']){
left.add({html:''+this.poi.additionalData['description']+'
',colspan:2});
}
for(var field in this.poi.additionalData){
if(field === 'image' || field === 'description'){
continue;
}
var value = this.poi.additionalData[field];
if(value.indexOf('http://') === 0 ){
addRow(field, ''+value+'');
}else if(value.indexOf('mailto:') === 0 && value.length > 7 ){
addRow(field, ''+value.substr(7)+'');
}else{
addRow(field, value);
}
}
}
function addRow(label, value) {
left.add({html:''+label+':
'});
left.add({html:''+value+'
'});
}
function getHttpUrl(url) {
if (url.match("^http://")) {
return url;
} else {
return "http://" + url;
}
}
}
Ext.ns('KZ');
KZ.ResultHashMap = function() {
var dictionary = new KZ.Dictionary();
this.add = function(obj) {
return dictionary.add(obj.name, obj);
}
this.exists = function(name) {
return dictionary.exists(name);
}
this.getValues = function() {
return dictionary.values();
}
this.getCount = function() {
return dictionary.count;
}
this.get = function(name){
return dictionary.getVal(name);
}
this.remove = function(names) {
if(names instanceof Array) {
for(var i = 0; i < names.length; ++i) {
var name = names[i];
if(this.exists(name)) {
dictionary.remove(name);
}
}
} else {
dictionary.remove(names);
}
}
this.removeAll = function(){
dictionary.removeAll();
}
}
Ext.ns('KZ');
KZ.SearchResultsPanel = function() {
var nonEmptyResult = false;
var resultStatus = "";
var defaultMessage = tr('searchhint');
var resultStatusPanel = createResultStatusPanel();
var messagePanel = createMessagePanel();
var singleResultPanel = createSingleResultPanel();
var multiResultPanel = createMultiResultPanel();
KZ.routeSearchResultsContainer = new KZ.RouteSearchResultsContainer();
var outerResultsPanel = createOuterResultsPanel();
this.extComponent = createSearchResultsPanel();
// *******************************************************************
this.getMessagePanel = function() {
return messagePanel;
}
this.getSingleResultPanel = function() {
return singleResultPanel;
}
this.getRouteResultPanel = function() {
return KZ.routeSearchResultsContainer.getContainer();
}
// *******************************************************************
this.resetSearchResultsPanel = function() {
this.showMessage( defaultMessage );
}
this.hasResults = function() {
return nonEmptyResult;
}
function highlightMarkerFromRecord(record) {
KZ.markerManager.setMarkerState(record, "hover-state");
}
// *******************************************************************
this.setResultStatus = function(newStatus) {
resultStatus = newStatus;
}
this.showResultsStatusPanel = function() {
resultStatusPanel.update( resultStatus );
resultStatusPanel.show();
this.extComponent.doLayout(false, true);
}
this.hideResultsStatusPanel = function() {
resultStatusPanel.hide();
this.extComponent.doLayout(false, true);
}
// *******************************************************************
this.setMessage = function( message ) {
messagePanel.removeClass('error-message').update(message);
}
this.showMessage = function( message ) {
messagePanel.removeClass('error-message').update(message);
this.showMessagePanel(true);
}
this.showErrorMessage = function( message ) {
messagePanel.addClass('error-message').update(message);
this.showMessagePanel(true);
}
///
this.showMessagePanel = function( hasNoResults ) {
showPanel.call(this, 'result-message-panel');
nonEmptyResult = ! hasNoResults; // hasNoResults param is not mandatory
this.hideResultsStatusPanel();
}
this.showSingleResultPanel = function() {
showPanel.call(this, 'single-result-panel');
nonEmptyResult = true;
resultStatusPanel.update( resultStatus );
this.showResultsStatusPanel();
}
this.showMultiResultPanel = function() {
showPanel.call(this, 'multi-result-panel');
nonEmptyResult = true;
this.showResultsStatusPanel();
}
// temorary separate method TODO move to Route search result type and put to single-result-panel
this.showRouteResult = function() {
showPanel('route-search-container');
KZ.routeSearchResultsContainer.extComponent.show();
nonEmptyResult = true;
this.showResultsStatusPanel();
}
var showPanel = function(panel) {
outerResultsPanel.getLayout().setActiveItem( panel );
}
// *******************************************************************
// result inner panels
/* private */ function createMessagePanel() {
var panel = new Ext.Panel({
id: 'result-message-panel',
autoScroll: true,
border: false,
padding: '5',
headerCssClass: 'hidden-header',
html: defaultMessage
});
return panel;
}
/* private */ function createResultStatusPanel() {
var panel = new Ext.Panel({
id: 'result-status-panel',
region: 'north',
autoScroll: true,
autoHeight:true,
border: false,
padding: '5',
hidden: true,
headerCssClass: 'hidden-header',
html: ''
});
return panel;
}
/* private */ function createSingleResultPanel() {
var panel = new Ext.Panel({
id: 'single-result-panel',
autoScroll: true,
border: false,
padding: '5',
headerCssClass: 'hidden-header',
html: ' '
});
return panel;
}
/* private */ function createMultiResultPanel() {
var accordion = new Ext.Panel({
id: 'multi-result-panel',
layout: 'accordion',
border:false,
bodyBorder:false,
autoDestroy:false,
layoutConfig: {
animate: false,
hideCollapseTool: true,
titleCollapse: true
},
autoScroll: true,
headerCssClass: 'hidden-header'
});
return accordion;
}
/* private */ function createSearchResultsPanel() {
var extPanel = new Ext.Panel({
id:'search-results-panel',
layout:'border',
region: 'center',
autoDestroy:false,
border: false,
layoutConfig: {
align : 'stretch',
pack: 'start'
},
items: [ resultStatusPanel,
outerResultsPanel ]
});
return extPanel;
}
// outer panel
/* private */ function createOuterResultsPanel() {
var outerPanel = new Ext.Panel({
id:'outer-results-panel',
layout:'accordion',
region: 'center',
autoDestroy:false,
border: false,
layoutConfig: {
align : 'stretch',
pack: 'start'
},
items:
[ messagePanel,
singleResultPanel,
multiResultPanel,
KZ.routeSearchResultsContainer.extComponent ]
});
return outerPanel;
}
this.addMultiResultItems = function( items ) {
multiResultPanel.add( items );
outerResultsPanel.doLayout(false, true);
}
}
Ext.ns('KZ');
KZ.SearchResultsManager = function( panel ) {
var resultHandlers = new KZ.ResultHashMap();
var searchResultsPanel = panel;
var currentSearchResults = undefined;
this.getCurrentSearchResults = function() {
return currentSearchResults;
}
this.resetCurrentSearchResults = function() {
currentSearchResults = undefined;
}
/* loading result handlers */
this.addResultHandlers = function(resultHandlers) {
if(resultHandlers instanceof Array) {
Ext.each(resultHandlers, function(handler) {
addResultHandler(handler);
}, this);
} else {
this.addResultHandler(handler);
}
}
// private
var addResultHandler = function(handler) {
if(handler instanceof KZ.AbstractResult) {
if( ! resultHandlers.add(handler) ) {
KZ.error("Result handler '"+ handler.name + !"' exists!");
}
} else {
KZ.error("Could not load '" + handler.name + "' handler, it's not a correct result handler!");
}
};
/**
* Prepares search results panel.
*/
this.initialiseSearchResultPanel = function() {
var multiResultItems = [];
Ext.each(resultHandlers.getValues(), function( handler ) {
if( handler.validate() ) {
handler.initialise( searchResultsPanel );
if( handler.isMultiResultType( ) ) {
var container = handler.getContainer();
container.hide();
multiResultItems.push( container );
}
}
}, this);
searchResultsPanel.addMultiResultItems( multiResultItems );
}
///////////////////////////////////////
this.handleSearchResults = function( results ) {
currentSearchResults = results;
KZ.layerManager.cleanSearchResults();
hideMultiResultPanelItems();
var handlers = resultHandlers.getValues();
var count = resultHandlers.getCount();
var first = true;
var viewToRefresh;
var hasHandled = false;
// move map to bounding box if bb defined and infoPanel wont be shown
var json = results.results;
if(json && json.swLatBB) {
KZ.mapPanel.extComponent.fireEvent("newSearchBoundingBoxEvent", json.swLatBB, json.swLngBB, json.neLatBB, json.neLngBB);
}
for(var i = 0; i < count; ++i) {
var handler = handlers[i];
if(handler.matchResult( results )) {
hasHandled = true;
var resultCount = handler.processResult( results );
searchResultsPanel.setResultStatus( handler.getResultStatus() );
handler.getContainer().show();
if(! handler.isMultiResultType()) {
handler.displayResults();
break;
} else if(first) {
if(handler.getContainer().collapsed) {
handler.getContainer().expand(false);
} else {
handler.getContainer().fireEvent('expand', handler.getContainer());
}
handler.displayResults();
first = false;
viewToRefresh = handler.getContainer().getView();
}
handler.renderLabel(true, resultCount);
}
}
if(! hasHandled) {
var handler = KZ.emptyResult;
handler.processResult( results );
searchResultsPanel.setResultStatus( handler.getResultStatus() );
handler.getContainer().show();
handler.displayResults();
}
searchResultsPanel.extComponent.show();
if(viewToRefresh) {
// workaround to show content of currently expanded component of the accordion.
viewToRefresh.refresh();
}
searchResultsPanel.extComponent.doLayout(false, true);
KZ.viewport.extComponent.getEl().dom.scrollTop = 0;
}
this.handleSearchAndHideResults = function( results ) {
this.handleSearchResults(results);
searchResultsPanel.resetSearchResultsPanel();
}
var hideMultiResultPanelItems = function() {
var handlers = resultHandlers.getValues();
for(var i = 0; i < handlers.length; ++i) {
var handler = handlers[i];
if(handler.isMultiResultType()) {
handler.getContainer().hide();
}
}
}
}
Ext.ns('KZ');
KZ.AbstractResult = function() {
var VALID_RESULT_PATTERN = /message|single-result|multi-result/;
var MESSAGE_PATTERN = /message/;
var SINGLE_RESULT_PATTERN = /single-result/;
var MULTI_RESULT_PATTERN = /multi-result/;
/* FIELDS */
this.name = 'undefined';
/**
* Label will be used as accordion label (only when combinedResult is set to true).
*/
this.label = '';
this.layers = [];
this.singleResultLayers = []; // supported layers for single result
/* final */ this.layerHandlers = {};
/* final */ this.resultType = 'single-result'; // message, single-result, multi-result
this.container; // results container
var showResultsCallback;
this.performRowClick; // not mandatory
this.resultStatusTpl = new Ext.Template('{0} "{searchTerm}".'.bind(tr('resultsfor')));
this.getResultStatusModel;
/**
* Creates result container.
*/
this.initialise = function(searchResultsPanel) {
if(KZ.util.isFunction( this.specInitialise )) {
this.specInitialise();
}
showResultsCallback = {
func: getShowResultsCallback.call(this, searchResultsPanel),
scope: searchResultsPanel
};
// set container
if(this.isSingleResultType()) {
this.container = searchResultsPanel.getSingleResultPanel();
} else if(this.isMessageResultType()) {
this.container = searchResultsPanel.getMessagePanel();
}
// get reference of relevant layer handlers instead of getting it every result
var layerSet = this.layers;
for( var i=0; i < layerSet.length; ++i ) {
var layerName = layerSet[i];
this.layerHandlers[layerName] = KZ.layerManager.getLayer( layerName );
}
}
this.validate = function() {
var valid = true;
if(this.name == 'undefined') {
KZ.error('Name of result handler is undefined, check handlers.');
valid = false;
}
if(! this.isValidResultType()) {
KZ.error("ResultType property of '" + handler.name + "' result handler is invalid!");
valid = false;
}
if(this.isMultiResultType() && this.label.length == 0) {
KZ.error('Label not test for "' + this.name + '" result handler.');
valid = false;
}
if(! this.isMessageResultType() & this.layers.length == 0) {
KZ.error('Result handler "' + this.name + '" doesn\'t correspond to any layer, but should be at least one.');
valid = false;
}
return valid;
}
/**
* this flag defines whether specific search result may be combined with other
* result types. In other words it gives possibility to show few kinds of
* result types on the GUI (in the accordion).
*/
this.combinedResult = false;
/* METHODS */
/**
* method checks if search result contains relevent data, if so, returns true, otherise false.
* @param searchResult JSON search result
* @return boolean value
*/
this.matchResult = function( json ) {
return json && this.hasResults(json);
};
/**
* Provides processing of the result.
* @param searchResult JSON search result
*/
/* abstract */ this.processResult;
this.displayResults = function() {
showResultsCallback.func.call( showResultsCallback.scope );
}
var getShowResultsCallback = function(panel) {
if(this.isMultiResultType()) {
return panel.showMultiResultPanel;
} else if(this.isSingleResultType()) {
return panel.showSingleResultPanel;
} else {
return panel.showMessagePanel;
}
}
// template is taken form layer
/**
* returns container of results.
*/
this.getContainer = function() {
return this.container;
}
// result type matchers
this.isMessageResultType = function() {
return MESSAGE_PATTERN.test(this.resultType);
}
this.isSingleResultType = function() {
return SINGLE_RESULT_PATTERN.test(this.resultType);
}
this.isMultiResultType = function() {
return MULTI_RESULT_PATTERN.test(this.resultType);
}
this.isValidResultType = function() {
return VALID_RESULT_PATTERN.test(this.resultType);
}
this.hasResults = function(json, layerSet) {
return hasSpecificResults(json.searchResults, this.layers) ||
hasSpecificResults(json.singleSearchResult, this.singleResultLayers);
return false;
}
var hasSpecificResults = function(results, layerSet) {
if( ! results ) {
return false;
}
for(var i = 0; i < layerSet.length; ++i) {
var layerName = layerSet[i];
var layerResults = results[layerName];
if( layerResults && layerResults.length > 0 ) {
return true;
}
}
return false;
}
this.loadSearchResults = function(results, store) {
var resultCount = 0;
store.removeAll();
if(results != undefined) {
var layerSet = this.layers;
for(var i = 0; i < layerSet.length; ++i) {
var layerName = layerSet[i];
var layerResults = results[layerName];
if( ! layerResults ) {
continue;
}
// if single result make it array
if( !(layerResults instanceof Array) && KZ.util.inArray(this.singleResultLayers, layerName)) {
layerResults = [ layerResults ];
}
if(layerResults.length > 0) {
resultCount += layerResults.length;
var layer = this.layerHandlers[layerName];
// map
layer.loadSearchResultMarkers(layerResults);
// left hand panel
if(store instanceof Ext.data.Store) {
var records = [];
for(var j = 0; j < layerResults.length; ++j) {
layerResults[j];
var record = new store.recordType( layerResults[j] );
record['twotmLayer'] = layer;
records.push( record );
}
store.add( records );
} // if store
}
} // loop over layerSet
}
return resultCount;
} // loadSearchResults
this.renderLabel = function(displayResultCount, count) {
var title = this.label;
if( displayResultCount ) {
var tpl = new Ext.Template(this.label, ' ({count})');
title = tpl.apply({ 'count' : count });
}
this.getContainer().setTitle(title);
}
this.singleResultClick = function( record, layer ) {
KZ.markerManager.markerClick(record, layer, this.performRowClick);
}
this.singleResultEnter = function( record ) {
KZ.markerManager.setMarkerState(record, "hover-state");
}
this.singleResultLeave = function() {
KZ.markerManager.restoreHoverStateMarker();
}
this.getResultStatus = function() {
var model = {};
if(KZ.util.isFunction( this.getResultStatusModel )) {
model = this.getResultStatusModel();
}
model.searchTerm = KZ.searchBoxPanel.currentSearchTerm;
return this.resultStatusTpl.apply( model );
}
}
Ext.ns('KZ');
KZ.AbstractMultiResult = function() {
this.resultType = 'multi-result';
// may be overriden in child class
this.emptyText = 'There were no matches';
// may be overriden in child class
this.labelIconClass = 'map-marker-sprite normal-state search-result-marker-type-normal-state-x';
// may be overriden in child class
this.defaultTemplate = new Ext.XTemplate(
'',
'',
'{name}',
'
', {
compiled:true
});
this.resultsStore = new Ext.data.JsonStore({
autoDestroy : false,
fields : [ 'id' ]
});
this.resultsPanelListeners = {
scope : this,
cellmouseenter : function(grid, rowIndex, htmlElement, event) {
var record = grid.getStore().getAt(rowIndex);
this.singleResultEnter(record.data);
},
cellmouseleave : function(grid, rowIndex, htmlElement, event) {
this.singleResultLeave();
},
rowclick : function(grid, rowIndex, e) {
var record = grid.getStore().getAt(rowIndex);
this.singleResultClick(record.data, record.twotmLayer);
}
};
this.specInitialise = function() {
this.container = new Ext.grid.GridPanel({
id : this.name + '-panel',
title : this.label,
layout : 'fit',
iconCls : this.labelIconClass,
autoScroll : true,
forceLayout : true,
border : false,
store : this.resultsStore,
stripeRows : true,
hideHeaders : true,
disableSelection: true,
columns: [{
id : 'id',
dataIndex : 'id',
menuDisabled: true,
renderer : {
fn : this.rowRenderer,
scope : this
},
css : "white-space: normal !important;"
}],
plugins : [Ext.ux.grid.GridMouseEvents],
viewConfig : {
forceFit : true,
scrollOffset: 2, //hides scrollbars
emptyText : this.emptyText
},
listeners : {
scope : this,
cellmouseenter : function(grid, rowIndex, htmlElement, event) {
var record = grid.getStore().getAt(rowIndex);
this.singleResultEnter(record.data);
},
cellmouseleave : function(grid, rowIndex, htmlElement, event) {
this.singleResultLeave();
},
rowclick : function(grid, rowIndex, e) {
var record = grid.getStore().getAt(rowIndex);
this.singleResultClick(record.data, record.twotmLayer);
}
}
});
}
// may be overriden in child class
this.rowRenderer = function(value, metaData, record, rowIndex, colIndex, store) {
return this.defaultTemplate.apply({
'name' : record.get('name')
});
}
this.loadMultipleSearchResults = function(json) {
var count = this.loadSearchResults(json.searchResults, this.resultsStore);
if( count == 0 ) {
this.loadSearchResults(json.singleSearchResult, this.resultsStore);
count = 1;
}
return count;
}
}
KZ.AbstractMultiResult.prototype = new KZ.AbstractResult;
Ext.ns('KZ');
KZ.EmptyResult = function() {
this.name = 'empty';
this.resultType = 'message';
this.matchResult = function(json) {
if( // has no any results
( !json && !KZ.util.isSet(json.singleSearchResult) && !KZ.util.isSet(json.results) && !KZ.util.isSet(json.searchResults) )
// or has suggestion
|| (KZ.util.isSet(json.results) && KZ.util.isSet(json.results.suggestion)) ) {
return true;
} else {
return false;
}
}
this.processResult = function(json) {
var resultStatus = "{0} {1}.".bind(tr('nomatchesfor'), KZ.searchBoxPanel.currentSearchTerm);
// suggestin
if (KZ.util.isSet(json.results) && KZ.util.isSet(json.results.suggestion)) {
resultStatus += "Did you mean .. \"" + jsonResults.results.suggestion + "\"?";
_gaq.push(['_trackEvent', 'Search Classification', 'Did You Mean', KZ.searchBoxPanel.currentSearchTerm + ' -> ' + jsonResults.results.suggestion]);
} else {
_gaq.push(['_trackEvent', 'Search Classification', 'No Search Matches', KZ.searchBoxPanel.currentSearchTerm]);
}
this.getContainer().update(resultStatus);
}
}
KZ.EmptyResult.prototype = new KZ.AbstractResult;
Ext.ns('KZ');
KZ.StopsResult = function() {
this.name = 'stop-results';
this.label = tr('busstops');
this.layers = [ 'bus' ];
this.singleResultLayers = [ 'bus' ];
this.emptyText = 'There were no bus stop matches';
this.resultStatusTpl = new Ext.Template('{0} "{searchTerm}".'.bind(tr('matchesfor')));
this.labelIconClass = 'map-marker-sprite normal-state search-result-marker-type-normal-state-x search-panel-stop-icon';
this.parent = this;
this.defaultTemplate = new Ext.XTemplate(
'',
'',
'
{name}',
'', ' ({stopIndicator})',
'', '
', '
',
'towards {towards}
', '',
'
',
'',
'{name} ',
// '{[xindex === xcount ? "" : ", "]}',
'', '
', '
', {
compiled : true,
isDefined : function(stringValue) {
return stringValue != undefined && stringValue != '';
}
});
this.processResult = function(json) {
// new search results
var count = this.loadMultipleSearchResults(json);
if(this.resultsStore.getCount() == 1) {
var stop = this.resultsStore.getAt(0).data;
var recentStopRecord = new KZ.userStopsStore.recentStopsStore.recordType({id:stop.id, smsCode:stop.smsCode, name:stop.name, stopIndicator:stop.stopIndicator, towards:stop.towards, direction:stop.direction, lat:stop.lat, lng:stop.lng, routes:stop.routes, objectType: stop.objectTyps}, stop.id);
KZ.viewport.extComponent.fireEvent("stopSelectedEvent", recentStopRecord);
}
_gaq.push(['_trackEvent', 'Search Classification', 'Stop Matche(s) Only', KZ.searchBoxPanel.currentSearchTerm]);
return count;
}
this.rowRenderer = function(value, metaData, record, rowIndex, colIndex, store) {
return this.defaultTemplate.apply({
'name' : record.get('name'),
'stopIndicator' : record.get('stopIndicator'),
'towards' : record.get('towards'),
'routes' : record.get('routes')
});
}
}
KZ.StopsResult.prototype = new KZ.AbstractMultiResult;
Ext.ns('KZ');
KZ.SingleStopResult = function() {
this.name = 'single-stop-result';
this.layers = ['bus']; // ugly
var layerHandler;
var lastStopName;
this.specInitialise = function() {
layerHandler = KZ.layerManager.getLayer('bus');
}
this.resultStatusTpl = new Ext.Template('Found {stopName} ({searchTerm})');
this.getResultStatusModel = function() {
return {
stopName: lastStopName
};
}
var template = new Ext.XTemplate(
'',
'',
'
{name}',
'', ' ({stopIndicator})',
'', '
', '
',
'towards {towards}
', '',
'
',
'',
'{name} ',
// '{[xindex === xcount ? "" : ", "]}',
'', '
', '
', {
compiled : true,
isDefined : function(stringValue) {
return stringValue != undefined && stringValue != '';
}
});
this.matchResult = function( json ) {
var result = json.singleSearchResult;
return result && result.bus;
}
this.processResult = function(json) {
var stop = json.singleSearchResult.bus;
lastStopName = stop.name;
var searchResultMarkers = [ stop ];
layerHandler.loadSearchResultMarkers(searchResultMarkers);
var stop = searchResultMarkers[0]; // get with DOM
var recentStopRecord = new KZ.userStopsStore.recentStopsStore.recordType(stop, stop.id);
var filterOutArray = stop.temporaryFilterOut;
var temporaryFilterEnabled = false;
if(filterOutArray != undefined && filterOutArray.length > 0) {
KZ.userStopsStore.updateFilters(stop.id, filterOutArray);
KZ.userStopsStore.setTemporaryFilterEnabled(stop.id, true);
} else {
KZ.userStopsStore.updateFilters(stop.id, []);
KZ.userStopsStore.setTemporaryFilterEnabled(stop.id, false);
}
var renderedStop = template.apply( stop );
this.getContainer().update( renderedStop );
Ext.get("single-stop-" + stop.id).on('click', function(e) {
this.singleResultClick( stop, layerHandler );
}, this);
KZ.viewport.extComponent.fireEvent("stopSelectedEvent", recentStopRecord); // !
_gaq.push(['_trackEvent', 'Search Classification', 'Stop Code Match', KZ.searchBoxPanel.currentSearchTerm]);
}
}
KZ.SingleStopResult.prototype = new KZ.AbstractResult;
Ext.ns('KZ');
KZ.AmbiguousResult = function() {
this.name = 'ambiguous';
this.layers = ['']; // temp
this.resultStatusTpl = new Ext.Template('Did you mean..?
');
var t = new Ext.Template([
'',
]);
//t.compile();
this.matchResult = function( json ) {
var results = json.results;
return results && results.routeResults && results.stopResults;
}
this.processResult = function(json) {
var ambiguousResult = "";
ambiguousResult += t.apply({id: 'postcode', resultType: 'Postcode: ' + KZ.searchBoxPanel.currentSearchTerm});
ambiguousResult += t.apply({id: 'busroute', resultType: 'Bus route: ' + KZ.searchBoxPanel.currentSearchTerm});
this.getContainer().update( ambiguousResult );
Ext.select('.ambiguous-result a').on({
'click' : {
fn: processAmbiguousSearchClick,
scope: json,
stopEvent: true
}
});
_gaq.push(['_trackEvent', 'Search Classification', 'Ambiguous Search - Postcode Or Bus Route', KZ.searchBoxPanel.currentSearchTerm]);
}
var processAmbiguousSearchClick = function(event, element) {
// TODO: fix history for this new menu, then delete comments from lines starting with KZ.history
var busStopOrPostCode = element.getAttribute('rel');
if(busStopOrPostCode == 'postcode') {
//KZ.historyManager.appendUserSelection('0');
// logic taken from previous version, where we displayed stopResults as postocde, why ?! is it bug ?
var tmp = {
lat : this.results.stopResults[0].lat,
lng : this.results.stopResults[0].lng
};
this.results = tmp;
} else if(busStopOrPostCode == 'busroute') {
//KZ.historyManager.appendUserSelection('1');
this.results.stopResults = undefined;
}
KZ.searchResultsManager.handleSearchResults(this);
}
}
KZ.AmbiguousResult.prototype = new KZ.AbstractResult;
Ext.ns('KZ');
KZ.RouteResult = function() {
this.name = 'route-result';
this.layers = ['']; // ugly hack
this.matchResult = function( json ) {
var results = json.results;
return results && results.routeResults && ! results.searchResults;
}
this.processResult = function(json) {
var busRoutesJson = json.results.routeResults;
if(busRoutesJson.length > 1 ) {
KZ.searchResultsPanel.setResultStatus("Did you mean..?
");
KZ.searchResultsPanel.showResultsStatusPanel();
var t = new Ext.Template([
'',
]);
t.compile();
var renderedMultipleRoutes = "";
var routePosition = 1;
Ext.each(busRoutesJson, function(route) {
var routeName = route.name;
if(route.operatorName != undefined) {
routeName += ' (' + route.operatorName + ')';
}
renderedMultipleRoutes += t.apply({id: routePosition, resultType: 'Bus route: ' + routeName});
++routePosition;
});
routePosition = 1; // this value is passed to the history in order to know wich route we have selected.
KZ.searchResultsPanel.getRouteResultPanel().update( renderedMultipleRoutes );
// handling
Ext.select('.ambiguous-result a').on({
'click' : {
fn: processAmbiguousSearchClick,
scope: json,
stopEvent: true
}
});
_gaq.push(['_trackEvent', 'Search Classification', 'Multiple Bus Route Matches', KZ.searchBoxPanel.currentSearchTerm]);
} else {
if(busRoutesJson[0].directions.length == 1) { //circular line, only one direction
KZ.routeSearchResultsContainer.displayBusRouteResults(busRoutesJson[0], 0);
} else {
KZ.routeSearchResultsContainer.displayBusRouteDirectionHyperlinks(busRoutesJson[0]);
}
_gaq.push(['_trackEvent', 'Search Classification', 'Bus Route Match', KZ.searchBoxPanel.currentSearchTerm]);
}
}
var processRouteResult = function(busRoutesJson) {
if(busRoutesJson.directions.length == 1) { //circular line, only one direction
KZ.routeSearchResultsContainer.displayBusRouteResults(busRoutesJson, 0);
} else {
KZ.routeSearchResultsContainer.displayBusRouteDirectionHyperlinks(busRoutesJson);
}
_gaq.push(['_trackEvent', 'Search Classification', 'Bus Route Match', KZ.searchBoxPanel.currentSearchTerm]);
}
var processAmbiguousSearchClick = function(event, element) {
// TODO: fix history for this new menu, then delete comments from lines starting with KZ.history
var busStopOrPostCode = element.getAttribute('rel');
if(busStopOrPostCode == 'postcode') {
//KZ.historyManager.appendUserSelection('0');
this.results.routeResults = undefined;
} else if(busStopOrPostCode == 'busroute') {
//KZ.historyManager.appendUserSelection('1');
this.results.searchResults = undefined;
} else { //We only have routes and we have to decide which direction
var menuPosition = busStopOrPostCode;
//KZ.historyManager.appendUserSelection(menuPosition);
var selectedRoute = this.results.routeResults[menuPosition - 1];
//this.results.routeResults = [ selectedRoute ];
processRouteResult(selectedRoute);
return;
}
// FIXME looks really nasty, refactor this
KZ.searchResultsManager.handleSearchResults(this);
}
this.displayResults = function() {
KZ.searchResultsPanel.showRouteResult();
}
}
KZ.RouteResult.prototype = new KZ.AbstractResult;
Ext.ns('KZ');
KZ.RailResult = function() {
this.name = 'rail-results';
this.label = tr('trainstations');
this.layers = [ 'rail' ];
this.singleResultLayers = [ 'rail' ];
this.emptyText = 'There were no train station matches';
this.labelIconClass = 'rail-marker-sprite rail-normal-state search-result-marker-type-normal-state-x';
this.processResult = function(json) {
var count = this.loadMultipleSearchResults(json);
_gaq.push( [ '_trackEvent', 'Search Classification', 'Train Station Match(es)',
KZ.searchBoxPanel.currentSearchTerm ]);
return count;
}
// different scope !
this.performRowClick = function() {
}
}
KZ.RailResult.prototype = new KZ.AbstractMultiResult;
Ext.ns('KZ');
KZ.BusStationsResult = function() {
this.name = 'bus-stations-results';
this.label = tr('busstations');
this.layers = [ 'busStation' ];
this.singleResultLayers = [ 'busStation' ];
this.emptyText = 'There were no bus station matches';
this.labelIconClass = 'busStation-marker-sprite bus-station-normal-state search-result-marker-type-normal-state-x';
this.processResult = function(json) {
var count = this.loadMultipleSearchResults(json);
_gaq.push( [ '_trackEvent', 'Search Classification', 'Bus Station Match(es)',
KZ.searchBoxPanel.currentSearchTerm ]);
return count;
}
// different scope !
this.performRowClick = function() {
}
}
KZ.BusStationsResult.prototype = new KZ.AbstractMultiResult;
Ext.ns('KZ');
KZ.MetroStationsResult = function() {
this.name = 'metro-stations-results';
this.label = 'Metro Stations';
this.layers = [ 'metroStation' ];
this.singleResultLayers = [ 'metroStation' ];
this.emptyText = 'There were no metro station matches';
this.labelIconClass = 'metroStation-marker-sprite metro-normal-state search-result-marker-type-normal-state-x';
this.processResult = function(json) {
var count = this.loadMultipleSearchResults(json);
_gaq.push( [ '_trackEvent', 'Search Classification', 'Metro Station Match(es)',
KZ.searchBoxPanel.currentSearchTerm ]);
return count;
}
// different scope !
this.performRowClick = function() {
}
}
KZ.MetroStationsResult.prototype = new KZ.AbstractMultiResult;
Ext.ns('KZ');
KZ.FerryResult = function() {
this.name = 'ferry-results';
this.label = 'Ferry';
this.layers = [ 'ferry' ];
this.singleResultLayers = [ 'ferry' ];
this.emptyText = 'There were no ferry matches';
this.labelIconClass = 'ferry-marker-sprite normal-state search-result-marker-type-normal-state-x';
this.processResult = function(json) {
var count = this.loadMultipleSearchResults(json);
_gaq.push( [ '_trackEvent', 'Search Classification', 'Ferry Match(es)',
KZ.searchBoxPanel.currentSearchTerm ]);
return count;
}
// different scope !
this.performRowClick = function() {
}
}
KZ.FerryResult.prototype = new KZ.AbstractMultiResult;
Ext.ns('KZ');
KZ.PlacesResult = function() {
this.name = 'place-results';
this.label = tr('placesandstreets');
this.layers = [ 'placeResults' ];
this.singleResultLayers = [ 'placeResults' ];
this.emptyText = 'There were no place matches';
this.labelIconClass = 'placeResults-marker-sprite normal-state search-result-marker-type-normal-state-x search-panel-stop-icon';
this.processResult = function( json ) {
var count = this.loadMultipleSearchResults(json);
_gaq.push(['_trackEvent', 'Search Classification', 'Place Matche(s)', KZ.searchBoxPanel.currentSearchTerm]);
return count;
}
// different scope !
this.performRowClick = function() {
}
}
KZ.PlacesResult.prototype = new KZ.AbstractMultiResult;
Ext.ns('KZ');
KZ.PoisResult = function(name, label) {
this.name = name+'-results';
this.label = label;
this.layers = [ name ];
this.singleResultLayers = [ name ];
this.emptyText = 'There were no PoI matches';
this.labelIconClass = 'poi-marker-sprite poi-normal-state search-result-marker-type-normal-state-x';
this.resultsStore = new Ext.data.JsonStore({
autoDestroy : false,
fields : [ 'id' ]
});
this.processResult = function(json) {
var count = this.loadMultipleSearchResults(json);
_gaq.push( [ '_trackEvent', 'Search Classification', 'PoI Matche(s)',
KZ.searchBoxPanel.currentSearchTerm ]);
return count;
}
// different scope !
this.performRowClick = function() {
}
}
KZ.PoisResult.prototype = new KZ.AbstractMultiResult;
Ext.ns('KZ');
KZ.PostcodeResult = function() {
this.name = 'postcode-result';
this.label = 'Postcode';
this.layers = [ 'placeResults' ];
var layerHandler;
this.resultStatusTpl = new Ext.Template('Found postcode "{searchTerm}".');
this.specInitialise = function() {
layerHandler = KZ.layerManager.getLayer('placeResults');
}
this.matchResult = function( json ) {
var results = json.results;
return results && results.lat && results.lng;
}
this.processResult = function(json) {
var results = json.results;
//this.setResultsStatus("Found postcode \"" + KZ.searchBoxPanel.currentSearchTerm + "\"");
KZ.mapPanel.extComponent.fireEvent("newSearchPointEvent", results.lat, results.lng);
var currentSearchTerm = KZ.searchBoxPanel.currentSearchTerm;
results.id = currentSearchTerm;
results.name = currentSearchTerm;
results.place = undefined;
layerHandler.loadSearchResultMarkers([ results ]);
this.getContainer().update('');
_gaq.push(['_trackEvent', 'Search Classification', 'Postal Code Match', KZ.searchBoxPanel.currentSearchTerm]);
}
}
KZ.PostcodeResult.prototype = new KZ.AbstractResult;
Ext.ns('KZ');
KZ.Dictionary = function() {
//Properties
//~Public
this.count = 0;
//~Private
this.Obj = new Object();
//Methods
//~Public
this.exists = Dictionary_exists;
this.add = Dictionary_add;
this.remove = Dictionary_remove;
this.removeAll = Dictionary_removeAll;
this.values = Dictionary_values;
this.keys = Dictionary_keys;
this.items = Dictionary_items;
this.getVal = Dictionary_getVal;
this.setVal = Dictionary_setVal;
this.setKey = Dictionary_setKey;
}
function Dictionary_exists(sKey){
return (this.Obj[sKey])?true:false;
}
function Dictionary_add(sKey,aVal){
var K = String(sKey);
if(this.exists(K)) return false;
this.Obj[K] = aVal;
this.count++;
return true;
}
function Dictionary_remove(sKey){
var K = String(sKey);
if(!this.exists(K)) return false;
delete this.Obj[K];
this.count--;
return true;
}
function Dictionary_removeAll(){
for(var key in this.Obj) delete this.Obj[key];
this.count = 0;
}
function Dictionary_values(){
var Arr = new Array();
for(var key in this.Obj) Arr[Arr.length] = this.Obj[key];
return Arr;
}
function Dictionary_keys(){
var Arr = new Array();
for(var key in this.Obj) Arr[Arr.length] = key;
return Arr;
}
function Dictionary_items(){
var Arr = new Array();
for(var key in this.Obj){
var A = new Array(key,this.Obj[key]);
Arr[Arr.length] = A;
}
return Arr;
}
function Dictionary_getVal(sKey){
var K = String(sKey);
return this.Obj[K];
}
function Dictionary_setVal(sKey,aVal){
var K = String(sKey);
if(this.exists(K))
this.Obj[K] = aVal;
else
this.add(K,aVal);
}
function Dictionary_setKey(sKey,sNewKey){
var K = String(sKey);
var Nk = String(sNewKey);
if(this.exists(K)){
if(!this.exists(Nk)){
this.add(Nk,this.getVal(K));
this.remove(K);
}
}
else if(!this.exists(Nk)) this.add(Nk,null);
}
Ext.ns('KZ');
KZ.MarkerHashMap = function() {
var dictionary = new KZ.Dictionary();
var eventHandlers = [];
this.add = function(marker) {
return dictionary.add(marker.id, marker);
}
this.exists = function(markerId) {
return dictionary.exists(markerId);
}
this.getValues = function() {
return dictionary.values();
}
this.getMarker = function(markerId){
return dictionary.getVal(markerId);
}
this.removeAll = function(){
this.fireEvent('unload', [this]);
dictionary.removeAll();
}
this.remove = function( markerId ){
dictionary.remove( markerId );
}
this.getCount = function() {
return dictionary.count;
}
this.loadData = function(markers, append) {
if(!append) {
this.removeAll();
}
for(var i = 0; i < markers.length; ++i) {
this.add(markers[i]);
}
this.fireEvent('load', [this, markers]);
}
this.handleEvent = function( event, callback, scope ) {
eventHandlers[event] = {
func : callback,
scope : scope
};
}
this.fireEvent = function( event, args) {
var handler = eventHandlers[event];
if( handler != undefined && KZ.util.isFunction(handler.func) ) {
var scope = handler.scope;
if(scope == undefined) {
scope = window;
}
handler.func.apply(scope, args);
}
}
}
Ext.ns('KZ');
/*****************************************
* I define what must be implemented by a map provider and
* define common functions across all map providers.
*****************************************/
KZ.AbstractMapProvider = function() {
var MSG_MAP_UNAVAILABLE = 'Map component is unavailable at this time.';
var mapProviderImplementationErrorMessage = function() { KZ.log("The Map Provider has not implemented a required method."); }
var markerDivContainer;
var topLeftControlsContainer;
var topLeftContainer;
/*********************************
* MAP FUNCTIONS
*********************************/
this.getZoom = mapProviderImplementationErrorMessage;
this.zoomIn = mapProviderImplementationErrorMessage;
this.zoomOut = mapProviderImplementationErrorMessage;
this.panUp = mapProviderImplementationErrorMessage;
this.panRight = mapProviderImplementationErrorMessage;
this.panDown = mapProviderImplementationErrorMessage;
this.panLeft = mapProviderImplementationErrorMessage;
this.setCenter = mapProviderImplementationErrorMessage;
this.zoomToBounds = mapProviderImplementationErrorMessage;
this.getBounds = function() {
// don't modify those values, it prevents against unnecessary requests for getting markers
return {swLat:1, swLng:1, neLat:0, neLng:0};
};
this.getBoundsCenter = mapProviderImplementationErrorMessage;
this.getBoundsZoomLevel = mapProviderImplementationErrorMessage;
// returns: a point with an x and y field
this.fromLatLngToDivPixel = function() {
return {x:0, y:0};
};
this.getMarkerDivContainer = function() {
if(markerDivContainer == undefined) {
var mapContainer = this.getTopLeftControlsContainer();
markerDivContainer = Ext.DomHelper.insertHtml('afterBegin', mapContainer, '');
}
return markerDivContainer;
}
this.addAfterMapLoadListener = function(callback, scope) {
if(! scope) {
scope = this;
}
callback.call(scope);
};
this.addDrawOverlayListener = function(callback, scope) {};
this.fireResizeEvent = function() {};
this.drawPolyline = mapProviderImplementationErrorMessage;
this.removeOverlay = mapProviderImplementationErrorMessage;
/*********************************
* MAP CONTROLS
*********************************/
this.addOverviewMapControl = mapProviderImplementationErrorMessage;
this.addScaleControl = mapProviderImplementationErrorMessage;
this.addMapTypeControl = mapProviderImplementationErrorMessage;
//this.getTopLeftControlsContainer = mapProviderImplementationErrorMessage;
this.preventClickPropagationOnControl = mapProviderImplementationErrorMessage;
/********************
* EVENTS
********************/
//the callback requires no arguments
this.addMoveEndListener = mapProviderImplementationErrorMessage;
//the callback requires no arguments
this.addMoveStartListener = mapProviderImplementationErrorMessage;
//the callback requires no arguments
this.addClickListener = mapProviderImplementationErrorMessage;
//the callback requires no arguments
this.addZoomEndListener = mapProviderImplementationErrorMessage;
//the callback requires no arguments
this.addDragEndListener = mapProviderImplementationErrorMessage;
//the callback requires no arguments
this.addMouseWheelListener = mapProviderImplementationErrorMessage;
//the callback requires no arguments
this.addDoubleClickListener = mapProviderImplementationErrorMessage;
this.checkResize = mapProviderImplementationErrorMessage;
/**********************
* ABSTRACT FUNCTIONS
**********************/
this.getTopLeftControlsContainer = function() {
if(topLeftControlsContainer == undefined) {
var mapContainer = document.getElementById(this.containerId);
topLeftControlsContainer = Ext.DomHelper.insertHtml('afterBegin', mapContainer, ''+MSG_MAP_UNAVAILABLE+'
');
}
return topLeftControlsContainer;
}
this.getTopLeftContainer = function() {
if(topLeftContainer == undefined) {
var mapControlsContainer = this.getTopLeftControlsContainer();
topLeftContainer = Ext.DomHelper.insertHtml('afterBegin', mapControlsContainer, '');
if(!this.isMapProviderAvailable()) {
topLeftContainer.className = 'x-hidden';
}
this.preventClickPropagationOnControl(topLeftContainer);
}
return topLeftContainer;
}
this.addPanAndZoomControl = function() {
var panControlButtons = [
{
xtype:'button',
id:'pan-north-button',
iconCls:'pan-north-icon',
x: 16,
y: 0,
handler: function(button, event) {
KZ.mapPanel.getMap().panUp();
_gaq.push(['_trackEvent', 'Map Pan', 'Navigation Button Pressed', 'North']);
event.stopEvent();
}
},
{
xtype:'button',
id:'pan-east-button',
iconCls:'pan-east-icon',
x: 32,
y: 16,
handler: function(button, event) {
KZ.mapPanel.getMap().panRight();
_gaq.push(['_trackEvent', 'Map Pan', 'Navigation Button Pressed', 'East']);
event.stopEvent();
}
},
{
xtype:'button',
id:'pan-west-button',
iconCls:'pan-west-icon',
x: 0,
y: 16,
handler: function(button, event) {
KZ.mapPanel.getMap().panLeft();
_gaq.push(['_trackEvent', 'Map Pan', 'Navigation Button Pressed', 'West']);
event.stopEvent();
}
},
{
xtype:'button',
id:'pan-south-button',
iconCls:'pan-south-icon',
x: 16,
y: 32,
handler: function(button, event) {
KZ.mapPanel.getMap().panDown();
_gaq.push(['_trackEvent', 'Map Pan', 'Navigation Button Pressed', 'South']);
event.stopEvent();
}
},
{
xtype:'button',
id:'zoom-out-button',
iconCls:'zoom-out-icon',
x: 0,
y: 48,
handler: function(button, event) {
KZ.mapPanel.getMap().zoomOut();
_gaq.push(['_trackEvent', 'Map Zoom', 'Navigation Button Pressed', 'Out']);
event.stopEvent();
}
},
{
xtype:'button',
id:'zoom-in-button',
iconCls:'zoom-in-icon',
x: 32,
y: 48,
handler: function(button, event) {
KZ.mapPanel.getMap().zoomIn();
_gaq.push(['_trackEvent', 'Map Zoom', 'Navigation Button Pressed', 'In']);
event.stopEvent();
}
}
];
var customMapControlsContainer = this.getTopLeftContainer();
new Ext.Panel({
layout: 'absolute',
height:66,
width:49,
baseCls: 'x-plain',
renderTo: customMapControlsContainer,
items: panControlButtons
});
}
this.isMapProviderAvailable = function() {
if(typeof this.providerAvailability == 'undefined') {
this.providerAvailability = true;
}
return this.providerAvailability;
}
}
Ext.ns('KZ');
/*****************************************
* This is a Google Maps abstraction
*****************************************/
KZ.MapProvider = function(containerId) {
/*********************************
* CONSTRUCTOR
*********************************/
this.containerId = containerId;
if (typeof GMap2 == 'undefined') {
this.providerAvailability = false;
return;
}
//lbtm.farasi.kizoom.com Google Maps Key ABQIAAAAoBYVJtlMCG18UkeA9cCNzRTzyv0w7mfIugLiRTlTfyunp-juThRyGqqkxafLJpWyAe2yW4aM5N9Xsg
var map = new GMap2(document.getElementById(containerId));
map.enableScrollWheelZoom();
/*********************************
* MAP FUNCTIONS
*********************************/
// The zoom may need some normalization
this.getZoom = function() {
return map.getZoom();
}
this.zoomIn = function() {
map.zoomIn();
}
this.zoomOut = function() {
map.zoomOut();
}
this.panUp = function() { map.panDirection(0, 1); }
this.panRight = function() { map.panDirection(-1, 0); }
this.panDown = function() { map.panDirection(0, -1); }
this.panLeft = function() { map.panDirection(1, 0); }
// zoom is an optional parameter
this.setCenter = function(latitude, longitude, zoom) {
map.setCenter(new GLatLng(latitude, longitude), zoom);
}
this.zoomToBounds = function(swLatBB, swLngBB, neLatBB, neLngBB) {
var swLatLng = new GLatLng(swLatBB, swLngBB);
var neLatLng = new GLatLng(neLatBB, neLngBB);
var bounds = new GLatLngBounds(swLatLng, neLatLng);
map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));
}
this.getBounds = function() {
var bounds = map.getBounds();
return {swLat:bounds.getSouthWest().lat(), swLng:bounds.getSouthWest().lng(), neLat:bounds.getNorthEast().lat(), neLng:bounds.getNorthEast().lng()};
}
this.getBoundsCenter = function(swLatBB, swLngBB, neLatBB, neLngBB) {
var swLatLng = new GLatLng(swLatBB, swLngBB);
var neLatLng = new GLatLng(neLatBB, neLngBB);
var bounds = new GLatLngBounds(swLatLng, neLatLng);
return {latitude: bounds.getCenter().lat(), longitude:bounds.getCenter().lng()};
}
this.getBoundsZoomLevel = function(swLatBB, swLngBB, neLatBB, neLngBB) {
var swLatLng = new GLatLng(swLatBB, swLngBB);
var neLatLng = new GLatLng(neLatBB, neLngBB);
var bounds = new GLatLngBounds(swLatLng, neLatLng);
return map.getBoundsZoomLevel(bounds);
}
/* returns: a point with an x and y field */
this.fromLatLngToDivPixel = function(latitude, longitude) {
return map.fromLatLngToDivPixel(new GLatLng(latitude, longitude));
}
this.getMarkerDivContainer = function() {
return map.getPane(G_MAP_MARKER_PANE);
}
this.drawPolyline = function(points){
var latLngPoints = [];
for(i = 0; i < latLngPoints.length; i++){
latLngPoints[i] = new GLatLng(points[i]['lat'], points[i]['lng']);
}
var polyline = new Polyline({'path': latLngPoints});
map.addOverlay(polyline);
}
/*********************************
* MAP CONTROLS
*********************************/
this.addMapTypeControl = function(disableHybrid) {
map.addControl(new GMapTypeControl());
if(disableHybrid == true) {
map.removeMapType(G_HYBRID_MAP);
}
}
this.addOverviewMapControl = function() {
map.addControl(new GOverviewMapControl());
}
this.addScaleControl = function() {
map.addControl(new GScaleControl());
}
this.getTopLeftControlsContainer = function() {
return Ext.DomQuery.selectNode('#map-container');
}
this.preventClickPropagationOnControl = function(element) {
// This did not appear to be necessary for Google maps since controls are added to an area of the
// map DOM where clicks do not propagate to the map itself.
}
/********************
* EVENTS
********************/
//the callback requires no arguments
this.addMoveEndListener = function(callback, scope) {
GEvent.bind(map, 'moveend', scope, callback);
}
//the callback requires no arguments
this.addMoveStartListener = function(callback, scope) {
GEvent.bind(map, 'movestart', scope, callback);
}
//the callback requires no arguments
this.addClickListener = function(callback, scope) {
GEvent.bind(map, 'click', scope, callback);
}
//the callback requires no arguments
this.addZoomEndListener = function(callback, scope) {
GEvent.bind(map, 'zoomend', scope, callback);
}
this.addDragEndListener = function(callback, scope) {
GEvent.bind(map, 'dragend', scope, callback);
}
this.addMouseWheelListener = function(callback, scope) {
GEvent.addDomListener(map.getContainer(), "DOMMouseScroll", callback);
}
this.addDoubleClickListener = function(callback, scope) {
GEvent.bind(map, "dblclick", scope, callback);
}
this.checkResize = function() {
map.checkResize();
}
return this;
}
KZ.MapProvider.prototype = new KZ.AbstractMapProvider;
Ext.ns('KZ');
KZ.MapPanel = function() {
var map;
var hideZoomInHintAfterDelayTask, showZoomInHintAfterDelayTask;
var backgroundMarkersCurrentlyHidden = false;
this.getMap = function() {
return map;
}
this.extComponent = new Ext.Container({
region:'center',
layout:'fit',
split:true,
minHeight:1,
items: [{
html: '',
xtype: 'panel'
}
]
});
this.initialise = function() {
map = new KZ.MapProvider('map-container');
map.setCenter(KZ.Config.initialMapLatitude, KZ.Config.initialMapLongitude, KZ.Config.initialMapZoom);
map.addPanAndZoomControl();
map.addScaleControl();
map.addMapTypeControl(false);
map.addAfterMapLoadListener(function(){
KZ.markerManager.initialise();
KZ.routePlotManager.initialize();
KZ.layerManager.initialise(KZ.Config.layers);
map.addMoveEndListener(this.onMapMoveEnd, this);
map.addZoomEndListener(this.onZoomEnd, this);
KZ.markerManager.fetchMarkers(); // initial marker fetch
KZ.userStopsStore.loadRecentStops();
KZ.userStopsStore.loadFavouriteStops();
map.addMoveStartListener(KZ.tooltipManager.hideTooltipInstantly, KZ.tooltipManager);
map.addClickListener(KZ.tooltipManager.hideTooltip, KZ.tooltipManager);
map.addDragEndListener(function() { _gaq.push(['_trackEvent', 'Map Pan', 'Map Dragged']); }, this);
map.addMouseWheelListener(function(evt) { if ((evt.detail || -evt.wheelDelta) < 0) { _gaq.push(['_trackEvent', 'Map Zoom', 'Mouse Wheel', 'In']); } else { _gaq.push(['_trackEvent', 'Map Zoom', 'Mouse Wheel', 'Out']); } }, this);
map.addDoubleClickListener(function(evt) { _gaq.push(['_trackEvent', 'Map Zoom', 'Double Click', 'In']);
}, this);
// invoked each time when it requires to redraw elements on the map
map.addDrawOverlayListener(function(){
this.onZoomEnd();
}, this);
this.extComponent.on('resize', function(event, element) {
var mapPanel = this;
var checkResizeFunc = function() {
map.checkResize();
// can't invoe soon after checkResize
(function() {
mapPanel.onMapMoveEnd();
}).defer(300);
}
checkResizeFunc.defer(300); // Without deferring the call, map doesn't always update properly
}, this);
}, this);
hideZoomInHintAfterDelayTask = new Ext.util.DelayedTask(function() {
Ext.get('zoom-in-hint').fadeOut({duration:1, remove:false, useDisplay: true});
});
showZoomInHintAfterDelayTask = new Ext.util.DelayedTask(function() {
Ext.get('zoom-in-hint').fadeIn({duration:1, remove:false, useDisplay: true, endOpacity: 0.88});
});
this.onMapMoveEnd.defer(500, this);
}
this.onZoomEnd = function() {
if(map.getZoom() < KZ.Config.ROUTE_MARKER_ZOOM_THRESHOLD){
KZ.markerManager.hideMarkers();
}
KZ.markerManager.updateMarkers();
if(map.getZoom() <= KZ.layerManager.getMinZoomThreshold() ){
// KZ.Config.BG_MARKER_ZOOM_THRESHOLD) {
if(KZ.searchResultsPanel.hasResults()) {
if (KZ.routeSearchResultsContainer.isShowingBusRouteDirectionHyperlinks())
Ext.get('zoom-in-hint').update('Select a route direction');
else
Ext.get('zoom-in-hint').update(tr('zoominclosertoviewothermarkers'));
} else {
Ext.get('zoom-in-hint').update(tr('zoominclosertoviewmarkers'));
}
if(!Ext.get('zoom-in-hint').isDisplayed()) {
showZoomInHintAfterDelayTask.delay(0);
}
hideZoomInHintAfterDelayTask.delay(8000);
} else {
hideZoomInHintAfterDelayTask.delay(0);
}
}
this.onMapMoveEnd = function() {
//KZ.branchLabelManager.updateLabels();
if(map.getZoom() >= KZ.layerManager.getMinZoomThreshold()) {
KZ.markerManager.updateMarkers();
KZ.markerManager.fetchMarkers();
}
KZ.routePlotManager.updateMarkers();
KZ.routePlotManager.showMarkers(map.getZoom() < KZ.Config.ROUTE_MARKER_ZOOM_THRESHOLD);
}
this.showStopInformationPanelFromStore = function() {
KZ.mapPanel.extComponent.fireEvent("stopSelectedEvent", this.marker);
}
this.showStopInformationPanelFromMarker = function() {
KZ.mapPanel.showStopInformationPanelFromId(this.stopIdArg);
}
// listOfLayers is optional
this.getMarkerRecord = function (stopId, listOfLayers) {
var listOfStores;
if(listOfLayers) {
listOfStores = listOfLayers;
} else {
listOfStores = new Array('stopResultsStore', 'selectedCallingPatternResultsStore', 'placeResultsStore', 'recentStopsStore', 'favouriteStopsStore');
listOfLayers = KZ.layerManager.getLayersNames();
}
var busStopRecord = undefined;
for(i=0; i KZ.Config.BG_MARKER_ZOOM_THRESHOLD) {
var centerPoint = map.getBoundsCenter(swLatBB, swLngBB, neLatBB, neLngBB);
map.setCenter(centerPoint.latitude, centerPoint.longitude, KZ.Config.BG_MARKER_ZOOM_THRESHOLD);
} else {
var boundsPadding = (neLatBB - swLatBB) * 0.05; //Such that search result markers are not placed at the VERY edge of the map
map.zoomToBounds(swLatBB - boundsPadding, swLngBB - boundsPadding, neLatBB + boundsPadding, neLngBB + boundsPadding);
}
this.onZoomEnd.defer(350);
},
scope: this
}
});
// IMPORTANT: newSearchPointEvent should be raised before you load data into the store (for efficiency)
this.extComponent.on({
'newSearchPointEvent' : {
fn: function(lat, lng) {
KZ.mapPanel.getMap().fireResizeEvent();
if(map.getZoom() < KZ.Config.BG_MARKER_ZOOM_THRESHOLD) {
map.setCenter(lat, lng, KZ.Config.BG_MARKER_ZOOM_THRESHOLD);
} else {
map.setCenter(lat, lng);
}
},
scope: this
}
});
this.extComponent.on({
'newSearchEvent' : {
fn: function(){},
scope: KZ.markerManager
}
});
}
Ext.ns('KZ');
KZ.TooltipManager = function() {
var tooltipTemplates = new KZ.Dictionary();
var tooltip, tooltipContents = undefined;
var hideTooltipAfterDelayTask = undefined;
var currentTooltipTargetPos = {top:-100, left:-100};
var HIDE_TOOLTIP_DELAY = 0;
var TOOLTIP_OFFSET = 18;
var previousTooltipId = undefined;
var stopMarkerTooltipTemplate, placeMarkerTooltipTemplate, customMarkerTooltipTemplate;
function initialiseStopMarkerTooltipXTemplate() {
if(!stopMarkerTooltipTemplate) {
stopMarkerTooltipTemplate = new Ext.XTemplate(
'',
'',
'towards {towards}
',
'',
'',
'{[this.getDirectionString(values.direction)]}
',
'',
'',
'{name} ',
'',
{
compiled:true,
isDefined: function(stringValue) {
return stringValue != undefined && stringValue != '';
},
getDirectionString: function(direction) {
switch (direction) {
case 'n':
return 'heading north'
break;
case 'ne':
return 'heading north-east'
break;
case 'e':
return 'heading east'
break;
case 'se':
return 'heading south-east'
break;
case 's':
return 'heading south'
break;
case 'sw':
return 'heading south-west'
break;
case 'w':
return 'heading west'
break;
case 'nw':
return 'heading north-west'
break;
default:
return '' // Empty string if we do not have heading information
break;
}
}
});
stopMarkerTooltipTemplate.compile();
}
}
var placeMarkerTooltipTemplateText = [''];
var customMarkerTooltipTemplateText = [''];
function initialiseTooltip() {
tooltip = Ext.get('map-marker-tooltip-holder');
tooltipContents = Ext.get('map-marker-tooltip');
tooltip.removeClass('x-hide-display');
tooltip.enableDisplayMode();
tooltip.on('mouseover', KZ.tooltipManager.cancelHideTooltipTask);
tooltip.on('mouseout', KZ.tooltipManager.hideTooltip);
}
this.showMarkerTooltip = function() {
if(!tooltip) {
initialiseTooltip();
}
if(hideTooltipAfterDelayTask) {
hideTooltipAfterDelayTask.cancel();
}
var targetEl = Ext.fly(this.dom);
// If user hovers back over the same tooltip
if(currentTooltipTargetPos.top == targetEl.getTop()
&& currentTooltipTargetPos.left == targetEl.getX()
&& previousTooltipId != undefined && previousTooltipId == targetEl.id) {
if(!tooltip.isVisible()) {
tooltip.show(true);
}
return;
}
var template;
// check if compiled templete exists
if(tooltipTemplates.exists(this.viewName)) {
template = tooltipTemplates.getVal(this.viewName);
} else {
if(this.view instanceof Ext.XTemplate) {
template = this.view;
} else {
template = new Ext.XTemplate(this.view);
}
template.compile();
// add to cache
tooltipTemplates.add(this.viewName, template);
}
template.overwrite(tooltipContents, this.model);
previousTooltipId = targetEl.id;
currentTooltipTargetPos.top = targetEl.getTop();
currentTooltipTargetPos.left = targetEl.getX();
tooltip.setTop(currentTooltipTargetPos.top);
tooltip.setLeft(currentTooltipTargetPos.left + TOOLTIP_OFFSET);
tooltip.show(true);
}
// deprecated
this.showTooltip = function(evt, targetEl, options) {
if(!tooltip) {
initialiseTooltip();
}
if(hideTooltipAfterDelayTask) {
hideTooltipAfterDelayTask.cancel();
}
targetEl = Ext.fly(targetEl);
// If user hovers back over the same tooltip
if(currentTooltipTargetPos.top == targetEl.getTop()
&& currentTooltipTargetPos.left == targetEl.getX()
&& previousTooltipId != undefined && previousTooltipId == targetEl.id) {
if(!tooltip.isVisible()) {
tooltip.show(true);
}
return;
}
switch(this.storeIdArg) {
case 'backgroundStopStore' :
var marker = KZ.markerManager.backgroundMarkerHashMap.getMarker(this.stopIdArg);
KZ.markerManager.setMarkerState(marker, 'hover-state');
initialiseStopMarkerTooltipXTemplate();
stopMarkerTooltipTemplate.overwrite(tooltipContents, {stopId: marker.id, name: marker.name, stopIndicator: marker.stopIndicator, towards: marker.towards, direction: marker.direction, routes: marker.routes});
break;
case 'stopResultsStore' :
var record = Ext.StoreMgr.lookup('stopResultsStore').getById(this.stopIdArg);
KZ.markerManager.setMarkerState(record, 'hover-state');
initialiseStopMarkerTooltipXTemplate();
stopMarkerTooltipTemplate.overwrite(tooltipContents, {stopId: record.get('id'), name: record.get('name'), stopIndicator: record.get('stopIndicator'), towards: record.get('towards'), direction: record.get('direction'), routes: record.get('routes')});
break;
case 'selectedCallingPatternResultsStore' :
var record = Ext.StoreMgr.lookup('selectedCallingPatternResultsStore').getById(this.stopIdArg);
KZ.markerManager.setMarkerState(record, 'hover-state');
initialiseStopMarkerTooltipXTemplate();
stopMarkerTooltipTemplate.overwrite(tooltipContents, {stopId: record.get('id'), name: record.get('name'), stopIndicator: record.get('stopIndicator'), towards: record.get('towards'), direction: record.get('direction'), routes: record.get('routes')});
break;
case 'placeResultsStore' :
// NB. For place and postcode
var record = Ext.StoreMgr.lookup('placeResultsStore').getById(this.stopIdArg);
KZ.markerManager.setMarkerState(record, 'hover-state');
if(!placeMarkerTooltipTemplate) {
placeMarkerTooltipTemplate = new Ext.XTemplate(placeMarkerTooltipTemplateText);
placeMarkerTooltipTemplate.compile();
}
placeMarkerTooltipTemplate.overwrite(tooltipContents, {id: record.id, name: record.get('name')});
break;
default :
KZ.log('Could not generate tooltip- type not defined');
}
previousTooltipId = targetEl.id;
currentTooltipTargetPos.top = targetEl.getTop();
currentTooltipTargetPos.left = targetEl.getX();
tooltip.setTop(currentTooltipTargetPos.top);
tooltip.setLeft(currentTooltipTargetPos.left + TOOLTIP_OFFSET);
tooltip.show(true);
}
this.hideTooltipInstantly = function() {
this.hideTooltip(true);
}
this.hideTooltip = function(instantly) {
if(tooltip) {
if(instantly === true) {
tooltip.hide();
KZ.markerManager.restoreHoverStateMarker();
return;
}
if(!hideTooltipAfterDelayTask) {
hideTooltipAfterDelayTask = new Ext.util.DelayedTask(function() {
tooltip.hide();
KZ.markerManager.restoreHoverStateMarker();
});
}
hideTooltipAfterDelayTask.delay(HIDE_TOOLTIP_DELAY);
}
}
this.cancelHideTooltipTask = function() {
if(hideTooltipAfterDelayTask) {
hideTooltipAfterDelayTask.cancel();
}
}
Ext.get('map-marker-tooltip').on({
'click' : {
fn: function(event, element) {
// check stop name vs bus route name
if(new Ext.Element(element).hasClass('stopAnchor')) {
var busStopId = element.getAttribute('rel');
KZ.mapPanel.showStopInformationPanelFromId(busStopId);
}
},
scope: this,
stopEvent: true,
delegate: 'a'
}
});
}
Ext.ns('KZ');
KZ.HistoryManager = function() {
Ext.History.init();
var mainPageToken = '/';
this.initialize = function() {
Ext.History.add(mainPageToken);
}
var tokenDelimiter = '=';
var actionDelimiter ='|';
var searchUrlWord = 'searchTerm';
var deptBoardStopIdUrlWord = 'stopCode';
var lastSearch = undefined;
var lastDeptBoard = undefined;
var lastMarkerId = undefined;
var lastLayerName = undefined;
this.getSearchUrlWord = function () {
return searchUrlWord;
}
this.getTokenDelimiter = function () {
return tokenDelimiter;
}
this.getActionDelimiter = function () {
return actionDelimiter;
}
this.getLastSearch = function() {
return lastSearch;
}
this.getLastDeptBoard = function() {
return lastDeptBoard;
}
//This lock is used to react against back/forward clicks and URL modified by the user only
var lockChangeEvent = false;
Ext.History.on('change', function(token){
if(!lockChangeEvent){
if(token != mainPageToken) {
if(token == '#map' || token == 'map') {
return;
}
KZ.historyManager.parseLastUrl(token);
if(lastSearch) {//if url contains searchterm
if(lastDeptBoard) { //if url contains searchterm + departure board
showDeptBoard(lastDeptBoard);
} else if(lastMarkerId && lastLayerName) {
showInfoBoard(lastMarkerId, lastLayerName);
} else {
var doNewSearch = true;
var currentResults = KZ.searchResultsManager.getCurrentSearchResults();
if( (currentResults != undefined && currentResults.stopCodeResult != undefined
&& currentResults.stopCodeResult.id == lastSearch)
|| (KZ.searchBoxPanel.currentSearchTerm != undefined
&& KZ.searchBoxPanel.currentSearchTerm == lastSearch)){
doNewSearch = false;
KZ.searchBoxPanel.performSearch(lastSearch,false);
}
if(doNewSearch == true) {
KZ.searchBoxPanel.performSearch(lastSearch);
}
}
} else if(lastDeptBoard) { //if url ONLY contains departure board
showDeptBoard(lastDeptBoard);
} else if(lastMarkerId && lastLayerName) {
showInfoBoard(lastMarkerId, lastLayerName);
} else {
KZ.log('url not well formed');
}
} else { //first page...
KZ.searchBoxPanel.currentSearchTerm = undefined;
KZ.searchResultsManager.resetCurrentSearchResults();
KZ.searchResultsPanel.resetSearchResultsPanel();
//KZ.searchPanel.doLayout(false, true); // not necessary
KZ.layerManager.cleanSearchResults();
Ext.StoreMgr.lookup('selectedCallingPatternResultsStore').removeAll();
KZ.mapPanel.getMap().setCenter(KZ.Config.initialMapLatitude, KZ.Config.initialMapLongitude, KZ.Config.initialMapZoom);
KZ.startPageManager.showStartPage();
}
}
lockChangeEvent = false;
});
// http://stackoverflow.com/questions/2409759/firefox-3-6-drops-favicon-when-doing-window-location-hash-something
if (Ext.isGecko) {
var head = Ext.get(document.getElementsByTagName("head")[0]);
if (head != null) {
Ext.History.on('change', function(token) {
var icon = head.first("link[rel='shortcut icon']");
if (icon != null) {
var href = icon.getAttribute('href');
icon.remove();
head.createChild({
tag: 'link',
rel: 'shortcut icon',
type: 'image/x-icon',
href: href
});
}
});
}
}
//TODO: similar to 'stopSelectedEvent' in TravelMap, maybe reuse some code by firing event, need to be carefully with recursion. --VF
//TODO: this needs refactoring to use the stopSelectedEvent --WR
this.restoreMarker = function (busStopRecord) {
// FIXME migration workaround, delete when finish it
if(busStopRecord instanceof Ext.data.Record) {
busStopRecord = busStopRecord.data;
}
KZ.userStopsStore.addRecentStop(busStopRecord);
KZ.markerManager.restoreSelectedStateMarker();
KZ.markerManager.setMarkerState(busStopRecord, 'selected-state');
KZ.markerManager.stopInformationPanelSelectedMarker = busStopRecord;
_gaq.push(['_trackEvent', 'Stop Selected', busStopRecord.id, busStopRecord.name]); // TODO: Once the stop selected event is introduced, this line should also be removed.
KZ.stopInformationPanel.show(busStopRecord); //TODO: This bypasses the stopSelectedEvent which should always be used as this is the common entry point for showing a departure board (used for tracking and other things). Once this method is refactored, remove the analytics tracking code above --WR
}
this.restorePoiMarker = function (poi) {
// FIXME migration workaround, delete when finish it
if(poi instanceof Ext.data.Record) {
poi = poi.data;
}
KZ.markerManager.restoreSelectedStateMarker();
KZ.markerManager.setMarkerState(poi, 'selected-state');
KZ.markerManager.stopInformationPanelSelectedMarker = poi;
_gaq.push(['_trackEvent', 'Poi Selected', poi.id, poi.name]); // TODO: Once the stop selected event is introduced, this line should also be removed.
KZ.stopInformationPanel.showPoi(poi); //TODO: This bypasses the stopSelectedEvent which should always be used as this is the common entry point for showing a departure board (used for tracking and other things). Once this method is refactored, remove the analytics tracking code above --WR
}
this.addNewSearchHistory = function(searchTerm, clearUrl) {
lockChangeEvent = true;
KZ.historyManager.parseLastUrl(Ext.History.getToken());
if(clearUrl != undefined && clearUrl == true) {
Ext.History.add(KZ.historyManager.buildUrl(searchTerm));
} else {
Ext.History.add(KZ.historyManager.buildUrl(searchTerm, lastDeptBoard));
}
}
this.addInfoBoard = function(markerId, layerName) {
lockChangeEvent = true;
KZ.historyManager.parseLastUrl(Ext.History.getToken());
var newUrl = KZ.historyManager.buildUrl(lastSearch, markerId, layerName);
Ext.History.add(newUrl);
}
this.addDepartureBoard = function(stopId) {
lockChangeEvent = true;
KZ.historyManager.parseLastUrl(Ext.History.getToken());
var newUrl = KZ.historyManager.buildUrl(lastSearch, stopId);
Ext.History.add(newUrl);
}
this.restoreBookmark = function(fragmentIdentifier) {
if(fragmentIdentifier != '') {
if(fragmentIdentifier == "#map"){
KZ.InitialSearchPanel.hideInitialSearchPanelAndShowMapCentered();
return;
}
KZ.historyManager.parseLastUrl(fragmentIdentifier);
if(lastDeptBoard) {//if url contains searchterm
showDeptBoard(lastDeptBoard);
} else if(lastMarkerId && lastLayerName) {
showInfoBoard(lastMarkerId, lastLayerName);
} else if(lastSearch) {
KZ.searchBoxPanel.performSearch(lastSearch);
}
_gaq.push(['_trackEvent', 'InitialSearchMethod', 'DeepLinkToDepartureBoard']);
}
}
var showDeptBoard = function (stopId, handleSearchResult) {
if(KZ.InitialSearchPanel.isVisible() || KZ.Config.showMapPageInsteadOfHomePage){
KZ.InitialSearchPanel.hideInitialSearchPanelAndShowMap();
}
var record = KZ.mapPanel.getMarkerRecord(stopId, [ 'bus' ]);
//if it's not in the store we have to do a new request
if(record == undefined) {
// temp
KZ.historyManager.showInfoBoardFromServer(stopId);
} else {
KZ.historyManager.restoreMarker(record);
}
}
var showInfoBoard = function (id, layerName) {
if(KZ.InitialSearchPanel.isVisible() || KZ.Config.showMapPageInsteadOfHomePage) {
KZ.InitialSearchPanel.hideInitialSearchPanelAndShowMap();
}
var record = KZ.mapPanel.getMarkerRecord(id, [ layerName ]);
//if it's not in the store we have to do a new request
if(record == undefined) {
KZ.historyManager.showInfoBoardFromServer(id, layerName);
} else {
if( layerName == 'poi') {
KZ.historyManager.restorePoiMarker(record);
} else {
KZ.historyManager.restoreMarker(record);
}
}
}
this.closeStopBoardPanel = function () {
KZ.historyManager.parseLastUrl(Ext.History.getToken());
if(lastDeptBoard != undefined) {
lockChangeEvent = true;
Ext.History.add(KZ.historyManager.buildUrl(lastSearch));
}
}
this.showInfoBoardFromServer = function ( markerId, layerName ) {
var url = 'marker/stop';
// for buses search by stopCode
if(! layerName || KZ.util.inArray(['bus'], layerName)) {
layerName = 'bus';
url = 'stopCode';
} else if( layerName == 'poi' ) {
url = 'marker/poi';
}
if(markerId) {
sendRequest('/' + url + '/' + markerId + '/', function(resp, opt) {
successMarkerDetailsCallback(resp, layerName);
});
}
}
var successMarkerDetailsCallback = function(resp, layerName) {
var jsonResults = KZ.util.evalToJSON(resp.responseText); // should return only one marker
KZ.searchResultsManager.handleSearchAndHideResults(jsonResults);
var layer = KZ.layerManager.getLayer( layerName );
var marker = layer.getSearchResultsStore().getValues()[0]; // take first marker
if( marker == undefined ) {
// this should not occur
return;
} else {
KZ.markerManager.markerClick(marker, layer);
}
}
var sendRequest = function(url, successCallback) {
try {
KZ.searchBoxPanel.getSearchConnection().request({
url : url,
headers : {
Accept : 'application/json'
},
timeout : 8000,
scope : this,
success : successCallback,
failure : function(response, opts) {
KZ.log('server-side failure with status code ' + response.status);
var emptyResults = {};
}
});
} catch (e) {
KZ.log(e);
}
}
//
//
this.parseLastUrl = function(url) {
lastSearch = undefined;
lastDeptBoard = undefined;
lastLayerName = undefined;
lastMarkerId = undefined;
if(url){
var parts = url.split(actionDelimiter);
for(i=0; i this.right
|| r2.right < this.left
|| r2.top > this.bottom
|| r2.bottom < this.top);
}
}
Ext.ns('KZ');
KZ.LayerSelectorMenu = function() {
this.addMenuOption = function( layer ) {
//checkboxGroupComponent.items.push( chkBox );
if(layer.subLayers){
var items = [];
for(var i=0; i Select Layers: ',
text: '{0}: '.bind(tr('selectlayer')),
xtype: 'tbtext'
}]
}),
region:'north',
height: 18,
width: 250,
minSize: 0,
maxSize:100,
margins: '0',
cmargins: '0',
border:false,
bodyBorder:false
});
this.makeCheckbox = function(layer, inMenu) {
var config = {
boxLabel: //" "
layer.label,
text: layer.label,
name: layer.label,
cls: 'layer-selector-checkbox '+ name + '-layer-selector-checkbox',
//margin: '1 1 1 1',
style: 'margin:0',
cmargin: '0',
checked: (layer.layerConfig)?layer.layerConfig.visible:false,
listeners: { check: changeLayerVisibility , checkchange: changeLayerVisibility}
}
var chkBox;
if(inMenu){
chkBox = new Ext.menu.CheckItem(config);
}else{
chkBox = new Ext.form.Checkbox(config);
}
chkBox.mapLayer = layer;
if (layer.selectorTooltip) {
chkBox.on('render', function(c){
new Ext.ToolTip({
// c is the checkbox itself, but we want the label as well, so we're
// adding the tooltip to a parent div containing both of them
target: c.getEl().findParent('div.x-form-check-wrap'),
html: layer.selectorTooltip
});
});
}
return chkBox;
}
this.refresh = function() {
// this.layerSelectorContainer.getTopToolbar().add( checkboxGroupComponent );
// checkboxGroupComponent.show();
this.layerSelectorContainer.doLayout(true, false);
// checkboxGroupComponent.show();
}
}
Ext.ns('KZ');
KZ.LayerHashMap = function() {
var dictionary = new KZ.Dictionary();
this.add = function(layer) {
return dictionary.add(layer.name, layer);
}
this.exists = function(layerName) {
return dictionary.exists(layerName);
}
this.getAllLayers = function() {
return dictionary.values();
}
this.getLayer = function(layerName){
return dictionary.getVal(layerName);
}
this.getLayers = function(layerNames) {
var layerMap = [];
if(layerNames instanceof Array) {
for(var i = 0; i < layerNames.length; ++i) {
var name = layerNames[i];
if(this.exists(name)) {
layerMap[layerMap.length] = new Array( name, this.getLayer(name) );
}
}
}
return layerMap;
}
this.removeLayers = function(layerNames) {
if(layerNames instanceof Array) {
for(var i = 0; i < layerNames.length; ++i) {
var name = layerNames[i];
if(this.exists(name)) {
dictionary.remove(name);
}
}
}
}
this.removeAll = function(){
dictionary.removeAll();
}
}
Ext.ns('KZ');
KZ.LayerManager = function() {
var minZoomThreshold = 100;
var layers = new KZ.LayerHashMap();
var layersNames = [];
var layerSelectorMenu = new KZ.LayerSelectorMenu();
/** loading layers **/
this.addLayers = function(layers) {
if(layers instanceof Array) {
Ext.each(layers, function(layer) {
this.addLayer(layer);
}, this);
} else {
this.addLayer(layers);
}
}
this.addLayer = function(layer) {
if(layer instanceof KZ.AbstractLayer) {
if(! layers.exists(layer.name)) {
layers.add(layer);
} else {
KZ.error("Layer '"+ layer.name + !"' exists!");
}
} else {
KZ.error("Could not load '" + layer.name + "' layer, it's not a layer!");
}
};
/**
* Initialises layers. Need to be invoked.
*
* @param config optional set of options for specific layers, an example:
* config = {
* poi : { visible: false }, // markers are initially invisible
* bus : { zoomThreshold: 10 } // there are a lot of markers on small area, so we want avoid to performance issues
* } // this is the way we should do this
*/
this.initialise = function(config) {
if(config == undefined) {
config = {};
}
var layersToRemove = [];
Ext.each(layers.getAllLayers(), function(layer) {
var layerConfig = config[ layer.name ];
var success = layer.initialise(layerConfig);
if(!success) {
layersToRemove.push(layer.name);
}else {
layersNames.push(layer.name);
// compute minZoomThreshold
if(minZoomThreshold > layer.layerConfig.zoomThreshold ){
minZoomThreshold = layer.layerConfig.zoomThreshold;
}
// add layer selector option
if( layer.layerConfig.fetchedMarkers ) {
layerSelectorMenu.addMenuOption( layer );
}
}
}, this);
layerSelectorMenu.refresh();
/* delete invalid and auto-destroyed layers */
layers.removeLayers(layersToRemove);
}
this.getMinZoomThreshold = function(){
return minZoomThreshold;
}
/** load layer **/
this.processLayerResults = function(layerSet) {
Ext.each(layerSet, function(layerResults) {
var layerName = layerResults.name;
// ignore layers which has no handling
if(layers.exists(layerName)) {
var layer = layers.getLayer(layerName);
layer.loadFetchedMarkers(layerResults.markers);
};
});
}
/** clean search result markers **/
this.cleanSearchResults = function() {
Ext.each(layers.getAllLayers(), function(layer) {
layer.cleanSearchResults();
});
};
this.getLayers = function() {
return layers;
}
this.getLayersNames = function() {
return layersNames;
}
this.getLayer = function(layerName) {
return layers.getLayer(layerName);
}
this.getLayerSelectorContainer = function() {
return layerSelectorMenu.layerSelectorContainer;
}
}
Ext.ns('KZ');
KZ.AbstractLayer = function() {
var notImplemented = function() { KZ.log("This Layer has not implemented a required method."); }
// regexp patterns temporarly here, after migration will go back to MarkerManager, toegether with marker specific methods
var MARKER_STATE_MATCHER = /normal-state|hover-state|selected-state/g;
var MARKER_TYPE_MATCHER = /bus-stop-marker-type|search-result-marker-type/g;
var SELECTED_STATE = /selected-state/g;
/*********************************
* LAYER FUNCTIONS
*********************************/
this.markerContainer;
/** fields **/
// MANDATORY:
// defines name of the layer (must be the same as corresponding node in JSON served by server)
this.name = 'unknown';
// label is mandatory when flag fetchedMarkers is set
// it's used on layer selector menu
this.label = 'unknown';
this.fetchedMarkersStore = new KZ.MarkerHashMap();
this.searchResultMarkersStore = new KZ.MarkerHashMap();
/** layer configuration **/
// may be overriden:
// - TODO in the layer,
// - while marker init (for more details check LayerManager.initialise)
// - at runtime using setConfig method
// fetchedMarkers and searchResults MUST NOT be changed at runtime
// TODO move config to store with support of listeners and blocking specific records
this.layerConfig = {
// false destroys layer while init, it may be used to specify
// if layer is available/supported for a client
enabled : true,
// makes visible (or not) markers on the map (also used while init render)
visible : true,
// default zoom threshold
zoomThreshold : KZ.Config.BG_MARKER_ZOOM_THRESHOLD,
// zoom threshold for search result markers
specialZoomThreshold : 7,//KZ.Config.ROUTE_MARKER_ZOOM_THRESHOLD,
// indicates support for display search result markers (on the map and in the left hand panel)
searchResults : true,
// indicates support for display fetched markers and
// option in the layer selector menu is added, when set this.label MUST be defined.
fetchedMarkers : true,
infoPanel : true,
stateClassPrefix: ''
// TODO in the future we should think about another threshold like markerCountThreshold
};
this.initConfig;
/** layer API methods need (or not) to be implemented **/
// NOT MANDATORY:
// TODO descripe params here
// event handler invoked when marker is clicked on the map (TODO or search results in the left hand panel)
this.onMarkerClick;
this.onMarkerOver;
this.onMarkerOut;
// marker specific event handlers
this.defineLayerSpecificEventHandlers;
// layer specific operations while init
this.layerSpecificInit;
/** layer initialisation */
this.initialise = function(config) {
if(this.initConfig) {
loadLayerConfig(this.initConfig, this);
}
loadLayerConfig(config, this);
/* auto-destroy && invalid */
if( !this.layerConfig.enabled || !this.validation()) {
return false;
}
/* create markers container */
var mapContainer = KZ.mapPanel.getMap().getMarkerDivContainer();
var dh = Ext.DomHelper;
this.markerContainer = dh.insertHtml('afterBegin', mapContainer, '');
/******* EVENT HANDLERS ******/
if( this.layerConfig.fetchedMarkers ) {
this.getMarkerStore().handleEvent( 'load', this.createFetchedMarkersFromStore, this );
}
if( this.layerConfig.searchResults ) {
this.getSearchResultsStore().handleEvent( 'load', this.createResultMarkersFromStore, this );
}
if(Ext.isFunction(this.layerSpecificInit)) {
this.layerSpecificInit(config);
}
return true;
}
/** basic **/
this.getMarkerStore = function() {
return this.fetchedMarkersStore;
}
this.getSearchResultsStore = function() {
return this.searchResultMarkersStore;
}
/** loading markers **/
this.loadFetchedMarkers = function(markers, move) {
this.loadMarkers(markers, this.getMarkerStore(), this.getSearchResultsStore(), move);
};
this.loadSearchResultMarkers = function(markers) {
this.loadMarkers(markers, this.getSearchResultsStore(), this.getMarkerStore(), true);
};
this.loadMarkers = function(markers, primStore, secStore, move) {
var unstoredMarkers = [];
for(var i = 0; i < markers.length; ++i) {
var marker = markers[i];
var id = marker.id;
if( ! primStore.exists(id) ) {
// if it exists in secondary store, move it to primary when 'move' flag is set
if( secStore.exists(id) ) {
if( ! move ) {
// we don't want to move marker to primary store, so we're ommiting it
continue;
}
var existingMarker = secStore.getMarker(id);
secStore.remove(id);
marker = markers[i] = existingMarker;
} else if( !KZ.util.isSet(marker.domRef) ) {
marker.domRef = undefined; // DOM reference to correspondig obj
}
unstoredMarkers.push(marker);
}
//
}
if(unstoredMarkers.length > 0) {
primStore.loadData(unstoredMarkers, true);
}
}
/** creating markers **/
this.createResultMarkersFromStore = function(store, records, searchResultMarkers) {
this.createMarkersFromStore(store, records, true);
}
this.createFetchedMarkersFromStore = function(store, records) {
this.createMarkersFromStore(store, records, false);
}
this.createMarkersFromStore = function(store, records, searchResultMarkers) {
var bounds = KZ.mapPanel.getMap().getBounds();
var swLat = bounds.swLat - 0.04;
var swLng = bounds.swLng - 0.04;
var neLat = bounds.neLat + 0.04;
var neLng = bounds.neLng + 0.04;
var markerType;
if(searchResultMarkers == undefined || !searchResultMarkers) {
markerType = 'bus-stop-marker-type';
threshold = this.layerConfig.zoomThreshold;
} else {
markerType = 'search-result-marker-type';
threshold = this.layerConfig.specialZoomThreshold;
}
var id, point;
// should we iterate over records instead of whole store ?
//store.each(function(record, index) {
// TODO change to records
var records = store.getValues();
for(var i = 0; i < records.length; ++i) {
var record = records[i];
if(record.domRef == undefined) {
this.createMarker(record, markerType, store.storeId);
}
// if marker exists - update if necessary
// TODO refactor this
else if( record.lat > swLat && record.lng > swLng && record.lat < neLat && record.lng < neLng && !this.shouldHideMarker(record) ) {
// update type
point = KZ.mapPanel.getMap().fromLatLngToDivPixel(record.lat, record.lng);
record.domRef.className = record.domRef.className.replace(MARKER_TYPE_MATCHER, markerType);
uncommenAnchorPoint = KZ.markerManager.getMarkerAnchorPoint(record.domRef.className.match(MARKER_STATE_MATCHER)[0]);
record.domRef.style.top = (point.y - uncommenAnchorPoint.y) + 'px';
record.domRef.style.left = (point.x - uncommenAnchorPoint.x) + 'px';
record.domRef.style.display = 'block';
}
if(KZ.mapPanel.getMap().getZoom() < threshold) {
record.domRef.style.display = 'none';
}
}
}
this.createMarker = function(record, markerType, storeId) {
if(record.domRef != undefined) {
return;
}
var markerAnchorPoint = KZ.markerManager.getMarkerAnchorPoint('normal-state');
var dh = Ext.DomHelper;
var scope = { stopIdArg: record.id, storeIdArg: storeId};
var point = KZ.mapPanel.getMap().fromLatLngToDivPixel(record.lat, record.lng);
point.y -= markerAnchorPoint.y;
point.x -= markerAnchorPoint.x;
//
var direction = record.direction;
if(direction == undefined || direction == '') {
direction = 'x';
}
//markerType = 'bus-stop-marker-type';
var cssName=(this.superLayer)?this.superLayer.name:this.name;
var markerElement = dh.insertHtml('afterBegin', this.markerContainer, '');
record.domRef = markerElement;
var markerScope = getMarkerScope(record);
handleEvent(markerElement, 'click', this.setSelectedState, record);
handleEvent(markerElement, 'click', this.onMarkerClick, markerScope);
handleEvent(markerElement, 'mouseover', this.setHoverState, record);
handleEvent(markerElement, 'mouseover', this.onMarkerOver, markerScope);
handleEvent(markerElement, 'mouseout', this.setNormalState, record);
handleEvent(markerElement, 'mouseout', this.onMarkerOut, markerScope);
if(Ext.isFunction(this.defineLayerSpecificEventHandlers)) {
this.defineLayerSpecificEventHandlers.call(markerScope);
}
}
// temp workaround
this.markerClick = function( record ) {
this.setSelectedState.call(record);
if(Ext.isFunction(this.onMarkerClick)) {
this.onMarkerClick.call( getMarkerScope(record) );
}
}
/** update markers **/
this.updateMarkers = function() {
if( !this.layerConfig.fetchedMarkers ) {
return;
}
this.updateStoreMarkers(this.getMarkerStore(), this.layerConfig.zoomThreshold);
}
this.updateSearchResultMarkers = function() {
if( !this.layerConfig.searchResults ) {
return;
}
this.updateStoreMarkers(this.getSearchResultsStore(), this.layerConfig.specialZoomThreshold);
}
this.updateStoreMarkers = function(store, threshold) {
if(store.getCount() == 0) {
return;
}
if(KZ.mapPanel.getMap().getZoom() < threshold) {
hideStoreMarkers(store);
return;
}
var commonAnchorPoint, uncommenAnchorPoint;
var bounds = KZ.mapPanel.getMap().getBounds();
var swLat = bounds.swLat - 0.04;
var swLng = bounds.swLng - 0.04;
var neLat = bounds.neLat + 0.04;
var neLng = bounds.neLng + 0.04;
var point;
var markers = store.getValues();
for(var i = 0; i < markers.length; ++i) {
var marker = markers[i];
// Render markers that are just off screen also so they are already rendered when the user drags the map
if(marker.lat > swLat && marker.lng > swLng && marker.lat < neLat && marker.lng < neLng) {
point = KZ.mapPanel.getMap().fromLatLngToDivPixel(marker.lat, marker.lng);
uncommenAnchorPoint = KZ.markerManager.getMarkerAnchorPoint(marker.domRef.className.match(MARKER_STATE_MATCHER)[0]);
marker.domRef.style.top = (point.y - uncommenAnchorPoint.y) + 'px';
marker.domRef.style.left = (point.x - uncommenAnchorPoint.x) + 'px';
// do not hide visible layer and selected / search result markers
if( !this.shouldHideMarker(marker) ) {
marker.domRef.style.display = 'block';
} else {
marker.domRef.style.display = 'none';
}
} else if(marker.domRef) {
marker.domRef.style.display = 'none';
}
}
}
/** additional stuff **/
this.setConfig = function(property, value) {
this.layerConfig[property] = value;
}
this.hasInformationPanel = function() {
return this.layerConfig.infoPanel;
}
/** update marker state **/
// methods with other scope
this.setNormalState = function() {
KZ.markerManager.setMarkerState(this, 'normal-state');
}
this.setHoverState = function() {
KZ.markerManager.setMarkerState(this, 'hover-state');
}
this.setSelectedState = function() {
KZ.markerManager.restoreSelectedStateMarker();
KZ.markerManager.setMarkerState(this, 'selected-state');
KZ.markerManager.stopInformationPanelSelectedMarker = this;
}
/** hide / show markers **/
this.shouldHideMarker = function(markerRecord) {
try {
var className = markerRecord.domRef.className;
if( this.isSearchResult(markerRecord) || SELECTED_STATE.test(className) || this.layerConfig.visible ) {
return false;
} else {
return true;
}
} catch(e) {
return false;
}
}
this.isSearchResult = function(markerRecord) {
return this.getSearchResultsStore().exists(markerRecord.id);
}
this.hideMarkers = function() {
Ext.each(this.getMarkerStore().getValues(), function(marker) {
if( this.shouldHideMarker(marker) ) {
changeMarkerDisplay(marker, 'none');
}
}, this);
}
this.showMarkers = function() {
Ext.each(this.getMarkerStore().getValues(), function(marker) {
changeMarkerDisplay(marker, 'block');
}, this);
}
var hideStoreMarkers = function(store) {
Ext.each(store.getValues(), function(marker) {
changeMarkerDisplay(marker, 'none');
}, this);
}
var showStoreMarkers = function(store) {
Ext.each(store.getValues(), function(marker) {
changeMarkerDisplay(marker, 'block');
}, this);
}
var changeMarkerDisplay = function(marker, display) {
var domRef = marker.domRef;
if(domRef) {
if(display == 'block') {
var point = KZ.mapPanel.getMap().fromLatLngToDivPixel(marker.lat, marker.lng);
var uncommenAnchorPoint = KZ.markerManager.getMarkerAnchorPoint(domRef.className.match(MARKER_STATE_MATCHER)[0]);
domRef.style.top = (point.y - uncommenAnchorPoint.y) + 'px';
domRef.style.left = (point.x - uncommenAnchorPoint.x) + 'px';
}
domRef.style.display = display;
}
}
this.validation = function() {
try {
validate('Layer name not set.', this.name != 'unknown');
validate('Function getMarkerStore() not defined in "' + this.name + '" layer.', this.getMarkerStore != notImplemented);
if( this.layerConfig.fetchedMarkers ) {
validate('Layer label not set.', this.label != 'unknown');
}
} catch(e) {
KZ.error(e);
return false;
}
return true;
}
/*** SEARCH RESULTS ***/
this.cleanSearchResults = function() {
var resultStore = this.getSearchResultsStore();
var markerStore = this.getMarkerStore();
if(this.layerConfig.fetchedMarkers) {
this.loadFetchedMarkers(resultStore.getValues(), true);
}
resultStore.removeAll();
}
this.setNormalMarkerType = function(record) {
changeMarkerType(record, MARKER_TYPE_MATCHER, 'bus-stop-marker-type');
}
this.setResultMarkerType = function(record) {
changeMarkerType(record, MARKER_TYPE_MATCHER, 'search-result-marker-type');
}
var changeMarkerType = function(marker, matcher, type) {
var domRef = marker.domRef;
if(domRef) {
domRef.className.replace(marker, type);
}
}
this.getMarker = function(markerId) {
var normalMarker = this.getMarkerStore().getMarker( markerId );
if(normalMarker) {
return normalMarker;
}
var resultMarker = this.getSearchResultsStore().getMarker( markerId );
if(resultMarker) {
return resultMarker;
}
return undefined;
}
/** PRIVATE METHODS **/
var loadLayerConfig = function(config, scope) {
if(scope.layerConfig != undefined) {
for(option in config) {
scope.layerConfig[option] = config[option];
}
} else {
KZ.error('Error during layer config loading.');
}
}
var handleEvent = function(obj, type, callback, scope) {
if(Ext.isFunction(callback)) {
Ext.EventManager.on(obj, type, callback, scope);
}
}
var contains = function(store, id) {
if(store.indexOfId(id) > -1) {
return true;
} else {
return false;
}
}
var getMarkerScope = function(record) {
return {
id : record.id,
marker : record,
dom : Ext.get( record.domRef )
};
}
var validate = function(errMessage, assertion) {
if(!assertion) {
throw "Validation failed. Cause: " + errMessage;
}
}
}
Ext.ns('KZ');
KZ.BusStopLayer = function() {
/** FIELDS **/
// ['id', 'smsCode', 'name', 'stopIndicator', 'towards', 'direction', 'lat', 'lng', 'routes', 'street', { name: 'objectType', defaultValue: 'STOP' } ];
this.name = 'bus';
this.label = tr('busstops');
var tooltipTemplate = new Ext.XTemplate(
'',
'',
'towards {towards}
',
'',
'',
'{[this.getDirectionString(values.direction)]}
',
'',
{
compiled:true,
isDefined: function(stringValue) {
return stringValue != undefined && stringValue != '';
},
getDirectionString: function(direction) {
switch (direction) {
case 'n':
return 'heading north'
break;
case 'ne':
return 'heading north-east'
break;
case 'e':
return 'heading east'
break;
case 'se':
return 'heading south-east'
break;
case 's':
return 'heading south'
break;
case 'sw':
return 'heading south-west'
break;
case 'w':
return 'heading west'
break;
case 'nw':
return 'heading north-west'
break;
default:
return '' // Empty string if we do not have heading information
break;
}
}
});
/** PUBLIC METHODS **/
this.onMarkerClick = function() {
this.storeIdArg = 'busStopMarkersStore';
this.stopIdArg = this.id;
//KZ.mapPanel.showStopInformationPanelFromStore.call(this);
KZ.viewport.extComponent.fireEvent("stopSelectedEvent", this);
KZ.historyManager.addInfoBoard(this.id, 'bus');
}
this.onMarkerOver = function() {
// tooltip
this.viewName = 'busStop';
this.view = tooltipTemplate;
this.model = {
id : this.marker.id,
name : this.marker.name,
stopIndicator : this.marker.stopIndicator,
towards : this.marker.towards,
direction : this.marker.direction,
street : this.marker.street
};
KZ.tooltipManager.showMarkerTooltip.call(this);
}
this.onMarkerOut = function() {
KZ.tooltipManager.hideTooltip.call(this);
}
return this;
}
KZ.BusStopLayer.prototype = new KZ.AbstractLayer;
Ext.ns('KZ');
KZ.PlacesLayer = function() {
/** FIELDS **/
var placesStoreFields = [ 'id', 'name', 'place', 'lat', 'lng' ];
var tooltipTemplate = [''];
this.name = 'placeResults';
this.initConfig = {
fetchedMarkers : false,
infoPanel : false
};
this.layerSpecificInit = function() {
this.getSearchResultsStore().handleEvent( 'unload', this.deleteMarkersFormDom, this );
}
this.onMarkerClick = function() {
KZ.stopInformationPanel.extComponent.fireEvent("closeStopBoard");
}
this.onMarkerOver = function() {
// tooltip
this.viewName = 'place';
this.view = tooltipTemplate;
this.model = {
id : this.marker.id,
name : this.marker.name
};
KZ.tooltipManager.showMarkerTooltip.call(this);
}
this.onMarkerOut = function() {
KZ.tooltipManager.hideTooltip.call(this);
}
this.deleteMarkersFormDom = function( store ) {
var markers = store.getValues();
for(var i = 0; i < markers.length; ++i) {
var marker = markers[i];
if(marker.domRef) {
marker.domRef.parentNode.removeChild(marker.domRef);
marker.domRef = undefined;
}
} // for
}
return this;
}
KZ.PlacesLayer.prototype = new KZ.AbstractLayer;
Ext.ns('KZ');
KZ.PoiLayer = function(layerName, layerLabel) {
/** FIELDS **/
this.name = layerName;
this.label = layerLabel;
this.selectorTooltip = tr('poiSelectorTooltip');
this.fetchedMarkersStore = new KZ.MarkerHashMap();
this.searchResultMarkersStore = new KZ.MarkerHashMap();
this.subLayers = null;
this.superLayer = null;
this.visible=true;
// tooltipTemplete may be array of strings or an instance of Ext.XTemplate
var tooltipTemplate = [''];
/** PUBLIC METHODS **/
/** methods with different scope
* this.id - marker id
* this.dom - DOM obj ref
* this.marker - marker data
*/
this.onMarkerClick = function() {
this.storeIdArg = 'poiMarkersStore';
KZ.stopInformationPanel.showPoi(this.marker);
KZ.historyManager.addInfoBoard(this.id, 'poi');
}
this.onMarkerOver = function() {
// tooltip
this.viewName = 'poi';
this.view = tooltipTemplate;
this.model = {
id : this.marker.id,
name : this.marker.name
};
KZ.tooltipManager.showMarkerTooltip.call(this);
}
this.onMarkerOut = function() {
KZ.tooltipManager.hideTooltip.call(this);
}
this.addSubLayer = function(subLayer){
if(this.subLayers === null){
this.subLayers = [];
}
subLayer.superLayer=this;
this.subLayers.push(subLayer);
}
/*
* Since the POI layer can have multiple instances it shouldn't
* it share it's visible property */
this.setConfig = function(key, value){
if(key == 'visible'){
this.visible = value;
}else{
KZ.PoiLayer.prototype.setConfig.call(this, key, value);
}
}
this.shouldHideMarker = function(markerRecord) {
return !this.visible || KZ.PoiLayer.prototype.shouldHideMarker.call(this, markerRecord);
}
/****************************************************************/
return this;
}
KZ.PoiLayer.prototype = new KZ.AbstractLayer;
Ext.ns('KZ');
KZ.FerryLayer = function() {
/** FIELDS **/
// ['id', 'name', 'lat', 'lng', { name: 'objectType', defaultValue: 'STOP' } ];
this.name = 'ferry';
this.label = 'Ferry';
var tooltipTemplate = [''];
/** PUBLIC METHODS **/
this.onMarkerClick = function() {
this.storeIdArg = 'ferryMarkersStore';
this.stopIdArg = this.id;
KZ.mapPanel.showStopInformationPanelFromStore.call(this);
KZ.historyManager.addInfoBoard(this.id, 'ferry');
}
this.onMarkerOver = function() {
// tooltip
this.viewName = 'ferry';
this.view = tooltipTemplate;
this.model = {
id : this.marker.id,
name : this.marker.name
};
KZ.tooltipManager.showMarkerTooltip.call(this);
}
this.onMarkerOut = function() {
KZ.tooltipManager.hideTooltip.call(this);
}
return this;
}
KZ.FerryLayer.prototype = new KZ.AbstractLayer;
Ext.ns('KZ');
KZ.MetroStationLayer = function() {
/** FIELDS **/
// ['id', 'name', 'lat', 'lng', 'objectType' ];
this.name = 'metroStation';
this.label = 'Metro Stations';
var tooltipTemplate = [''];
/** PUBLIC METHODS **/
this.onMarkerClick = function() {
this.storeIdArg = 'metroStationMarkersStore';
this.stopIdArg = this.id;
KZ.mapPanel.showStopInformationPanelFromStore.call(this);
KZ.historyManager.addInfoBoard(this.id, 'metroStation');
}
this.onMarkerOver = function() {
// tooltip
this.viewName = 'metroStation';
this.view = tooltipTemplate;
this.model = {
id : this.marker.id,
name : this.marker.name
};
KZ.tooltipManager.showMarkerTooltip.call(this);
}
this.onMarkerOut = function() {
KZ.tooltipManager.hideTooltip.call(this);
}
return this;
}
KZ.MetroStationLayer.prototype = new KZ.AbstractLayer;
Ext.ns('KZ');
KZ.RailLayer = function() {
/** FIELDS **/
// ['id', 'name', 'lat', 'lng', 'objectType' ];
this.name = 'rail';
this.label = tr('trainstations');
var tooltipTemplate = [''];
/** PUBLIC METHODS **/
this.onMarkerClick = function() {
this.storeIdArg = 'railMarkersStore';
this.stopIdArg = this.id;
KZ.mapPanel.showStopInformationPanelFromStore.call(this);
KZ.historyManager.addInfoBoard(this.id, 'rail');
}
this.onMarkerOver = function() {
// tooltip
this.viewName = 'rail';
this.view = tooltipTemplate;
this.model = {
id : this.marker.id,
name : this.marker.name
};
KZ.tooltipManager.showMarkerTooltip.call(this);
}
this.onMarkerOut = function() {
KZ.tooltipManager.hideTooltip.call(this);
}
return this;
}
KZ.RailLayer.prototype = new KZ.AbstractLayer;
Ext.ns('KZ');
KZ.BusStationLayer = function() {
/** FIELDS **/
// ['id', 'name', 'lat', 'lng', 'objectType' ];
this.name = 'busStation';
this.label = tr('busstations');
var tooltipTemplate = [''];
/** PUBLIC METHODS **/
this.onMarkerClick = function() {
// TODO fill this
this.storeIdArg = 'busStationMarkersStore';
this.stopIdArg = this.id;
//console.info(this);
KZ.mapPanel.showStopInformationPanelFromStore.call(this);
//Ext.EventManager.on(markerElement, , scope);
//KZ.stopInformationPanel.arrivalsGrid.refreshArrivals(true, false);
KZ.historyManager.addInfoBoard(this.id, 'busStation');
}
this.onMarkerOver = function() {
// tooltip
this.viewName = 'busStation';
this.view = tooltipTemplate;
this.model = {
id : this.marker.id,
name : this.marker.name
};
KZ.tooltipManager.showMarkerTooltip.call(this);
}
this.onMarkerOut = function() {
KZ.tooltipManager.hideTooltip.call(this);
}
return this;
}
KZ.BusStationLayer.prototype = new KZ.AbstractLayer;
Ext.ns('KZ');
KZ.MarkerManager = function() {
var markerTilesFetchedSet = new KZ.Dictionary(); // Stores mercator tiles (map squares) for which we have already fetched for.
var MERCATOR_PROJECTION_ZOOM_LEVEL = 13; // How large an area do we fetch markers for on each request
var markerConnection;
var searchResultsMarkerContainer;
var hoverStateMarker; // The marker currently in the hover state
this.stopInformationPanelSelectedMarker; // The marker currently selected
var MARKER_STATE_MATCHER = /normal-state|hover-state|selected-state/g;
var MARKER_TYPE_MATCHER = /bus-stop-marker-type|search-result-marker-type/g;
var NORMAL_STATE_X_ANCHOR_POINT = 7;
var NORMAL_STATE_Y_ANCHOR_POINT = 7;
var HOVER_STATE_X_ANCHOR_POINT = 8;
var HOVER_STATE_Y_ANCHOR_POINT = 23;
var SELECTED_STATE_X_ANCHOR_POINT = 12;
var SELECTED_STATE_Y_ANCHOR_POINT = 32;
this.initialise = function () {
// its not used
//var mapContainer = KZ.mapPanel.getMap().getMarkerDivContainer();
}
// These functions make use of a mercator projection to break the map into square regions. These regions are then
// used as areas of the map in which to request tiles
function longToTile(lon,zoom) { return (Math.floor((lon+180)/360*Math.pow(2,zoom))); }
function latToTile(lat,zoom) { return (Math.floor((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom))); }
function tileToLong(x,z) { return (x/Math.pow(2,z)*360-180); }
function tileToLat(y,z) { var n=Math.PI-2*Math.PI*y/Math.pow(2,z); return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n)))); }
function getParams() {
var poiIds = [];
var groupIds = [];
var stopIds = [];
var layerListToIgnore = [];
var layers = KZ.layerManager.getLayers().getAllLayers();
for(var i = 0; i < layers.length; ++i) {
var layer = layers[i];
// list layerListToIgnore
if(KZ.mapPanel.getMap().getZoom() < layer.layerConfig.zoomThreshold ){
layerListToIgnore.push(layer.name);
} else {
var layerMarkers = layer.getMarkerStore().getValues();
for(var j = 0; j < layerMarkers.length; ++j) {
var marker = layerMarkers[i];
if (marker != undefined) {
// if we are not ignoring given layer anyway, send
// already known ids.
// TODO, mayby send only from given BBox ?
if (layer.name == 'poi') {
// if(isMarkerInBBox())
poiIds.push(marker.id);
} else if (marker.objectType == 'GROUP') {
groupIds.push(marker.id);
} else {
stopIds.push(marker.id);
}
}
} // inner loop
} // else threshold
} // outer loop
return {
knownPoiIds: poiIds.join(','),
knownStopGroupIds: groupIds.join(','),
knownStopIds: stopIds.join(','),
layersToIgnore: layerListToIgnore.join(',')
};
}
/** fetch markers **/
// Fetch markers based on mercator projection tiles. Store the set of tiles you have already requested
// such that they are not re-requested.
this.fetchMarkers = function() {
if(!markerConnection) {
markerConnection = new Ext.data.Connection({
timeout: 8000,
autoAbort: false
});
}
var bounds = KZ.mapPanel.getMap().getBounds();
var minXTileNumber = longToTile(bounds.swLng, MERCATOR_PROJECTION_ZOOM_LEVEL);
var maxXTileNumber = longToTile(bounds.neLng, MERCATOR_PROJECTION_ZOOM_LEVEL);
var minYTileNumber = latToTile(bounds.neLat, MERCATOR_PROJECTION_ZOOM_LEVEL);
var maxYTileNumber = latToTile(bounds.swLat, MERCATOR_PROJECTION_ZOOM_LEVEL);
for (var xTileNumber = minXTileNumber; xTileNumber <= maxXTileNumber; xTileNumber++) {
for (var yTileNumber = minYTileNumber; yTileNumber <= maxYTileNumber; yTileNumber++) {
try {
if(markerTilesFetchedSet.exists('tileKey-' + xTileNumber + '-' + yTileNumber + '-at-' + KZ.mapPanel.getMap().getZoom())) {
continue; // This mercator tile area has already had its stops fetched
} else {
markerTilesFetchedSet.add('tileKey-' + xTileNumber + '-' + yTileNumber + '-at-' + KZ.mapPanel.getMap().getZoom() , 'a');
}
var tileSwLat = tileToLat(yTileNumber + 1, MERCATOR_PROJECTION_ZOOM_LEVEL);
var tileSwLng = tileToLong(xTileNumber, MERCATOR_PROJECTION_ZOOM_LEVEL);
var tileNeLat = tileToLat(yTileNumber, MERCATOR_PROJECTION_ZOOM_LEVEL);
var tileNeLng = tileToLong(xTileNumber + 1, MERCATOR_PROJECTION_ZOOM_LEVEL);
markerConnection.request({
url: "/markers/swLat/" + tileSwLat + "/swLng/" + tileSwLng + "/neLat/" + tileNeLat + "/neLng/" + tileNeLng + "/",
headers: {Accept: 'application/json'},
params: getParams(),
timeout: 8000,
scope: this,
success: function(resp, opt) {
var markerResults = KZ.util.evalToJSON( resp.responseText );
if(KZ.util.isSet(markerResults.layers)) {
KZ.layerManager.processLayerResults(markerResults.layers);
}
},
failure: function(response, opts) {
markerTilesFetchedSet.remove('tileKey-' + xTileNumber + '-' + yTileNumber + '-at-' + KZ.mapPanel.getMap().getZoom(), 'a');
KZ.log('Could not fetch markers by LayerManager. Server-side failure with status code ' + response.status);
}
});
} catch(e) {
KZ.log(e);
}
}
}
} // function fetchMarkers
/** hide markers **/
this.hideMarkers = function(layoutNameArr) {
var layers = KZ.layerManager.getLayers();
if(KZ.util.isArray(layoutNameArr) && layoutNameArr.length > 0) {
var layerMap = layers.getLayers(layoutNameArr);
Ext.each(layerMap, function(layer) {
layer.hideMarkers();
});
} else {
// hide all layers
Ext.each(layers.getAllLayers(), function(layer) {
layer.hideMarkers();
});
}
}
/** update markers **/
this.updateMarkers = function() {
var layers = KZ.layerManager.getLayers();
Ext.each(layers.getAllLayers(), function(layer) {
layer.updateMarkers();
layer.updateSearchResultMarkers();
});
};
this.getMarkerAnchorPoint = function(state) {
var point = {};
switch (state) {
case 'normal-state' :
point.y = NORMAL_STATE_Y_ANCHOR_POINT;
point.x = NORMAL_STATE_X_ANCHOR_POINT;
return point;
case 'hover-state' :
point.y = HOVER_STATE_Y_ANCHOR_POINT;
point.x = HOVER_STATE_X_ANCHOR_POINT;
return point;
case 'selected-state' :
point.y = SELECTED_STATE_Y_ANCHOR_POINT;
point.x = SELECTED_STATE_X_ANCHOR_POINT;
return point;
default :
KZ.log('Invalid state argument: ' + state);
}
}
this.restoreSelectedStateMarker = function() {
if(this.stopInformationPanelSelectedMarker != undefined) {
KZ.markerManager.alterMarkerState(this.stopInformationPanelSelectedMarker, 'normal-state', 6);
this.stopInformationPanelSelectedMarker = undefined;
}
}
this.restoreHoverStateMarker = function() {
if(hoverStateMarker != undefined) {
if(this.stopInformationPanelSelectedMarker == undefined ||
(this.stopInformationPanelSelectedMarker != undefined && this.stopInformationPanelSelectedMarker.id != hoverStateMarker.id)) {
this.alterMarkerState(hoverStateMarker, 'normal-state', 6);
hoverStateMarker = undefined;
}
}
}
this.setMarkerState = function(marker, state) {
// Restore previously highlighted marker
this.restoreHoverStateMarker();
// save currently highlighted marker
hoverStateMarker = marker;
// highlight the marker
if(this.stopInformationPanelSelectedMarker == undefined ||
this.stopInformationPanelSelectedMarker.id != marker.id) {
this.alterMarkerState(marker, state, 10);
}
}
this.alterMarkerState = function(marker, state, zIndex) {
if(marker instanceof Ext.data.Record) {
marker = marker.data;
}
var markerAnchorPoint = this.getMarkerAnchorPoint(state);
var markerPoint = KZ.mapPanel.getMap().fromLatLngToDivPixel(marker.lat, marker.lng);
var origDisplay = marker.domRef.style.display;
marker.domRef.style.display = 'none';
marker.domRef.style.top = markerPoint.y - markerAnchorPoint.y + 'px';
marker.domRef.style.left = markerPoint.x - markerAnchorPoint.x + 'px';
marker.domRef.className = marker.domRef.className.replace(MARKER_STATE_MATCHER, state);
marker.domRef.style.zIndex = zIndex;
marker.domRef.style.display = origDisplay;
}
this.markerClick = function( record, layer, callback ) {
if(! (layer instanceof KZ.AbstractLayer)) {
return;
}
if( ! layer.hasInformationPanel() ) {
(function(){
KZ.mapPanel.extComponent.fireEvent("newSearchPointEvent", record.lat, record.lng);
}).defer(300);
}
layer.markerClick(record);
if(Ext.isFunction(callback)) {
callback.call(record);
}
}
}
Ext.ns('KZ');
KZ.RoutePlotManager = function() {
var overlays = [];
var arrowMarkerContainer;
var markerElements = [];
var NORMAL_STATE_X_ANCHOR_POINT = 20;
var NORMAL_STATE_Y_ANCHOR_POINT = 20;
this.initialize = function() {
var mapContainer = KZ.mapPanel.getMap().getMarkerDivContainer();
var dh = Ext.DomHelper;
arrowMarkerContainer = dh.insertHtml('afterBegin', mapContainer, '');
}
this.drawRoute = function(){
Ext.StoreMgr.lookup('routeDirectionResultsStore').each(function(data){
Ext.each(data.data.routeGeometry, function(routeSegment){
overlays[overlays.length] = KZ.mapPanel.getMap().drawPolyline(routeSegment);
});
/*
* The following code is commented out, until telent and LBSL decide how
* we will plot arrow heads. CloudMade may add a function to do it for us,
* or we may have to perfect image overlay.
*
Ext.each(data.data.segments, function(segment){
if (segment.markers.length <=10)
return;
var marker = segment.markers[segment.markers.length-1];
var point = KZ.mapPanel.getMap().fromLatLngToDivPixel(marker.lat,
marker.lng);
var offsetTop = point.y - NORMAL_STATE_Y_ANCHOR_POINT;
var offsetLeft = point.x - NORMAL_STATE_X_ANCHOR_POINT;
if (marker.direction == undefined || marker.direction == '') {
marker.direction = 'x';
}
var dh = Ext.DomHelper;
var markerElement = dh.insertHtml('afterBegin', arrowMarkerContainer,
'');
markerElements[markerElements.length] = {'domElement': markerElement, 'lat':marker.lat, 'lng':marker.lng};
});
*/
});
}
this.clearRoute = function(){
for(var i=0; i Ext.getBody().getViewSize().height + Ext.getBody().getScroll().top) {
window.scrollTo(0, Ext.get("container").getTop());
}
}
}
// Path to the blank image must point to a valid location on your server
Ext.BLANK_IMAGE_URL = '../../js-lib/ext/resources/images/default/s.gif';
Ext.ns('KZ');
Ext.onReady(function(){
KZ.startPageManager = new KZ.StartPageManager();
KZ.historyManager = new KZ.HistoryManager();
if(location.hash == '') {
KZ.historyManager.initialize();
}
KZ.userStopsStore = new KZ.UserStopsStore();
KZ.InitialSearchPanel.show();
KZ.searchBoxPanel = new KZ.SearchBoxPanel();
KZ.searchResultsPanel = new KZ.SearchResultsPanel();
KZ.searchResultsManager = new KZ.SearchResultsManager( KZ.searchResultsPanel );
// keep in mind - results are matched to handlers defined below based on array order
// in addResultHandlers param
KZ.emptyResult = new KZ.EmptyResult();
KZ.singleStopResult = new KZ.SingleStopResult();
KZ.postcodeResult = new KZ.PostcodeResult();
KZ.stopsResult = new KZ.StopsResult();
KZ.railResult = new KZ.RailResult();
KZ.busStationsResult = new KZ.BusStationsResult();
KZ.metroStationsResult = new KZ.MetroStationsResult();
KZ.ferryResult = new KZ.FerryResult();
KZ.placesResult = new KZ.PlacesResult();
KZ.poisResult = new KZ.PoisResult();
KZ.searchResultsManager.addResultHandlers([
KZ.emptyResult,
new KZ.AmbiguousResult(),
new KZ.RouteResult(),
KZ.singleStopResult,
KZ.postcodeResult,
KZ.stopsResult,
KZ.railResult,
KZ.busStationsResult,
KZ.metroStationsResult,
KZ.ferryResult,
KZ.placesResult
]);
KZ.searchResultsManager.addResultHandlers([new KZ.PoisResult('poi', 'Points of Interest')]);
for(var i=0; i{1} '.bind(tr('search'), tr('mystops')));
} else {
KZ.searchPanel.setTitle('{0} Homepage'.bind(tr('search')));
}
// TWT-46
var appendHash = function(event, target) {
var hash = window.location.hash;
if (hash) {
// to insert hash into parameter we need to get rid of #
target.href += '?ref=' + hash.substring(1);
}
};
Ext.getBody().on('click', appendHash, this, { delegate: 'a.mystops-link' });
// TODO: The user stops panel can be enabled here.
//KZ.userStopsPanel = new KZ.UserStopsPanel(KZ.userStopsStore);
// TODO: This should be using a vbox with split layout. If there is no user stops panel, then it can use an even more simple layout
// http://www.extjs.com/forum/showthread.php?t=65982
KZ.leftColumnContainer = new Ext.Panel({
id:'left-column-container',
layout:'border',
split:true, // This shouldn't be true now we no longer have the bottom left panel. May re-enable however at a later date.
region:'west',
width: 240,
minSize: 0,
maxSize:500,
margins: '0 0 4 0',
cmargins: '0 0 0 0',
border:false,
bodyBorder:false,
//items: [KZ.searchPanel, KZ.userStopsPanel.extComponent]
items: [KZ.searchPanel]
});
KZ.markerManager = new KZ.MarkerManager();
KZ.routePlotManager = new KZ.RoutePlotManager();
//KZ.branchLabelManager = new KZ.BranchLabelManager();
KZ.tooltipManager = new KZ.TooltipManager();
KZ.poiPanel = new KZ.PoiInformationPanel();
// center to stop and show it's stop board
function stopSelectedEventHandler(stopCode) {
// (the implementation includes focusing on the stop's marker, so it must be already downloaded - this is guaranteed by the servlet that returns markers)
KZ.viewport.extComponent.fireEvent("stopSelectedEvent", {id: stopCode});
}
KZ.stopInformationPanel = new KZ.StopInformationPanel(false, true, false, undefined, false, true, KZ.poiPanel, stopSelectedEventHandler);
KZ.mapPanel = new KZ.MapPanel();
KZ.layerManager = new KZ.LayerManager();
KZ.layerManager.addLayers([
new KZ.PlacesLayer(),
new KZ.BusStopLayer(),
new KZ.BusStationLayer(),
new KZ.MetroStationLayer(),
new KZ.RailLayer(),
new KZ.FerryLayer()
]);
var poiLayer = new KZ.PoiLayer('poi', 'Points of Interest');
KZ.layerManager.addLayer(poiLayer);
for(var i=0; i