InsideTrip allows YOU to rate your trip according to each of these factors and helps you wade through the hundreds of flight options on the market today.
'
}, // end unsupportedBrowserUi
tripEvaluators : { // gives both the display names and the display order
'speed': {
'speed-stops': 'Number of Stops',
'speed-duration': 'Travel Duration',
'speed-ontime': 'On-time Stats',
'speed-wait': 'Security Wait'
},
'comfort': {
'comfort-legroom': 'Legroom',
'comfort-aircraft-type': 'Aircraft Type',
'comfort-aircraft-age': 'Aircraft Age',
'comfort-filled': 'Load Factor'
},
'ease': {
'ease-connection': 'Connect Time',
'ease-routing': 'Routing Quality',
'ease-lost-bags': 'Lost Bags Rank',
'ease-gate': 'Gate Location'
}
}, // end tripEvaluators
tripGantt : function(chart, legs, highDur, airportNames) {
var ganttMaxWidthPx = 190;
var ganttMinWidthPx = 80;
// scan legs for times and durations
var depTime = [], arrTime = [], dur = [], cnxDur = [];
var totalDur = 0;
for (var i = 0, len = legs.length; i < len; i++) {
var leg = legs[i];
depTime[i] = parseInt(leg.time_dep_str.substr(0,2), 10) * 60 + parseInt(leg.time_dep_str.substr(3,2), 10);
arrTime[i] = parseInt(leg.time_arr_str.substr(0,2), 10) * 60 + parseInt(leg.time_arr_str.substr(3,2), 10);
totalDur += dur[i] = leg.dur;
if (i > 0) {
cnxDur[i] = depTime[i] - arrTime[i - 1];
if (cnxDur[i] < 0) { cnxDur[i] += 1440; } // overnight layover
totalDur += cnxDur[i];
}
}
// build segments for legs and connections
var wrapper = Ext.DomHelper.append(chart, { tag: 'div', cls: 'gantt-wrapper' }, true);
var totalWidth = totalDur / highDur * ganttMaxWidthPx;
var segment, width, tooltip;
var sumWidth = 0; // because of rounding sumWidth might not exactly equal totalWidth
for (i = 0; i < len; i++) {
if (cnxDur[i]) {
sumWidth += (width = Math.round( cnxDur[i] / totalDur * totalWidth ));
segment = Ext.DomHelper.append(wrapper, { tag: 'div', cls: 'gantt-segment-connection', style: 'width:' + width + 'px' }, true);
Ext.DomHelper.append(segment, { tag: 'span', cls: 'gantt-connection-airport', html: '' });
Ext.DomHelper.append(segment, { tag: 'span', cls: 'gantt-connection-time', html: InsideTrip.template.minutesToDurationShort(cnxDur[i]) });
var cnxPort = legs[i-1].port_arr;
Ext.QuickTips.register({
text:
InsideTrip.template.minutesToDurationLong(cnxDur[i]) + ' connection in ' +
(airportNames ? ( airportNames[cnxPort] + ' ('+cnxPort+')' ) : cnxPort)
,
target: segment
});
}
sumWidth += (width = Math.round( dur[i] / totalDur * totalWidth ));
segment = Ext.DomHelper.append(wrapper, { tag: 'div', cls: 'gantt-segment', style: 'width:' + width + 'px' }, true);
var depPort = legs[i].port_dep, arrPort = legs[i].port_arr;
Ext.QuickTips.register({
text:
'Depart ' + (airportNames ? ( airportNames[depPort] + ' ('+depPort+')' ) : depPort) + ' at ' + InsideTrip.template.minutesToTime(depTime[i]) + ' ' +
'Arrive ' + (airportNames ? ( airportNames[arrPort] + ' ('+arrPort+')' ) : arrPort) + ' at ' + InsideTrip.template.minutesToTime(arrTime[i])
,
target: segment
});
if (i == 0) { // first leg
Ext.DomHelper.append(segment, { tag: 'span', cls: 'gantt-depart-airport', html: depPort });
Ext.DomHelper.append(segment, { tag: 'span', cls: 'gantt-depart-time', html: InsideTrip.template.minutesToTime(depTime[i]) });
}
if (i == len - 1) { // last leg
Ext.DomHelper.append(segment, { tag: 'span', cls: 'gantt-arrive-airport', html: arrPort });
Ext.DomHelper.append(segment, { tag: 'span', cls: 'gantt-arrive-time', html: InsideTrip.template.minutesToTime(arrTime[i]) });
}
}
// set horizontal chart boundaries
var wrapperWidth = sumWidth;
if (wrapperWidth < ganttMinWidthPx) { wrapperWidth = ganttMinWidthPx; }
wrapper.setStyle('width', wrapperWidth+'px');
return chart;
}, // end tripGantt()
minutesToTime : function(minutes) {
var h = Math.floor( minutes / 60 );
var m = minutes % 60;
var ampm = ((h % 24) < 12) ? 'a' : 'p';
h = h % 12 || 12;
return h + ':' + (m.toString().length==1 ? '0' : '') + m + ampm;
},
minutesToTimeLong : function(minutes) {
var shortForm = InsideTrip.template.minutesToTime(minutes);
return shortForm.replace('a', ' am').replace('p', ' pm');
},
minutesToDuration : function(minutes) {
var h = Math.floor( minutes / 60 );
var m = minutes % 60;
return (h ? (h + 'h ') : '') + m + 'm';
},
minutesToDurationLong : function(minutes) {
var shortForm = InsideTrip.template.minutesToDuration(minutes);
return shortForm.replace('h', ' hour, ').replace('m', ' minute');
// var h = Math.floor( minutes / 60 );
// var m = minutes % 60;
// return (
// (h ? (h + ((h > 1) ? ' hours, ' : ' hour, ')) : '') +
// (m ? (m + ((m > 1) ? ' minutes' : ' minute')) : '')
// );
},
minutesToDurationShort : function(minutes) {
var h = Math.floor( minutes / 60 );
var m = minutes % 60;
return (h ? (h + 'h') : '') + (m ? ((m.toString().length==1 ? '0' : '') + m + "'") : '');
}
}, // end template
util : {
checksum : function(s, bits) {
bits = bits || 16;
var checksum = 0;
for (var i = 0, len = s.length; i < len; i++) {
checksum += s.charCodeAt(i);
}
return (checksum % (1<
'
);
// register application with History Manager
Ext.ux.History.register(
this.application, // application ("module") name
'', // initial application state
this.activateState.createDelegate(this) // state-change handler (in application context)
);
// set History Manager to initialize application state when ready
Ext.ux.History.on(
'load',
this.initState,
this // execute in application context
);
// app init sequence
this.initApplication.defer(200, this);
};
Ext.extend(InsideTrip.Application, Object, {
application : null,
param : null,
activated : false,
getQueryString : function(url) {
// returns URL (before the hash) deserialized
var href = url || top.location.href;
if (href) {
var matches =
href.indexOf('#') > 0 ?
/\:\/{1,2}.+?\/(.+?)\#/.exec(href) :
/\:\/{1,2}.+?\/(.+)/.exec(href)
;
var query = '';
if (matches) {
var path = matches[1];
var i = path.indexOf('?');
if (i >= 0) {
query = path.substr(i+1);
path = path.substr(0, i);
var namesAndValues = query.split('/');
var namesAndValuesLength = namesAndValues.length;
if (namesAndValuesLength > 0) {
var pairs = [];
if (!namesAndValues[0]) { namesAndValues.shift(); }
for (i = 0; i < namesAndValuesLength; i += 2) {
pairs.push( namesAndValues[i]+'='+namesAndValues[i+1] );
}
query = pairs.join('&');
}
}
}
return query;
}
return '';
}, // end getQueryString()
cache : function(el) {
if (!el || Ext.type(el)=='string') {
// cache all elements matching either given selector or .autocache
var selector = el || '.autocache';
var els = Ext.query(selector);
for (var i = 0, len = els.length; i < len; i++) {
this.cache( els[i] );
}
} else {
// cache specific element given
el = Ext.get(el);
if (el) { this.domCache[el.id] = el; }
// remove element from DOM
if (el && el.dom.parentNode) {
el.dom.parentNode.removeChild(el.dom);
}
}
}, // end cache()
retrieve : function(elId) {
// look for element of given ID in the DOM and then the DOM cache and return it
return Ext.get(elId) || this.domCache[elId];
}, // end retrieve()
initApplication : function() {
// init trip query results collection
this.trips = []; // to contain trip query results by numerical index
this.tripQueryIndex = null; // to contain the numerical index of the currently-displayed trip query
this.tripQueryStrings = {}; // to contain numerical indeces, indexed by trip query string
this.nextTripQueryIndex = 1;
// initialize tooltips
Ext.QuickTips.init();
var tips = InsideTrip.config.tooltips;
for (i = 0, len = tips.length; i < len; i++) {
Ext.QuickTips.register( tips[i] );
}
// init trip query form
this.initTripQueryForm( Ext.get('trip-query') );
// init recent searches
this.initRecentSearches();
// activate state if it hasn't been yet
if (this.onFirstActivation !== null) { this.activateState(); }
}, // end initApplication
addTripRecent : function(params) {
var searches = this.recentSearches;
var tripQueryForm = this.tripQueryForm;
var thisSearch = tripQueryForm.translateNames(params);
if (!thisSearch['depart-airport']) { return; }
// are these airports and dates already in the recent searches?
for (var i = 0, len = searches.length; i < len ; i++) {
var pastSearch = tripQueryForm.translateNames( searches[i] );
if (
pastSearch['depart-airport'] == thisSearch['depart-airport'] &&
pastSearch['depart-date'] == thisSearch['depart-date'] &&
pastSearch['arrive-airport'] == thisSearch['arrive-airport'] &&
(
(pastSearch['journey-type-oneway'] && thisSearch['journey-type-oneway']) ||
(
pastSearch['journey-type-roundtrip'] && thisSearch['journey-type-roundtrip'] &&
pastSearch['return-date'] == thisSearch['return-date']
)
)
) {
// delete the old trip entry, to be replaced with the new one given
searches.splice(i, 1);
break;
}
}
// add given search to top of recent-searches array
searches.unshift(params);
// keep list trimmed to maximum number
var maxSearches = InsideTrip.config.recentSearchesMax;
if (searches.length > maxSearches) { searches.pop(); }
}, // end addTripRecent()
initRecentSearches : function() {
// start recent searches with those given by back end
this.recentSearches = [];
var recentList = Ext.get('trip-recent-list');
if (!recentList) { return; }
var backendSearches = recentList.query('li');
for (var i = 0, len = backendSearches.length; i < len; i++) {
var li = Ext.get( backendSearches[i] );
this.addTripRecent( Ext.urlDecode(
this.getQueryString( li.child('a').dom.getAttribute('href') )
) );
li.remove();
}
}, // end initRecentSearches()
initTripQueryForm : function(formEl) {
if (!formEl) { return; }
// create Form object to represent the form in DOM
var tripQueryForm = this.tripQueryForm = new InsideTrip.Form(
formEl,
{
defaultValues: InsideTrip.config.defaults.tripQueryForm,
url: InsideTrip.config.url.tripSearch,
timeout: InsideTrip.config.timeout.tripSearch,
method: 'POST',
baseParams: {
'outputType': 'json',
'maxCnx': InsideTrip.config.input.maxConnections
},
onResponse: this.handleTripQueryResponse.createDelegate(this) // execute handler in the application's, not the form's, scope
}
);
// populate Form object with field objects and tie them into input elements on the page
var depAirInputID = Ext.get('input-depart-airport').dom.name;
var arrAirInputID = Ext.get('input-arrive-airport').dom.name;
var departDate, returnDate, departTime, returnTime;
tripQueryForm.add(
new Ext.form.Radio({id: 'journey-type-roundtrip'}).applyTo('input-journey-type-roundtrip'),
new Ext.form.Radio({id: 'journey-type-oneway'}).applyTo('input-journey-type-oneway'),
new InsideTrip.AirportComboBox({id: 'depart-airport', hiddenName: depAirInputID, travelPointName: 'departure airport', app: this}).applyTo('input-depart-airport'),
new Ext.form.Checkbox({id: 'depart-nearby'}).applyTo('input-depart-nearby'),
new InsideTrip.AirportComboBox({id: 'arrive-airport', hiddenName: arrAirInputID, travelPointName: 'destination airport', app: this}).applyTo('input-arrive-airport'),
new Ext.form.Checkbox({id: 'arrive-nearby'}).applyTo('input-arrive-nearby'),
departDate = new Ext.form.TextField(InsideTrip.config.input.dateField).applyTo('input-depart-date'),
departTime = new Ext.form.TextField({id: 'depart-time'}).applyTo('input-depart-time'),
returnDate = new Ext.form.TextField(InsideTrip.config.input.dateField).applyTo('input-return-date'),
returnTime = new Ext.form.TextField({id: 'return-time'}).applyTo('input-return-time'),
new Ext.form.TextField({id: 'travelers'}).applyTo('input-travelers'),
// new Ext.form.TextField({id: 'cabin'}).applyTo('input-cabin')
new Ext.form.Field({id: 'cabin'}).applyTo('input-cabin')
);
departDate.id = 'depart-date';
returnDate.id = 'return-date';
// hardwire depart/return time names in field objects and blank them in the dom
// (rather than submitting with the trip query, these fields become results filter settings)
var departTimeName = departTime.getName();
departTime.getName = function() { return departTimeName; }
departTime.el.dom.name = '';
var returnTimeName = returnTime.getName();
returnTime.getName = function() { return returnTimeName; }
returnTime.el.dom.name = '';
// create DatePickers and TripDateManager for departure and return dates
tripQueryForm.tripDateManager = new InsideTrip.TripDateManager({
departText: departDate,
returnText: returnDate,
roundTripEntry: tripQueryForm.findField('journey-type-roundtrip')
});
// set journey-type switch to turn relevant steps off and on
var onJourneyChange = function() {
var journeyType = Ext.get('input-journey-type-oneway').dom.checked ? 'oneway' : 'roundtrip';
if (journeyType != this.journeyType) {
// alter live form
this.journeyType = journeyType;
if ('oneway' == journeyType) {
Ext.get('step-return-when').hide();
this.findField('return-date').disable();
} else if ('roundtrip' == journeyType) {
this.findField('return-date').enable();
Ext.get('step-return-when').show();
}
}
}; // pass reference to this application object to the handler
if (Ext.isSafari) {
Ext.get('input-journey-type-roundtrip').on('click', onJourneyChange, tripQueryForm, { buffer: 150 });
Ext.get('input-journey-type-oneway').on('click', onJourneyChange, tripQueryForm, { buffer: 150 });
} else {
Ext.get('input-journey-type-roundtrip').on('focus', onJourneyChange, tripQueryForm, { buffer: 150 });
Ext.get('input-journey-type-oneway').on('focus', onJourneyChange, tripQueryForm, { buffer: 150 });
}
tripQueryForm.on('setValues', onJourneyChange, tripQueryForm);
// make sure comboboxes are collapsed before executing a form action (e.g. submission)
tripQueryForm.on(
'beforeaction',
function(form, action) {
var departCombobox = this.findField('depart-airport');
var arriveCombobox = this.findField('arrive-airport');
departCombobox.collapse();
arriveCombobox.collapse();
return departCombobox.queryComplete(this) && arriveCombobox.queryComplete(this);
}
);
// call application state handler on form submission
tripQueryForm.on(
'beforeaction',
this.handleTripQueryAction,
this // execute in application scope
);
}, // end initTripQueryForm()
initTripQuality : function(formEl) {
// create Form object to represent the form in DOM
var tripQualityForm = this.tripQualityForm = new InsideTrip.Form(
formEl,
{
defaultValues: { 'tripQuality': 0 }
}
);
// add in filter elements/widgets
var tripQuality;
tripQualityForm.add(
tripQuality = new InsideTrip.Slider('field-tripQuality', {
id: 'tripQuality', hiddenName: 'tripQuality',
readout: Ext.get('tripQuality-low'),
readoutContainer: Ext.get('tripQuality-low').findParent('.filter-readout', 2, true)
})
);
tripQuality.el.addClass('autocache');
// attach listener to handle filter form change
tripQuality.on(
'change',
function() {
Ext.apply(this.param, this.tripQualityForm.getValues());
this.param.page = 1;
this.registerState();
},
this
);
}, // end initTripQuality
initTripFilters : function(formEl) {
// create Form object to represent the form in DOM
var tripFilters = this.tripFilters = new InsideTrip.Form(
formEl,
{
defaultValues: {
'time-depart': '0-1440',
'time-return': '0-1440',
'connection': '0-2880'
}
}
);
// prepare slider readout functions
var refreshTimeRangeReadout = function(low, high) {
this.readout.low.update( InsideTrip.template.minutesToTime(low) );
var highTime = InsideTrip.template.minutesToTime(high);
this.readout.high.update( '12:00a'==highTime ? '11:59p' : highTime );
};
var refreshDurationRangeReadout = function(low, high) {
this.readout.low.update( InsideTrip.template.minutesToDuration(low) );
this.readout.high.update( InsideTrip.template.minutesToDuration(high) );
};
// add in filter elements/widgets
var tripQuality, timeDepart, timeReturn, connection;
tripFilters.add(
new InsideTrip.Checkbox({id: 'stops-0'}).applyTo('input-stops-0'),
new InsideTrip.Checkbox({id: 'stops-1'}).applyTo('input-stops-1'),
new InsideTrip.Checkbox({id: 'stops-2'}).applyTo('input-stops-2'),
timeDepart = new InsideTrip.DoubleSlider('field-time-depart', {
id: 'time-depart', hiddenName: this.tripQueryForm.findField('depart-time').getName() || 'timeDep',
min: 0, max: 1440, granularity: 30, start: '0-1440',
readout: { low: Ext.get('time-depart-low'), high: Ext.get('time-depart-high') },
readoutContainer: Ext.get('time-depart-low').findParent('.filter-readout', 2, true),
refreshReadout: refreshTimeRangeReadout
}),
timeReturn = new InsideTrip.DoubleSlider('field-time-return', {
id: 'time-return', hiddenName: this.tripQueryForm.findField('return-time').getName() || 'timeRtn',
min: 0, max: 1440, granularity: 30, start: '0-1440',
readout: { low: Ext.get('time-return-low'), high: Ext.get('time-return-high') },
readoutContainer: Ext.get('time-return-low').findParent('.filter-readout', 2, true),
refreshReadout: refreshTimeRangeReadout
}),
connection = new InsideTrip.DoubleSlider('field-connection', {
id: 'connection', hiddenName: 'connect',
min: 10, max: 1440, granularity: 10, start: '0-1440',
readout: { low: Ext.get('connection-low'), high: Ext.get('connection-high') },
readoutContainer: Ext.get('connection-low').findParent('.filter-readout', 2, true),
refreshReadout: refreshDurationRangeReadout
})
);
timeDepart.el.addClass('autocache');
timeReturn.el.addClass('autocache');
connection.el.addClass('autocache');
// attach listener to handle filter form change
tripFilters.on(
'change',
function() {
Ext.apply(this.param, this.tripFilters.getValues());
this.param.page = 1;
this.registerState();
},
this
);
}, // end initTripFilters
initTripFlags : function(formEl) {
// create Form object to represent the form in DOM
var tripFlags = this.tripFlags = new InsideTrip.FlagForm(formEl);
tripFlags.add(
new InsideTrip.Checkbox({category: 'airports', id: 'same-airports'}).applyTo('input-airports')
);
// create caches for airport filters
tripFlags.airports = {
depart: [],
connect: [],
arrive: []
};
// create cache and methods for airline filters
tripFlags.airlines = [];
tripFlags.selectAirlinesAll = function(e) {
var airlines = tripFlags.airlines;
var changed = false;
var a;
for (var i = 0, len = airlines.length; i < len; i++) {
a = tripFlags.findField('airline-' + airlines[i]);
if (a) {
changed = changed || !a.getValue();
a.setValue(true);
}
}
if (changed) { tripFlags.fireEvent('change'); }
if (e) { e.stopEvent(); return false; }
};
tripFlags.selectAirlinesOnly = function(only, e) {
var airlines = tripFlags.airlines;
var changed = false;
var a, newValue;
for (var i = 0, len = airlines.length; i < len; i++) {
a = tripFlags.findField('airline-' + airlines[i]);
if (a) {
newValue = (airlines[i]==only);
changed = changed || (newValue == !a.getValue());
a.setValue(newValue);
}
}
if (changed) { tripFlags.fireEvent('change'); }
if (e) { e.stopEvent(); return false; }
};
Ext.get('filter-airlines-selectall').on('click', tripFlags.selectAirlinesAll);
// attach listener to handle filter form change
tripFlags.on(
'change',
function() {
Ext.apply(this.param, this.tripFlags.getFlags(true));
this.param.page = 1;
this.registerState();
},
this
);
}, // end initTripFlags
initTripDashboard : function(formEl) {
// create Form object to represent the form in DOM
var tripDashboard = this.tripDashboard = new InsideTrip.FlagForm(
formEl,
{
defaultValues: InsideTrip.config.defaults.tripDashboard
}
);
// add in checkboxes
tripDashboard.add(
new InsideTrip.Checkbox({category: 'speed', id: 'speed-stops'} ).applyTo('input-speed-stops'),
new InsideTrip.Checkbox({category: 'speed', id: 'speed-duration'} ).applyTo('input-speed-duration'),
new InsideTrip.Checkbox({category: 'speed', id: 'speed-ontime'} ).applyTo('input-speed-ontime'),
new InsideTrip.Checkbox({category: 'speed', id: 'speed-wait'} ).applyTo('input-speed-wait'),
new InsideTrip.Checkbox({category: 'comfort', id: 'comfort-legroom'} ).applyTo('input-comfort-legroom'),
new InsideTrip.Checkbox({category: 'comfort', id: 'comfort-aircraft-type'}).applyTo('input-comfort-aircraft-type'),
new InsideTrip.Checkbox({category: 'comfort', id: 'comfort-aircraft-age'} ).applyTo('input-comfort-aircraft-age'),
new InsideTrip.Checkbox({category: 'comfort', id: 'comfort-filled'} ).applyTo('input-comfort-filled'),
new InsideTrip.Checkbox({category: 'ease', id: 'ease-connection'} ).applyTo('input-ease-connection'),
new InsideTrip.Checkbox({category: 'ease', id: 'ease-routing'} ).applyTo('input-ease-routing'),
new InsideTrip.Checkbox({category: 'ease', id: 'ease-lost-bags'} ).applyTo('input-ease-lost-bags'),
new InsideTrip.Checkbox({category: 'ease', id: 'ease-gate'} ).applyTo('input-ease-gate')
);
// wire "clear" and "select" links into flagAll() and unflagAll() functions on dashboard object
formEl.child('.dashboard-clear-all a').on(
'click',
function(e) {
e.stopEvent();
this.unflagAll();
},
tripDashboard
);
formEl.child('.dashboard-select-all a').on(
'click',
function(e) {
e.stopEvent();
this.flagAll();
},
tripDashboard
);
// activate "recalculate" link
Ext.get('trip-dashboard-recalculate').on(
'click',
function(e) {
e.stopEvent();
Ext.apply(this.param, this.tripDashboard.getFlags());
this.param.page = 1;
this.registerState();
},
this // application context
);
// activate hide/show link
Ext.get('panel-trip-dashboard-head-link').on(
'click',
function(e) {
e.stopEvent();
var link = Ext.get(e.target).findParent('a', 2, true);
if ( link.hasClass('x-dashboard-show') ) {
Ext.get('trip-dashboard').setStyle('display', 'block');
link.replaceClass('x-dashboard-show', 'x-dashboard-hide');
} else {
Ext.get('trip-dashboard').setStyle('display', 'none');
link.replaceClass('x-dashboard-hide', 'x-dashboard-show');
}
}
);
}, // end initTripDashboard
conscribeTripFilters : function() {
var filters = this.tripFilters;
var flags = this.tripFlags;
var tripQueryIndex = this.tripQueryIndex;
var tripSet = this.trips[tripQueryIndex];
// are the filters already conscribed for this trip set?
if( filters.el.hasClass('trips-' + tripQueryIndex) ) { // yes
return;
}
// update filters form class
filters.el.replaceClassIndexed('trips-', tripQueryIndex);
// roundtrip or one-way trip set?
var roundtrip = (typeof tripSet.returnTimeHigh == 'number');
// which number-of-stops options are relevant?
if (tripSet.stopsLow > 0) {
filters.findField('stops-0').disable();
filters.defaultValues['stops-0'] = false;
} else {
filters.findField('stops-0').enable();
filters.defaultValues['stops-0'] = true;
}
if (tripSet.stopsLow > 1 || tripSet.stopsHigh < 1) {
filters.findField('stops-1').disable();
filters.defaultValues['stops-1'] = false;
} else {
filters.findField('stops-1').enable();
filters.defaultValues['stops-1'] = true;
}
if (tripSet.stopsHigh < 2) {
filters.findField('stops-2').disable();
filters.defaultValues['stops-2'] = false;
} else {
filters.findField('stops-2').enable();
filters.defaultValues['stops-2'] = true;
}
// set departure day and time min and max
var timeDepart = filters.findField('time-depart');
var granularity = timeDepart.granularity;
var min = Math.floor(tripSet.departTimeLow / granularity) * granularity;
var max = Math.ceil(tripSet.departTimeHigh / granularity) * granularity;
timeDepart.setRange(min, max);
// one-way or roundtrip?
if (roundtrip) { // roundtrip
// show return time
var returnContainer = Ext.get('filter-time-return-container');
if (returnContainer.getStyle('display') != 'block') {
Ext.get('filter-time-return-container').setStyle('display', 'block');
Ext.get('field-time-return-widget').setStyle('display', 'block');
// realign connection time widget
filters.resynchSliders();
}
// set return time min and max
var timeReturn = filters.findField('time-return');
var granularity = timeReturn.granularity;
var min = Math.floor(tripSet.returnTimeLow / granularity) * granularity;
var max = Math.ceil(tripSet.returnTimeHigh / granularity) * granularity;
timeReturn.setRange(min, max);
} else { // one-way
// hide return time
var returnContainer = Ext.get('filter-time-return-container');
if (returnContainer.getStyle('display') != 'none') {
Ext.get('filter-time-return-container').setStyle('display', 'none');
Ext.get('field-time-return-widget').setStyle('display', 'none');
// realign connection time widget
filters.resynchSliders();
}
}
// set connection time min and max
var connection = filters.findField('connection');
var granularity = connection.granularity;
var min = Math.floor(tripSet.connectionLow / granularity) * granularity;
var max = Math.ceil(tripSet.connectionHigh / granularity) * granularity;
connection.setRange(min, max);
// can leave/return same airports option be turned off?
if (tripSet.airports.arrive.length==1 && tripSet.airports.depart.length==1) {
flags.findField('same-airports').disable();
flags.defaultValues['same-airports'] = true;
} else {
flags.findField('same-airports').enable();
flags.defaultValues['same-airports'] = false;
}
// insert departure and arrival/return city names
Ext.get('filter-airports-depart-city').update('Departure');
Ext.get('filter-airports-arrive-city').update(roundtrip ? 'Arrival/Return' : 'Arrival');
// clear out any old airports
var airports = flags.airports;
var apCode, apField, apSet, apEl;
for (var apSetName in airports) {
apSet = airports[apSetName];
while (apSet.length > 0) {
apCode = apSet.pop();
// remove from filters form
apField = flags.findField('airport-' + apSetName + '-' + apCode);
if (apField) { flags.remove(apField); }
// // remove from document and cache
// apEl = Ext.get('field-filter-airport-' + apSetName + '-' + apCode);
// if (apEl) { this.cache(apEl); }
// remove from document
apEl = Ext.get('field-filter-airport-' + apSetName + '-' + apCode);
if (apEl) { apEl.remove(); }
}
}
// clear out any old airlines
var airlines = flags.airlines;
var alCode, alField, alEl;
while (airlines.length > 0) {
alCode = airlines.pop();
// remove from flags form
alField = flags.findField('airline-' + alCode);
if (alField) { flags.remove(alField); }
// remove from document and cache
alEl = Ext.get('field-filter-airline-' + alCode);
if (alEl) { this.cache(alEl); }
}
// add airport checkbox/entry elements
var tripSetPorts = tripSet.airports;
var apNames = tripSet.airportNames;
var apSort = function(a, b) {
return apNames[a] > apNames[b] ? 1 : -1;
}
var apTemplate = new Ext.Template( InsideTrip.template.filterAirport );
var apSet, apCode, container, apEl, apId;
for (var apSetName in airports) {
apSet = tripSetPorts[apSetName];
apSet.sort(apSort);
container = Ext.get('filter-airports-' + apSetName + '-container');
for (var i = 0, len = apSet.length; i < len; i++) {
apCode = apSet[i];
apId = 'airport-' + apSetName + '-' + apCode;
apEl = this.retrieve('field-filter-' + apId);
if (apEl) {
apEl.appendTo(container);
} else {
apTemplate.append(container, { id: apId, airportCode: apCode, airportName: apNames[apCode] });
}
airports[apSetName].push(apCode);
flags.add(
new InsideTrip.Checkbox({ id: apId, category: 'airports-' + apSetName }).applyTo('input-filter-' + apId)
);
}
}
// add airline checkbox/entry elements to DOM and to filters form
airlines = [];
var airlineNames = tripSet.airlines;
for (alCode in airlineNames) { airlines.push(alCode); }
airlines.sort(
function(a, b) {
return a=='multiple' ? -1 : (
b=='multiple' ? 1 : (
airlineNames[a] > airlineNames[b] ? 1 : -1
)
);
}
);
var airlineTemplate = new Ext.Template( InsideTrip.template.filterAirline );
var airlinesContainer = Ext.get('filter-airlines-container');
for (i = 0, len = airlines.length; i < len; i++) {
alCode = airlines[i];
airlineTemplate.append(airlinesContainer, { airlineCode: alCode, airlineName: airlineNames[alCode] });
flags.add(
new InsideTrip.Checkbox({ category: 'airlines', id: 'airline-' + alCode }).applyTo('input-filter-airline-' + alCode)
);
Ext.get('field-filter-airline-' + alCode).child('a').on(
'click',
flags.selectAirlinesOnly.createDelegate(this, [ alCode ], 0)
);
}
flags.airlines = airlines;
}, // end conscribeTripFilters
conscribeTripQuality : function() {
var form = this.tripQualityForm;
var trips = this.trips[ this.tripQueryIndex ];
// set TripQuality max
Ext.get('tripQuality-high').update( trips.tripQualityHigh );
// set TripQuality min
form.findField('tripQuality').setRange(trips.tripQualityLow, trips.tripQualityHigh);
}, // end conscribeTripQuality
snapTripQueryTimeRanges : function( params ) {
// first, make sure we have the time range options
var rangeOptions = this.timeRangeOptions;
if (typeof rangeOptions != 'object') {
// create collection of time range options from trip query form in DOM
rangeOptions = this.timeRangeOptions = [];
var timeSelectOptions = this.tripQueryForm.findField('depart-time').getEl().dom.options;
for (var i = 0, len = timeSelectOptions.length; i < len; i++) {
rangeOptions.push( timeSelectOptions[i].value );
}
}
if (typeof rangeOptions[0] != 'string') {
// no range options were found, so return input values unchanged
return params;
}
// copy params and snap time-range values if it has them
var newParams = this.tripQueryForm.translateNames( params );
var timeRangeFieldNames = ['depart-time', 'return-time'];
for (var i = 0, len = timeRangeFieldNames.length; i < len; i++) {
var fieldName = timeRangeFieldNames[i];
var fieldValue = newParams[fieldName];
var valueMatch = false;
if (typeof fieldValue == 'string') {
for (var j = 0, len = rangeOptions.length; j < len; j++) {
if (rangeOptions[j] == fieldValue) {
valueMatch = true;
break;
}
}
if (!valueMatch) { newParams[fieldName] = rangeOptions[0]; }
}
}
return newParams;
}, // end snapTimeRange
handleLogoClick : function(e) {
e.stopEvent();
this.registerState('');
}, // end handleLogoClick
handleRecentLinkClick : function(e) {
e.stopEvent();
var href = Ext.get(e.target).findParent('a').getAttribute('href');
var queryStringStart = href.indexOf('#');
if (queryStringStart < 0) { queryStringStart = 0, href = '#' + href; }
this.registerState( href.substr(queryStringStart + 1) );
}, // end handleRecentLinkClick
handleTripQueryAction : function(form, action) {
//alert('action.formQueryString = '+action.formQueryString);
if ('submit'==action.type) {
if ('client'==action.failureType || !form.isValid()) {
//TODO: handle validation error
//TODO: pop up context errors in the form
//console.log('validation error');
} else {
// form validated, last step before submission to back end
var formQueryString = form.getQueryString();
action.formQueryString = formQueryString; // (preserving this because the state of the form itself may change)
var needToSubmit;
// how was this submit action initiated?
if (!action.options.stateActivated) { // this submit action was initiated directly by the user, and has not yet passed through activateState()
// extract depart and return times (which are set in the query form, but not included in the actual query)
var depTimeField = form.findField('depart-time');
var filters = '&' + depTimeField.getName() + '=' + depTimeField.getValue();
var roundtrip = form.findField('journey-type-roundtrip').getValue();
if (roundtrip) {
var rtnTimeField = form.findField('return-time');
filters += '&' + rtnTimeField.getName() + '=' + rtnTimeField.getValue();
}
// is the submitted trip query the same as currently displayed?
if (
( this.tripQueryStrings[ formQueryString ] === this.tripQueryIndex ) && // check the base trip search
( this.param[ depTimeField.getName() ] == depTimeField.getValue() ) && // and check the depart/return times
( !roundtrip || (this.param[ rtnTimeField.getName() ] == rtnTimeField.getValue()) ) // (filters that can be set from the trip query form)
) {
//TODO: behavior for attempt to resubmit current query
} else {
// register start ui state immediately before submit, so user can return easily
if ('start'==this.param.ui) {
Ext.apply(this.param, Ext.urlDecode(formQueryString));
//TODO: apply other forms
this.registerState();
}
// don't use cached results for manual form submission, so delete them if we have them
this.deleteTripsByQueryString(formQueryString);
}
// open a new state for results of this query
this.registerState( 'ui=results&' + formQueryString + filters );
// cancel submission (activateState() will take care of this later), but give the query string an index
needToSubmit = false;
} else { // this submit action is coming from activateState()
// has this trip query been submitted by this application instance before?
var tripQueryIndex = this.tripQueryStrings[formQueryString];
if (this.trips[tripQueryIndex] === undefined) { // no, it hasn't
// prepare to submit this trip query
needToSubmit = true;
this.trips[tripQueryIndex] = null; // (nothing to put here yet, but null is not undefined!)
}
}
// cancel submission if unnecessary
return needToSubmit;
} // end if submitted form validates
} // end if action is 'submit'
}, // end handleTripQueryAction()
handleTripQueryResponse : function(form, action) {
var json = action.result;
var tripQueryIndex = this.tripQueryStrings[ action.formQueryString ];
// is this response for the trip query now displayed?
var displayed = ('results'==this.param.ui && tripQueryIndex===this.tripQueryIndex);
// check for errors
if (!json || !json.success) {
// revoke the trip query index for this query string, because we have nothing to store there
delete this.tripQueryStrings[ action.formQueryString ];
// return to start ui with error message (unless the user has moved on somewhere else)
if (displayed) {
this.stateDirective = {
err: (
json ?
(
( json.attributes && InsideTrip.config.orbitzErrorMessages[json.attributes.errorcode] ) ?
InsideTrip.config.orbitzErrorMessages[json.attributes.errorcode](json.attributes)
: (
(json.attributes && json.attributes.errormessage) ? json.attributes.errormessage : json.status
)
)
: (action.response.statusText + "\nPlease try again.")
)
};
this.registerState( action.formQueryString );
}
return;
}
// parse and store results
var drydock = Ext.get('drydock');
var results = new InsideTrip.TripStore({
data: json,
queryString: action.formQueryString,
domContainer: drydock,
domIdRoot: 'trip-' + tripQueryIndex + '-',
clickHandler: this.handleTripClick.createDelegate(this)
});
if (results) {
// load into trips collection
this.trips[tripQueryIndex] = results;
// cache new elements from the drydock
if (drydock) { this.cache('#drydock .trip-result'); }
// re-activate state, now with results ready
if (displayed) { this.activateState(this.state); }
}
}, // end handleTripQueryResponse()
handleTripSortClick : function(e) {
var target = Ext.get(e.target).findParent('a', 2, true);
e.stopEvent();
// get clicked sort column name
var sortCol = target.id.split('trip-sort-');
sortCol = sortCol[1];
if (!sortCol) { return false; }
// ignore clicks on disabled links
if (target.hasClass('x-sort-disabled')) { return false; }
// determine new sort direction
var sortDir;
if ( target.hasClass('x-sort-ASC') ) { // toggling from ASC
sortDir = 'DESC';
} else if ( target.hasClass('x-sort-DESC') ) { // toggling from DESC
sortDir = 'ASC';
} else { // switching to new sort column, so use default direction for the column
if (sortCol=='score') { // score defaults to DESC
sortDir = 'DESC';
} else { // all others default to ASC
sortDir = 'ASC';
}
}
// translate column name to param format
var tripFieldKeys = InsideTrip.config.data.tripFieldKeys;
for (var key in tripFieldKeys) {
if (tripFieldKeys[key] == sortCol) {
sortCol = key;
break;
}
}
// set new sort parameters and register new state
this.param.sort = sortCol;
this.param.dir = sortDir;
this.param.page = 1;
this.registerState();
return false;
}, // end handleTripSortClick
handleTripClick : function(e) {
var target = Ext.get(e.target);
var result = target.findParent('.trip-result');
var tripId = result.id.split('-')[2];
var link;
e.stopEvent();
if ( link = target.findParent('.trip-result-purchase') ) { // purchase link
// show interstitial ui while browser follows link
this.stateDirective = {
ui: 'interstitial-purchase',
purchaseUrl: target.findParent('a').href
};
this.registerState();
return false;
}
var detailsOpened = target.findParent('.trip-result-with-details') ? true : false;
var openDetails = false;
var switchToCategory = '';
if ( target.findParent('.trip-result-breakdown') ) { // category score link
// details panel needs to be open
openDetails = true;
// show selected category
for (var cat in {speed:1, comfort:1, ease:1}) {
if ( target.findParent('.trip-result-breakdown-' + cat) ) {
switchToCategory = cat;
break;
}
}
} else if ( target.findParent('.trip-result-details-tabs') ) { // category tab link
openDetails = true;
link = target.findParent('td', 3, true);
if (!link.hasClass('x-active-category-tab')) {
for (var cat in {speed:1, comfort:1, ease:1}) {
if (link.hasClass('trip-result-details-tab-' + cat)) {
switchToCategory = cat;
break;
}
}
}
} else if ( target.findParent('.trip-result-tripQuality', 3, true) ) { // details open link
openDetails = true;
} else if ( target.findParent('.trip-result-details-link', 3, true) ) { // details toggle link
openDetails = !detailsOpened;
} else if ( target.findParent('.trip-result-details-close', 2, true) ) { // details close link
openDetails = false;
}
if (detailsOpened != openDetails) {
// either open or close details for this trip
result = Ext.get(result);
link = result.child('.trip-result-details-link a');
var container = result.child('.trip-result-details-container');
if (openDetails) { // open the details
// for IE, create new container
if (Ext.isIE) {
var newContainer = Ext.DomHelper.insertAfter(container, { tag: 'div', cls: 'trip-result-details-container' }, true);
container.remove();
container = newContainer;
}
// generate the details markup for this trip and append to result row
var detailsId = 'trip-details-' + this.tripQueryIndex + '-' + tripId;
this.trips[ this.tripQueryIndex ].loadDomDetails(tripId, container);
link.replaceClass('x-details-show', 'x-details-hide');
link.findParent('.trip-result', 10, true).addClass('trip-result-with-details');
if (Ext.isIE) {
container.setStyle('position', 'static');
}
} else { // close the details
// remove details from result row
result.child('.trip-result-details').remove();
link.replaceClass('x-details-hide', 'x-details-show');
link.findParent('.trip-result', 10, true).removeClass('trip-result-with-details');
if (Ext.isIE) {
container.setStyle('position', 'absolute');
}
}
}
if (switchToCategory) {
// display the specified category score breakdown
this.trips[ this.tripQueryIndex ].loadDomDetailsCategory(tripId, cat);
}
return false;
}, // end handleTripClick
handleTripPagingClick : function(e) {
var target = Ext.get(e.target);
var link = target.findParent('a');
var page = link.id.split('trip-results-page-');
page = page[1];
e.stopEvent();
if (!page) { return; }
// translate jump-to links into page numbers
var currentPage = parseInt(this.param.page, 10) || 1;
switch (page) {
case 'first':
page = 1;
break;
case 'previous':
page = currentPage - 1;
break;
case 'next':
page = currentPage + 1;
break;
case 'last':
page = 99; // there will never be this many pages
break;
}
// navigate to selected page
this.param.page = page;
this.registerState();
}, // end handleTripPagingClick()
initState : function() {
// activate the initial state
this.activateState( Ext.ux.History.getCurrentState(this.application) );
}, // end initState()
activateState : function(state) {
// if first activation and non-supported browser, throw up the unsupported-browser ui and stop
if ( this.onFirstActivation && !(Ext.isGecko || Ext.isIE7 || Ext.isIE || Ext.isSafari) ) {
// this.renderContentBlocks( InsideTrip.template.unsupportedBrowserUi );
// return;
}
// activate the state given as a query string
if (!state) {
if (Ext.isIE) {
// getBookmarkedState() needs a tiny pause to return accurate results in IE
if (this.activateDeferred) {
delete this.activateDeferred;
} else {
this.activateDeferred = true;
this.activateState.defer(10, this);
return;
}
}
// set default/initial state
state =
Ext.ux.History.getBookmarkedState(this.application) || // bookmarked hash
this.getQueryString() // or URL query string
InsideTrip.config.defaults.state || // or default state
'' // or nothing
;
}
// set new state parameters in the application
var param = Ext.urlDecode( state );
var directive = this.stateDirective;
if (typeof directive == 'object') {
Ext.apply(param, directive);
this.stateDirective = null; // directives are strictly one-time
}
var uiChange = (this.param.ui && this.param.ui != param.ui);
this.param = param;
this.state = state;
// ui parameter defaults to 'start'
if (!param.ui) { param.ui = 'start'; }
// follow application-specific state-setting procedure
switch(this.application) {
case 'trip-query': // trip search application
// has the trip query form been initialized yet?
var tripQueryForm = this.tripQueryForm;
if (!tripQueryForm) { return; }
// load default values and then state parameter values into the trip query form
var tripQueryParams = this.snapTripQueryTimeRanges( param );
tripQueryForm.setValues( tripQueryParams );
// set up the UI stage to display
switch (param.ui) {
case 'results':
// validate trip query in current state
if (!tripQueryForm.isValid()) {
param.ui = 'start';
this.tripQueryIndex = 0;
break;
}
// set the state's tripQueryIndex from new state values, via the just-populated trip query form
var tripQueryString = tripQueryForm.getQueryString();
var tripQueryIndex = this.tripQueryStrings[ tripQueryString ];
var tripQueryChange = (tripQueryIndex != this.tripQueryIndex);
this.tripQueryIndex = tripQueryIndex;
// has this trip query been submitted by this application instance before?
if (
tripQueryIndex && this.trips[tripQueryIndex] &&
(this.trips[tripQueryIndex].secondsSinceLoad() < InsideTrip.config.data.tripRecordsExpiry)
) { // yes, and results have been loaded that are not expired
// make sure results ui is shown, including "wait" message
this.renderUI('results');
// add trip query to recent searches
this.addTripRecent( param );
var query = this.tripQueryForm.translateNames( param );
// this.addTripRecent.defer(100, this, [
// query['depart-airport'],
// query['arrive-airport'],
// query['depart-date'],
// query['journey-type-roundtrip'] ? query['return-date'] : null,
// this.state
// ]);
// pause briefly (for browser to catch its breath) then process and display trip results
this.activateTripResults.defer(10, this);
} else { // no, either trip query hasn't been submitted or its results aren't back yet
if (tripQueryIndex && this.trips[tripQueryIndex]) { // actually, we do have results but they're expired
// delete expired results before submitting query anew
this.deleteTripsByQueryString(tripQueryString);
tripQueryIndex = null;
}
if ( !tripQueryIndex ) { // query has not in fact been submitted, so submit now
// assign an index to the query string
this.tripQueryStrings[tripQueryString] = this.tripQueryIndex = this.nextTripQueryIndex++;;
// submit query to back end
Ext.Ajax.timeout = InsideTrip.config.timeout.tripSearch * 1000;
tripQueryForm.submit({
success: tripQueryForm.onResponse,
failure: tripQueryForm.onResponse,
params: tripQueryString,
stateActivated: true // (handleTripQueryAction uses this to distinguish between automatic and user-initiated submit actions)
});
this.preloadImageArray.defer(1500, this, [InsideTrip.config.preloadsInterstitial]);
}
// show interstitial for the wait
this.renderUI('interstitial-search');
}
break; // end case 'results'
case 'start':
// blank tripQueryIndex
this.tripQueryIndex = 0;
// render the start UI
this.renderUI('start');
// initiate trip query entry by focusing the form's first input element
tripQueryForm.findField('journey-type-roundtrip').focus();
break;
case 'interstitial-purchase':
this.renderUI('interstitial-purchase');
Ext.get('trip-purchase-transfer').on(
'click',
function() { window.location = this.url; },
{ url: param.purchaseUrl }
);
break;
}
if (uiChange && Ext.isIE) {
// re-apply trip query form values for IE
tripQueryForm.setValues(tripQueryParams || param);
}
break; // end trip search application
} // end switch(this.application)
// are we activating state for the first time?
if (this.onFirstActivation) {
this.onFirstActivation();
this.onFirstActivation = null; // obviously, we only want to do this once
}
// track new state with Google Analytics
pageTracker._trackPageview('?' + state);
}, // end activateState()
activateTripResults : function() {
// make sure dashboard and filters forms are initialized
if (!this.tripQualityForm) { this.initTripQuality( Ext.get('trip-tripQuality') ); }
if (!this.tripFilters) { this.initTripFilters( Ext.get('trip-filters') ); }
if (!this.tripFlags) { this.initTripFlags( Ext.get('trip-flags') ); }
if (!this.tripDashboard) { this.initTripDashboard( Ext.get('trip-dashboard') ); }
// (re)conscribe filters for this trips set
this.conscribeTripFilters();
// load default+state parameter values into the dashboard and filters forms
this.tripQualityForm.setValues( this.param );
this.tripFilters.setValues( this.param );
this.tripFlags.setValues( this.param );
this.tripDashboard.setValues( this.param );
// (re)score trips and (re)conscribe tripQuality
var tripQueryIndex = this.tripQueryIndex;
var trips = this.trips[tripQueryIndex];
trips.scoreTrips( this.tripDashboard.getValues(true) );
this.conscribeTripQuality();
// (re)filter trips
trips.filterTrips(
Ext.apply( this.tripFlags.getValues(true), this.tripFilters.translateNames(), this.tripQualityForm.getValues() ),
this.tripFlags.getFlags()
);
// sort trips and set sort links
var oneway = (trips.returnTimeLow==null);
if (oneway) {
Ext.get('trip-results-head-return').select('a').addClass('x-sort-disabled');
} else {
Ext.get('trip-results-head-return').select('a').removeClass('x-sort-disabled');
}
this.param.sort = this.param.sort || 'price'; // sort field defaults to overall tripQuality
this.param.dir = this.param.dir || 'ASC'; // sort order defaults to ascending
Ext.get('trip-results-head').select('a').removeClass(['x-sort-ASC', 'x-sort-DESC']);
var sortField = InsideTrip.config.data.tripFieldKeys[ this.param.sort ];
Ext.get( 'trip-sort-' + sortField ).addClass('x-sort-' + this.param.dir);
trips.sort(sortField, this.param.dir);
// set paging
var tripCount = trips.getCount();
var startIndex, endIndex, pageCount;
if (tripCount > 0) {
var tripsPerPage = InsideTrip.config.tripResultsPerPage;
pageCount = Math.ceil( tripCount / tripsPerPage );
var pagesContainer = Ext.get('trip-results-pages');
var pages = [];
for (var i = 1; i <= pageCount; i++) {
pages.push( '' + i + '' );
}
pagesContainer.update( pages.join(' | ') ).select('a').on('click', this.handleTripPagingClick, this);
var page = this.param.page || 1;
if (page > pageCount) { this.param.page = page = pageCount; }
Ext.get('trip-results-page-' + page).addClass('trip-results-page-current');
startIndex = tripsPerPage * (page - 1);
endIndex = startIndex + tripsPerPage;
if (page == 1) {
Ext.get('trip-results-page-first').addClass('trip-results-page-disabled');
Ext.get('trip-results-page-previous').addClass('trip-results-page-disabled');
} else {
Ext.get('trip-results-page-first').removeClass('trip-results-page-disabled');
Ext.get('trip-results-page-previous').removeClass('trip-results-page-disabled');
}
if (page == pageCount) {
endIndex = tripCount - 1;
Ext.get('trip-results-page-next').addClass('trip-results-page-disabled');
Ext.get('trip-results-page-last').addClass('trip-results-page-disabled');
} else {
Ext.get('trip-results-page-next').removeClass('trip-results-page-disabled');
Ext.get('trip-results-page-last').removeClass('trip-results-page-disabled');
}
} else {
// zero results to display
this.param.page = pageCount = startIndex = endIndex = 0;
Ext.get('trip-results-pages').update('');
}
if (pageCount > 1) {
Ext.get('trip-results-paging').show();
} else {
Ext.get('trip-results-paging').hide();
}
// remove "wait" message
Ext.get('trip-results-wait').hide();
// cache any trip results currently inside the results container
this.cache('#trip-results-list .trip-result');
// generate new results list from cache
var results = Ext.get('trip-results-list');
results.update('');
var trips = this.trips[ this.tripQueryIndex ];
var records = trips.getRange(startIndex, endIndex);
for (var i = 0, len = records.length; i < len; i++) {
var tripId = records[i].id;
var tripDomId = 'trip-' + this.tripQueryIndex + '-' + tripId;
var tripRow = this.retrieve(tripDomId) || trips.loadDom(tripId);
if (tripRow) { results.appendChild(tripRow); }
}
if (len == 0) {
// all results have been filtered out
results.update( InsideTrip.template.tripsAllFilteredOutMsg );
}
}, // end activateTripResults()
preloadImageArray : function(imgArray) {
dh = Ext.DomHelper;
for(var i = 0; i < imgArray.length; i++) {
var newImgObj = new Object();
newImgObj.tag = 'img';
newImgObj.cls = 'preload';
newImgObj.src = imgArray[i];
var preloadImg = dh.append('drydock',newImgObj);
}
this.cache('#drydock .preload');
},
renderUI : function(ui) {
var t = InsideTrip.template;
// process application+ui state for new content
switch(this.application) {
case 'trip-query': // trip search application
// interstitials don't show footer; other UIs do
if ( ui.match('interstitial') ) {
Ext.get('content-foot').hide();
} else {
Ext.get('content-foot').show();
}
switch(ui) {
case 'start':
// is basic markup for start ui in place?
if ( 'trip-query-start' != document.body.id ) {
// build trip query start ui in the document
this.renderContentBlocks( t.tripStartUi );
// check trip query form into new container and make sure it's visible
Ext.get('trip-query-container').appendChild( this.tripQueryForm.el );
Ext.get('trip-query').setStyle('display', 'block');
// check recent queries list into new container, decide whether to display, and fill in trips
var recentContainer = Ext.get('trip-recent-container');
var recentList = this.retrieve('trip-recent-list');
var recentDateFormat = InsideTrip.template.tripRecentDateFormat;
var recentTemplate = new Ext.Template( InsideTrip.template.tripRecentSearch )
var recentSearches = this.recentSearches;
recentContainer.appendChild(recentList);
this.cache('#trip-recent-list li');
if ( recentSearches.length > 0 ) {
for (var i = 0, len = recentSearches.length; i < len; i++) {
var recentParams = this.tripQueryForm.translateNames( recentSearches[i] );
var departPort = recentParams['depart-airport'];
var departDate = recentParams['depart-date'];
var arrivePort = recentParams['arrive-airport'];
var returnDate = (recentParams['journey-type-roundtrip'] ? recentParams['return-date'] : 'oneway');
var recentId = 'trip-recent-' + departPort + '-' + departDate + '-' + arrivePort + '-' + returnDate;
var recentEl = this.retrieve(recentId);
if (recentEl) {
recentEl.appendTo(recentList);
} else {
departDate = Date.parseDate(departDate, 'Y-m-d');
if (!departDate) { continue; }
returnDate = Date.parseDate(returnDate, 'Y-m-d');
recentEl = recentTemplate.append(recentList, {
id: recentId,
dates: departDate.format(recentDateFormat) + (returnDate ? ('-' + returnDate.format(recentDateFormat)) : ''),
'depart-airport': departPort,
'arrive-airport': arrivePort,
queryString: Ext.urlEncode( recentSearches[i] )
}, true);
var a = recentEl.child('a');
a.on('click', this.handleRecentLinkClick, this);
}
}
recentContainer.show();
} else {
recentContainer.hide();
}
// id document body
document.body.id = 'trip-query-start';
//preload images
if ( this.onFirstActivation ) {
this.preloadImageArray.defer(3000, this, [InsideTrip.config.preloads]);
}
}
break;
case 'interstitial-search':
if ( 'trip-query-interstitial-search' != document.body.id ) {
this.renderContentBlocks( t.tripInterstitialSearchUi );
document.body.id = 'trip-query-interstitial-search';
}
break;
case 'interstitial-purchase':
if ( 'trip-query-interstitial-purchase' != document.body.id ) {
this.renderContentBlocks( t.tripInterstitialPurchaseUi );
document.body.id = 'trip-query-interstitial-purchase';
}
break;
case 'results':
// make sure basic markup for results ui is in place
if ( 'trip-query-results' != document.body.id ) {
// need to build trip query results ui in the document
this.renderContentBlocks( t.tripResultsUi );
// retrieve or generate head logo link
var logoLink = this.retrieve('logo-link');
if (logoLink) {
Ext.get('logo-container').appendChild(logoLink);
} else {
Ext.get('logo-container').update( t.logoLink );
Ext.get('logo-link').on('click', this.handleLogoClick, this);
}
// retrieve or generate filters and dashboard panels into new containers
var filtersPanel = this.retrieve('panel-trip-filters');
if (filtersPanel) {
Ext.get('filters-container').appendChild(filtersPanel);
} else {
Ext.get('filters-container').update( t.tripFilterPanel );
}
var dashboardPanel = this.retrieve('panel-trip-dashboard');
if (dashboardPanel) {
Ext.get('dashboard-container').appendChild(dashboardPanel);
} else {
Ext.get('dashboard-container').update( t.tripDashboard );
}
// check trip query form into new container
Ext.get('trip-query-container').appendChild( Ext.get(this.tripQueryForm.el) );
// attach listeners to "modify" and "hide" links
Ext.get('trip-query-show').on('click', function(e) {
Ext.get('trip-query-show').setStyle('display', 'none');
Ext.get('trip-query-hide').setStyle('display', 'block');
Ext.get('trip-query').setStyle('display', 'block');
this.tripFilters.resynchSliders();
e.stopEvent();
}, this);
Ext.get('trip-query-hide').on('click', function(e) {
Ext.get('trip-query-hide').setStyle('display', 'none');
Ext.get('trip-query-show').setStyle('display', 'block');
Ext.get('trip-query').setStyle('display', 'none');
this.tripFilters.resynchSliders();
e.stopEvent();
}, this);
// retrieve or generate results list head into new container, and attach sort link listeners if needed
var resultsHead = this.retrieve('trip-results-head');
var resultsContainer = Ext.get('trip-results-container');
if (resultsHead) {
resultsContainer.appendChild(resultsHead);
} else {
resultsContainer.update( t.tripResultsHead );
Ext.get('trip-results-head').select('a').on('click', this.handleTripSortClick, this);
}
// create new results list container and paging container
Ext.DomHelper.append(resultsContainer, { tag: 'div', id: 'trip-results-list' }, true);
var pagingContainer = Ext.DomHelper.append(resultsContainer, { tag: 'div', id: 'trip-results-paging-container' }, true);
pagingContainer.hide(); // we'll show this when we're ready for the user to see it
// retrieve or generate results list paging, and attach link listeners if needed
var resultsPaging = this.retrieve('trip-results-paging');
if (resultsPaging) {
pagingContainer.appendChild(resultsPaging);
} else {
Ext.DomHelper.append(pagingContainer, t.tripResultsPaging);
Ext.get('trip-results-paging').select('a').on('click', this.handleTripPagingClick, this);
}
// create "wait" message
var waitMsg = new Ext.Layer({ id: 'trip-results-wait', constrain: true, shim: true });
waitMsg.hide().update(InsideTrip.template.tripResultsWaitMsg);
// id document body
document.body.id = 'trip-query-results';
}
// show "wait" message for now
this.retrieve('trip-results-wait').center().show();
// snap trip query form closed (hidden)
Ext.get('trip-query').setStyle('display', 'none');
Ext.get('trip-query-hide').setStyle('display', 'none');
Ext.get('trip-query-show').setStyle('display', 'block');
// make sure slider widgets are checked into the document and slider positions are synched
var docBody = Ext.get(document.body);
var widget;
if (this.tripQualityForm) {
if ( (widget = this.retrieve('field-tripQuality-widget')) && !docBody.contains(widget) ) { widget.appendTo(docBody); }
this.tripQualityForm.resynchSliders();
}
if (this.tripFilters) {
if ( (widget = this.retrieve('field-time-depart-widget')) && !docBody.contains(widget) ) { widget.appendTo(docBody); }
if ( (widget = this.retrieve('field-time-return-widget')) && !docBody.contains(widget) ) { widget.appendTo(docBody); }
if ( (widget = this.retrieve('field-connection-widget')) && !docBody.contains(widget) ) { widget.appendTo(docBody); }
this.tripFilters.resynchSliders();
}
// get current trips store
var trips = this.trips[ this.tripQueryIndex ];
// update search synopsis if necessary
var tripQuerySegment = Ext.get('segment-trip-query');
if ( !tripQuerySegment.hasClass('trips-' + this.tripQueryIndex) ) {
var tripQuery = this.tripQueryForm.translateNames( this.tripQueryForm.translateValues(trips.queryString) );
Ext.get('synopsis-location-depart-name').update(trips.airportNames[ tripQuery['depart-airport'] ]);
Ext.get('synopsis-location-depart-code').update(tripQuery['depart-airport']);
Ext.get('synopsis-location-arrive-name').update(trips.airportNames[ tripQuery['arrive-airport'] ]);
Ext.get('synopsis-location-arrive-code').update(tripQuery['arrive-airport']);
var departDate = Date.parseDate( tripQuery['depart-date'], 'Y-m-d' );
var synopsisDates = departDate.format('D, n/d/y');
if ( tripQuery['journey-type-roundtrip'] ) {
var returnDate = Date.parseDate( tripQuery['return-date'], 'Y-m-d' );
synopsisDates += ' - ' + returnDate.format('D, n/d/y');
}
Ext.get('synopsis-dates').update(synopsisDates);
Ext.get('synopsis-journey-type').update(tripQuery['journey-type-roundtrip'] ? 'Roundtrip' : 'One way');
Ext.get('synopsis-travelers').update(tripQuery['travelers'] + ' adult' + (tripQuery['travelers'] > 1 ? 's' : ''));
Ext.get('synopsis-cabin').update(tripQuery['cabin']);
tripQuerySegment.replaceClassIndexed('trips-', this.tripQueryIndex);
}
break; // end case results
} // end switch ui
if (this.param.err) {
// display error message
this.renderError(this.param.err, this.param.errField);
}
break;
} // end switch application
}, // end renderState()
renderContentBlocks : function(blocks) {
// cache autocache elements for safe keeping
this.cache();
// write given markup into each content area ("block")
var key, el;
for (key in blocks) {
el = Ext.get('content-' + key);
if (el) {
el.update( blocks[key] );
}
}
}, // end renderContentBlocks
renderError : function(message, field) {
//TODO: make a proper dialog and attach to field if given
alert(message);
}, // end renderError()
registerState : function(state) {
if (typeof state != 'string') {
// state not given, so generate new state from current parameters
state = Ext.urlEncode(this.param);
}
// is the new state the same as the current state (and there is no state directive)
var isRedundant = (state==this.state);
var hasStateDirective = (typeof this.stateDirective == 'object');
if (isRedundant && !hasStateDirective) {
// requested state is already the current state, so do nothing
return false;
}
// record state in application, extract application state parameters, and record state in history
this.state = state;
this.param = Ext.urlDecode(state);
Ext.ux.History.navigate( this.application, this.state );
// is the new state redundant with a state directive?
if (isRedundant && hasStateDirective) {
// manually invoke activateState()
this.activateState();
}
}, // end registerState()
deleteTripsByQueryString : function(queryString) {
var queryIndex = this.tripQueryStrings[ queryString ];
if (queryIndex) {
delete this.trips[ queryIndex ];
delete this.tripQueryStrings[ queryString ];
}
} // end deleteTripsByQueryString()
});
/**
* Customized JsonStore for InsideTrip trip query results
*/
InsideTrip.TripStore = function(config) {
Ext.apply(this, config, {
airports: {
depart: [],
connect: [],
arrive: []
},
airlines: {}
});
var dummyData = {};
dummyData[ this.root ] = [];
InsideTrip.TripStore.superclass.constructor.call(this, { data: this.data, fields: this.fields });
if (config.data) {
this.loadTripsJson(config.data);
}
this.roundtrip = (this.returnTimeLow !== null);
}
Ext.extend(InsideTrip.TripStore, Ext.data.JsonStore, {
fields: InsideTrip.config.data.tripFields,
root: InsideTrip.config.data.tripRecordsRoot,
scoreInfo: null,
filterInfo: null,
resultTemplate: new Ext.Template( InsideTrip.template.tripResult ),
detailsTemplate: {
Main: new Ext.Template( InsideTrip.template.tripResultDetails ),
Flight: new Ext.Template( InsideTrip.template.tripResultsDetailsFlight ),
Evaluator: new Ext.Template( InsideTrip.template.tripResultsDetailsFlightEvaluator ),
FlightHeadFirst: new Ext.Template( InsideTrip.template.tripResultsDetailsFlightHeadFirst ),
FlightHeadSub: new Ext.Template( InsideTrip.template.tripResultsDetailsFlightHeadSubsequent )
},
secondsSinceLoad : function() {
if (this.loadTimestamp) {
return Math.floor( this.loadTimestamp.getElapsed( new Date() ) / 1000 );
} else {
return null;
}
},
loadTripsJson : function(json) {
// overall trip-set attributes
this.connectionLow = null;
this.connectionHigh = null;
this.departTimeLow = null;
this.departTimeHigh = null;
this.returnTimeLow = null;
this.returnTimeHigh = null;
this.stopsLow = null;
this.stopsHigh = null;
this.departDurationHigh = null;
this.departDurationLow = null;
this.returnDurationHigh = null;
this.returnDurationLow = null;
var allAirports = this.airports = { depart: [], connect: [], arrive: [] };
var allDepartPortsHash = {}, allConnectPortsHash = {}, allArrivePortsHash = {};
var allAirportNames = this.airportNames = {};
var allAirlines = this.airlines = {};
// timestamp
this.loadTimestamp = new Date();
// load trip records from json
var trips = [];
var t = json[this.root];
var departTimeDepart, departTimeArrive, returnTimeDepart, returnTimeArrive;
var connectionLow, connectionHigh, airline, airlines;
var airports, departPorts, connectPorts, arrivePorts, departPortsHash, connectPortsHash, arrivePortsHash;
var r, departDir, returnDir, connections, j, clen, stops, aName, aCode, a, airlinesHash, legs, llen, returnLegs, legAttr;
for (var i = 0, len = t.length; i < len; i++) {
r = t[i];
departDir = r.itrip_dirs[0];
returnDir = r.itrip_dirs[1];
departTimeDepart = this.dateStringToMinutes( departDir.legs[0].time_dep_str );
departTimeArrive = this.dateStringToMinutes( departDir.legs[departDir.legs.length - 1].time_arr_str );
returnTimeDepart = returnDir ? this.dateStringToMinutes( returnDir.legs[0].time_dep_str ) : null;
returnTimeArrive = returnDir ? this.dateStringToMinutes( returnDir.legs[returnDir.legs.length - 1].time_arr_str ) : null;
if (this.departTimeLow===null || this.departTimeLow > departTimeDepart) { this.departTimeLow = departTimeDepart; }
if (this.departTimeHigh===null || this.departTimeHigh < departTimeDepart) { this.departTimeHigh = departTimeDepart; }
if (returnDir) {
if (this.returnTimeLow===null || this.returnTimeLow > returnTimeDepart) { this.returnTimeLow = returnTimeDepart; }
if (this.returnTimeHigh===null || this.returnTimeHigh < returnTimeDepart) { this.returnTimeHigh = returnTimeDepart; }
}
connectionLow = 1440;
connectionHigh = 0;
connections = departDir.cnx;
stops = connections.length;
if (returnDir) {
connections = connections.concat(returnDir.cnx);
stops = (returnDir.cnx.length > stops) ? returnDir.cnx.length : stops;
}
departPortsHash = {}, connectPortsHash = {}, arrivePortsHash = {};
for (j = 0, clen = connections.length; j < clen; j++) {
connectPortsHash[ connections[j].port ] = true;
if (connectionLow > connections[j].dur) { connectionLow = connections[j].dur; }
if (connectionHigh < connections[j].dur) { connectionHigh = connections[j].dur; }
}
if (connectionHigh == 0) { connectionHigh = connectionLow = null; } // no connections
if (this.connectionLow===null || (connectionLow !== null && this.connectionLow > connectionLow)) { this.connectionLow = connectionLow; }
if (this.connectionHigh===null || (connectionHigh !== null && this.connectionHigh < connectionHigh)) { this.connectionHigh = connectionHigh; }
if (this.stopsLow===null || this.stopsLow > stops) { this.stopsLow = stops; }
if (this.stopsHigh===null || this.stopsHigh < stops) { this.stopsHigh = stops; }
airline = null;
airlinesHash = {};
legs = departDir.legs;
departPortsHash[ legs[0].port_dep ] = true;
arrivePortsHash[ legs[legs.length - 1].port_arr ] = true;
if (returnDir) {
returnLegs = returnDir.legs;
sameAirports = (
legs[legs.length - 1].port_arr == returnLegs[0].port_dep &&
legs[0].port_dep == returnLegs[returnLegs.length - 1].port_arr
);
legs = legs.concat(returnLegs);
llen = legs.length;
arrivePortsHash[ returnLegs[0].port_dep ] = true; // note: "departure" and "arrival" cities/airports are defined in terms of the departure direction's origination and destination
departPortsHash[ legs[llen - 1].port_arr ] = true;
} else {
llen = legs.length;
sameAirports = true;
}
for (j = 0; j < llen; j++) {
legAttr = legs[j].attributes;
aName = legAttr.airline;
aCode = legAttr.airline_code;
if (aCode) {
airline = (airline && airline != aCode) ? 'multiple' : aCode;
airlinesHash[aCode] = true;
if (aName) { allAirlines[aCode] = aName; }
}
}
if ('multiple'==airline) {
airlinesHash['multiple'] = true;
allAirlines['multiple'] = 'Multiple carriers';
}
airports = {
depart: departPorts = [],
connect: connectPorts = [],
arrive: arrivePorts = []
};
for (a in departPortsHash) { departPorts.push(a); allDepartPortsHash[a] = true; }
for (a in connectPortsHash) { connectPorts.push(a); allConnectPortsHash[a] = true; }
for (a in arrivePortsHash) { arrivePorts.push(a); allArrivePortsHash[a] = true; }
airlines = [];
for (a in airlinesHash) { airlines.push(a); }
if (this.departDurationLow===null || this.departDurationLow > departDir.dur) { this.departDurationLow = departDir.dur; }
if (this.departDurationHigh===null || this.departDurationHigh < departDir.dur) { this.departDurationHigh = departDir.dur; }
if (returnDir) {
if (this.returnDurationLow===null || this.returnDurationLow > returnDir.dur) { this.returnDurationLow = returnDir.dur; }
if (this.returnDurationHigh===null || this.returnDurationHigh < returnDir.dur) { this.returnDurationHigh = returnDir.dur; }
}
trips.push({
'price': r.price,
'depart-time-depart': departTimeDepart,
'depart-time-arrive': departTimeArrive,
'depart-duration': departDir.dur,
'return-time-depart': returnTimeDepart,
'return-time-arrive': returnTimeArrive,
'return-duration': returnDir ? returnDir.dur : null,
'connection-low': connectionLow,
'connection-high': connectionHigh,
'stops': stops,
'airline': airline,
'airlines': airlines,
'airports': airports,
'same-airports': sameAirports,
'quality-pick': (r.attributes.qualityPick),
json: r
});
}
this.loadData(trips);
// aggregate trip-set airports
if (json.airports) {
var a = json.airports;
for (i = 0, len = a.length; i < len; i++) {
var code = a[i].attributes.code;
var name = a[i].value;
allAirportNames[code] = name;
}
}
for (a in allDepartPortsHash) { allAirports.depart.push(a); }
for (a in allConnectPortsHash) { allAirports.connect.push(a); }
for (a in allArrivePortsHash) { allAirports.arrive.push(a); }
}, // end loadTripsJson()
dateStringToMinutes : function(string) {
return parseInt(string.substr(0,2), 10) * 60 + parseInt(string.substr(3,2), 10);
}, // end dateStringToMinutes()
loadDom : function(tripId, container) {
var template = this.resultTemplate;
container = Ext.get(container || this.domContainer);
if (!container || !template) { return false; }
var idRoot = this.domIdRoot || 'trip-';
var elId = idRoot + tripId;
var dom = template.append(container, { id: elId }, true);
var r = this.getById(tripId);
if (this.clickHandler) {
var purchaseLink = dom.child('.trip-result-purchase a');
purchaseLink.on('click', this.clickHandler);
purchaseLink.dom.setAttribute('href', r.get('json').buy);
dom.child('.trip-result-details-link a').on('click', this.clickHandler);
dom.child('.trip-result-tripQuality a').on('click', this.clickHandler);
dom.select('.trip-result-breakdown a').on('click', this.clickHandler);
}
if (r.get('quality-pick')) { dom.addClass('trip-result-qualityPick'); }
dom.child('.trip-result-price').update( '$' + (r.get('price') / 100) );
dom.child('.trip-result-airline').update( this.airlines[r.get('airline')] );
var maxLegDur = Math.max(this.departDurationHigh, this.returnDurationHigh);
InsideTrip.template.tripGantt(
dom.child('.trip-result-departure .trip-result-gantt'),
r.get('json').itrip_dirs[0].legs,
maxLegDur,
this.airportNames
);
dom.child('.trip-result-departure .trip-result-duration span').update(
InsideTrip.template.minutesToDuration( r.get('depart-duration') )
);
if (this.roundtrip) {
InsideTrip.template.tripGantt(
dom.child('.trip-result-return .trip-result-gantt'),
r.get('json').itrip_dirs[1].legs,
maxLegDur,
this.airportNames
);
dom.child('.trip-result-return .trip-result-duration span').update(
InsideTrip.template.minutesToDuration( r.get('return-duration') )
);
} else { // for oneway trips
var returnEl = dom.child('.trip-result-return');
returnEl.addClass('trip-result-noreturn');
returnEl.child('.trip-result-gantt').update('No return flight.').replaceClass('trip-result-gantt', 'trip-result-nogantt');
}
dom.select('.gantt-segment').addClassOnOver('x-gantt-highlight');
dom.select('.gantt-segment-connection').addClassOnOver('x-gantt-highlight');
// insert scoring data
this.loadDomScores(dom, r);
return dom;
}, // end loadDom()
loadDomScores : function(dom, record) {
var scoringFlags = this.scoringFlags;
if (typeof scoringFlags != 'object') { return; }
var detailsEl = dom.child('.trip-result-details');
var score, bracket;
for (var category in scoringFlags) {
score = record.get('score-' + category);
bracket = ( score === null ? '0' : Math.min(Math.floor(score/20)+1, 5) );
dom.child('.trip-result-breakdown-' + category + ' span').update(score).replaceClassIndexed('score-level-', bracket);
if (detailsEl) {
detailsEl.child('.trip-result-details-tab-' + category).replaceClassIndexed('score-level-', bracket);
}
}
dom.child('.trip-result-departure .trip-result-direction-score span').update( record.get('score-depart') );
dom.child('.trip-result-return .trip-result-direction-score span').update( record.get('score-return') );
dom.child('.trip-result-tripQuality-score').update( record.get('score') );
}, // end loadDomScores
loadDomDetails : function(id, container) {
container = Ext.get(container || this.domContainer);
var r = this.getById(id);
if (!r || !container) { return false; }
id = this.domIdRoot + 'details-' + r.id;
var dom = this.detailsTemplate.Main.append(container, { id: id }, true);
// links
if (this.clickHandler) {
dom.child('.trip-result-details-close').on('click', this.clickHandler);
dom.child('.trip-result-details-tab-speed a').on('click', this.clickHandler);
dom.child('.trip-result-details-tab-comfort a').on('click', this.clickHandler);
dom.child('.trip-result-details-tab-ease a').on('click', this.clickHandler);
}
// trip summary
var tripQuery = app.tripQueryForm.translateNames(this.queryString);
var roundtrip = !tripQuery['journey-type-oneway'];
var dirs = r.get('json').itrip_dirs;
var imgUrl = InsideTrip.config.url.airlineImages + '/' + r.get('airline') + '_logo.gif';
dom.child('.trip-result-details-summary img').dom.setAttribute('src', imgUrl);
dom.child('.trip-result-details-departure-city').update(this.airportNames[ dirs[0].legs[0].port_dep ]);
dom.child('.trip-result-details-arrival-city').update(this.airportNames[ dirs[0].legs[ dirs[0].legs.length - 1 ].port_arr ]);
var dates = Date.parseDate(dirs[0].legs[0].date_dep_str.substr(0,10), 'Y-m-d').format('D, M j, Y');
if (roundtrip) {
dates += ' - ' + Date.parseDate(dirs[1].legs[0].date_dep_str.substr(0,10), 'Y-m-d').format('D, M j, Y');
}
dom.child('.trip-result-details-summary-dates').update(dates);
dom.child('.trip-result-details-journey-type').update(roundtrip ? 'Roundtrip' : 'One Way');
var travelers = tripQuery.travelers;
dom.child('.trip-result-details-travelers').update( travelers + ' adult' + (travelers > 1 ? 's' : '') );
// category tabs
for (var category in {speed:1, comfort:1, ease:1}) {
score = r.get('score-' + category);
dom.child('.trip-result-details-tab-' + category).replaceClassIndexed( 'score-level-', score === null ? '0' : Math.min(Math.floor(score/20)+1, 5) );
}
// trip legs
var tbody = dom.child('.trip-result-details-table-body');
var legs, connections, flight, head, layoverLength, time;
for (var d = 0, dlen = dirs.length; d < dlen; d++) {
legs = dirs[d].legs;
connections = dirs[d].cnx;
for (var i = 0, len = legs.length; i < len; i++) {
flight = this.detailsTemplate.Flight.append(tbody, {}, true);
head = flight.child('.trip-result-details-flight-head');
if (i==0) { // first leg of direction
flight.addClass('trip-result-details-flight-first');
head = this.detailsTemplate.FlightHeadFirst.append(head, {}, true);
head.child('.trip-result-details-flight-direction').update( d==0 ? 'Departure' : 'Return' );
head.child('.trip-result-details-flight-date').update(
Date.parseDate(legs[i].date_dep_str.substr(0,10), 'Y-m-d').format('m/d/y')
);
} else { // subsequent leg
head = this.detailsTemplate.FlightHeadSub.append(head, {}, true);
layoverLength = InsideTrip.template.minutesToDurationLong( connections[i - 1].dur );
head.child('.trip-result-details-flight-layover-length').update(layoverLength);
}
flight.child('.trip-result-details-flight-itinerary-airline').update( legs[i].attributes.airline );
flight.child('.trip-result-details-flight-itinerary-number').update( legs[i].attributes.fnum );
time = legs[i].time_dep_str;
time = InsideTrip.template.minutesToTimeLong( parseInt(time.substr(0,2), 10) * 60 + parseInt(time.substr(3,2), 10) );
flight.child('.trip-result-details-flight-itinerary-depart-time').update(time);
flight.child('.trip-result-details-flight-itinerary-depart-code').update( legs[i].port_dep );
flight.child('.trip-result-details-flight-itinerary-depart-city').update( this.airportNames[legs[i].port_dep] );
time = legs[i].time_arr_str;
time = InsideTrip.template.minutesToTimeLong( parseInt(time.substr(0,2), 10) * 60 + parseInt(time.substr(3,2), 10) );
flight.child('.trip-result-details-flight-itinerary-arrive-time').update(time);
flight.child('.trip-result-details-flight-itinerary-arrive-code').update( legs[i].port_arr );
flight.child('.trip-result-details-flight-itinerary-arrive-city').update( this.airportNames[legs[i].port_arr] );
}
}
// activate the Speed tab by default
this.loadDomDetailsCategory(r.id, 'speed');
return dom;
}, // end loadDomDetails()
loadDomDetailsCategory : function(id, cat) {
var r = this.getById(id);
if (!r) { return false; }
var domId = this.domIdRoot + 'details-' + r.id;
var dom = Ext.get(domId);
if (!dom) { return false; }
// switch active category tab
dom.select('.x-active-category-tab').removeClass('x-active-category-tab');
dom.child('.trip-result-details-tab-' + cat).addClass('x-active-category-tab');
// evaluator headings
var heads = dom.query('.trip-result-details-evaluator-head');
var evalNames = InsideTrip.template.tripEvaluators[cat];
var i = 0;
for (var evaluator in evalNames) {
Ext.get(heads[i]).update( evalNames[evaluator] );
i++;
}
// clear out any old evaluators
dom.select('.trip-result-details-evaluator').remove();
// flight rows
var flights = dom.query('.trip-result-details-flight');
var dirs = r.get('json').itrip_dirs;
var findEval = function(key, collection) {
var rtnCollection = null;
for (var i = 0, len = collection.length; i < len; i++) {
if (collection[i].attributes.key == key) {
if ( rtnCollection == null )
rtnCollection = Array();
var ord = 0;
if ( collection[i].attributes.ord )
ord = collection[i].attributes.ord;
rtnCollection [ord]= collection[i].attributes;
}
}
return rtnCollection;
};
var tripEvalKeys = InsideTrip.config.data.tripEvalKeys;
var dirEvals, legEvals, row, col, rowspan, data, cell;
var r = 0;
for (var d = 0, dlen = dirs.length; d < dlen; d++) {
legs = dirs[d].legs;
dirEvals = dirs[d].evals;
for (var i = 0, len = legs.length; i < len; i++, r++) {
legEvals = legs[i].evals;
row = Ext.get(flights[r]);
col = 0;
for (evaluator in evalNames) {
col++;
if ( dataCollection = findEval(tripEvalKeys[evaluator], dirEvals) ) { // per-direction evaluator data
if (i >= dataCollection.length) { continue; } // no additional cells in this column
if (dataCollection.length > 1 )
rowspan = 1;
else
rowspan = len;
data = dataCollection[i];
} else { // per-leg evaluator data
rowspan = 1;
dataCollection = findEval(tripEvalKeys[evaluator], legEvals);
if ( dataCollection )
data = dataCollection[0];
else
data = { val: 0, max: 0, text: 'N/A' };
}
cell = this.detailsTemplate.Evaluator.append(row, {}, true);
cell.dom.setAttribute(Ext.isIE ? 'rowSpan' : 'rowspan', rowspan);
cell.addClass( 'trip-result-details-evaluator-col-' + col );
var scoreLevel = ( data.max == 0 ? '0' : Math.min(Math.floor(data.val / data.max * 5)+1, 5) );
cell.addClass( 'score-level-' + scoreLevel );
if ( (rowspan == len || i == (len - 1)) && d == dlen - 1 ) {
cell.addClass('trip-result-details-evaluator-bottom');
}
cell.child('.trip-result-details-evaluator-class').update(data.text);
cell.child('.trip-result-details-evaluator-description').update(data.raw);
} // end for each evaluator
} // end for each leg/flight
} // end for each direction
}, // end loadDomDetailsCategory
sort : function(fieldName, dir) {
// check that this isn't a redundant call, then call superclass sort
var sortInfo = this.sortInfo;
if (
typeof sortInfo != 'object' ||
sortInfo.field != fieldName ||
sortInfo.direction != dir
) {
return InsideTrip.TripStore.superclass.sort.call(this, fieldName, dir);
}
}, // end sort()
scoreTrips : function(flagsByCategory) {
// update scoring flags
this.scoringFlags = flagsByCategory;
// prepare scoring criteria
var flagsAll = {};
for (var cat in flagsByCategory) {
Ext.apply(flagsAll, flagsByCategory[cat]);
}
if ( this.scoreInfo && Ext.urlEncode(this.scoreInfo)==Ext.urlEncode(flagsAll) ) {
// trips are already scored to these criteria
return;
}
this.scoreInfo = flagsAll;
// score all trips
this.tripQualityLow = this.tripQualityHigh = null;
var mean = function(a) {
var sum = 0;
for (var count = 0, len = a.length; count < len; count++) {
sum += a[count];
}
return count ? (sum/count) : null;
};
var data = this.snapshot || this.data;
data.each( function(record) { // score complete unfiltered set (stored in snapshot)
var json = record.get('json');
if (!json) { return; }
// direction scores
var directions = json.itrip_dirs;
var directionQuality = [];
var evalScores = {};
for (var i = 0, lena = directions.length; i < lena; i++) { // for each direction
var d = directions[i];
var dirScore_points = 0;
var dirScore_possible = 0;
for (var evalName in flagsAll) { // for each evaluator score
// ignore dashboard-excluded evaluators
if (!flagsAll[evalName]) { continue; }
// translate evaluator name to json key
var key = InsideTrip.config.data.tripEvalKeys[evalName];
// add evaluator into direction quality
dirScore_points += d.ts[key];
dirScore_possible += d.ts_pos[key];
// add this evaluator score into total evaluator score
if (!evalScores[evalName]) { evalScores[evalName] = { points: 0, possible: 0 }; }
evalScores[evalName].points += d.ts[key];
evalScores[evalName].possible += d.ts_pos[key];
}
var dirScore = ( dirScore_possible ? (dirScore_points / dirScore_possible * 100) : null );
directionQuality[i] = dirScore ? Math.round(dirScore) : 0;
}
record.set('score-depart', directionQuality[0]);
record.set('score-return', directionQuality[1] || null);
// overall score
var tripQuality = mean(directionQuality);
tripQuality = tripQuality ? Math.round(tripQuality) : null;
record.set('score', tripQuality);
if (this.tripQualityLow === null || tripQuality < this.tripQualityLow) { this.tripQualityLow = tripQuality; }
if (this.tripQualityHigh === null || tripQuality > this.tripQualityHigh) { this.tripQualityHigh = tripQuality; }
// category scores
for (var category in flagsByCategory) {
var catScore_points = 0, catScore_possible = 0;
for (var evalName in flagsByCategory[category]) {
var evalScore = evalScores[evalName];
if (evalScore) {
catScore_points += evalScore.points;
catScore_possible += evalScore.possible;
}
}
var catScore = (catScore_possible ? (catScore_points / catScore_possible * 100) : null);
record.set( ('score-' + category), catScore_possible ? (Math.round(catScore) || 0) : null );
}
// update scores in dom
var dom = ( app ? app.retrieve(this.domIdRoot + record.id) : Ext.get(this.domIdRoot + record.id) );
if (dom) {
this.loadDomScores(dom, record);
}
}, this ); // end each trip record (execute in this TripStore scope)
}, // end scoreTrips
filterTrips : function(f, flags) {
// separate out departure time range low and high
var timeDepartRange = f['time-depart'].split('-');
f['time-depart-low'] = timeDepartRange[0];
f['time-depart-high'] = timeDepartRange[1];
// separate out return time range low and high
var timeReturnRange = f['time-return'].split('-');
f['time-return-low'] = timeReturnRange[0];
f['time-return-high'] = timeReturnRange[1];
// separate out return time range low and high
var connectionRange = f['connection'].split('-');
f['connection-low'] = connectionRange[0];
f['connection-high'] = connectionRange[1];
// are these trips already filtered on these criteria?
if (
this.filterInfo && Ext.urlEncode(this.filterInfo)==Ext.urlEncode(f) &&
this.flagInfo && Ext.urlEncode(this.flagInfo)==Ext.urlEncode(flags)
) {
// trips are already scored to these criteria
return;
}
this.filterInfo = f;
this.flagInfo = flags;
// filter all trips
this.clearFilter(true);
this.filterBy( this._filterTrips );
}, // end filterTrips
_filterTrips : function(r) {
var f = this.filterInfo;
// filter on tripQuality score
var tripQuality = r.get('score');
if (typeof tripQuality == 'number' && tripQuality < f.tripQuality) { return false; }
// filter on stops
var stops = r.get('stops');
if (!f['stops-0'] && stops===0) { return false; }
if (!f['stops-1'] && stops===1) { return false; }
if (!f['stops-2'] && stops >= 2) { return false; }
// filter on departure time
var departTimeDepart = r.get('depart-time-depart');
if ( departTimeDepart < f['time-depart-low'] ) { return false; }
if ( departTimeDepart > f['time-depart-high'] ) { return false; }
// filter on return time
var returnTimeDepart = r.get('return-time-depart');
if (
typeof returnTimeDepart == 'number' && (
returnTimeDepart < f['time-return-low'] ||
returnTimeDepart > f['time-return-high']
)
) { return false; }
// filter on connection length
var connectionLow = r.get('connection-low');
var connectionHigh = r.get('connection-high');
if ( connectionLow && ( connectionLow < f['connection-low'] ) ) { return false; }
if ( connectionHigh && (connectionHigh > f['connection-high']) ) { return false; }
// filter on arrive/depart same airport
if ( f['airports']['same-airports'] && !r.get('same-airports') ) { return false; }
// filter on airports
var airports = r.get('airports');
for (var apSetName in airports) {
var apSet = airports[apSetName];
var apSetFilter = f['airports-' + apSetName];
for (var i = 0, len = apSet.length; i < len; i++) {
if (!apSetFilter[ 'airport-' + apSetName + '-' + apSet[i] ]) { return false; }