<!DOCTYPE html>
<html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script>
<script data-require="angularjs@1.2.15" data-semver="1.2.15" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.js"></script>
<script src="http://danielcrisp.github.io/angular-rangeslider/angular.rangeSlider.js"></script>
<script src="http://huei90.github.io/angular-validation/dist/angular-validation.js"></script>
<script src="http://huei90.github.io/angular-validation/dist/angular-validation-rule.js"></script>
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/themes/smoothness/jquery-ui.css">
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="http://danielcrisp.github.io/angular-rangeslider/angular.rangeSlider.css">
<script src="app.js"></script>
<script src="flightSearchCtrl.js"></script>
<script src="flightService.js"></script>
<script src="dateTimePickerDirective.js"></script>
</head>
<body data-ng-app="flightSearch">
<div class="main" ng-controller="flightSearchCtrl">
<h1 class=header>Flight Search Engine</h1>
<div class="amber search-error" ng-show="!clear && flightData.length == 0">No Flights available for given search criteria!!!</div>
<section class="col col1 sidebar">
<ul class="tabs group">
<li ng-click="roundTrip=false;"><a href="#box-one" ng-class="{'highlight':!roundTrip}">One Way</a></li>
<li ng-click="roundTrip=true;"><a href="#box-two" ng-class="{'highlight':roundTrip}">Two Way</a></li>
</ul>
<div class="tab">
<form ng-submit="search(origin,destination)" name="form">
<input type="text" name="origin" ng-model="origin" placeholder="Enter Origin City" required/>
<span class ="amber" ng-show="(form.origin.$dirty || submitted) && form.origin.$error.required">
Origin City is Required
</span>
<input type="text" name="departure" ng-model="destination" placeholder="Enter Destination City" required/>
<span class ="amber" ng-show="(form.departure.$dirty || submitted) && form.departure.$error.required">
Departure City is Required
</span>
<div>
<input type="text" name="departureDt" class="date" placeholder="Departure Date" ng-model="departureDate" datepicker required/>
<span class ="amber" ng-show="(form.departureDt.$dirty || submitted) && form.departureDt.$error.required">
Departure Date is Required
</span>
</div>
<div ng-if="roundTrip">
<input type="text" name="returnDt" class="date" placeholder="Return Date" ng-model="returnDate" datepicker required/>
<span class ="amber" ng-show="(form.returnDt.$dirty || submitted || roundTrip) && form.returnDt.$error.required">
Return Date is Required
</span>
</div>
<div>
<label for="passengers">Passengers:</label>
<select ng-model="count" id="passengers" required>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
</select>
</div>
<button class="search">search</button>
<div class="rangeSlider">
<h4>Refine Flight Search</h4>
<div range-slider min="0" max="10000" model-min="range.minPrice" step="1000" model-max="range.maxPrice" filter="currency:'Rs'"></div>
</div>
</form>
</div>
</section>
<section class="col col2 container" ng-if="!clear">
<h1 class="route"><span>{{origin}} > {{destination}}</span><span ng-show="roundTrip"> {{origin}}</span></h1>
<div>
<div>Departure Date: {{departureDate}}</div>
<div ng-show="roundTrip">Return Date: {{returnDate}} </div>
<div>Passengers count: {{count}}</div>
</div>
<div class="trip" ng-repeat="trips in flightData | filter:filterRange">
<div class="floatRight" >
<img ng-src="{{trips.imgUrl}}" alt="{{trips.airline}}" />
<button ng-click="bookingStatus = 'BOOKED!!!'" ng-bind="bookingStatus"></button>
</div>
<div>
<div class="price">Rs.{{trips.price}}</div>
<div class="airModel">{{trips.airline}}</div>
<div>{{trips.date}}</div>
<div>{{trips.from}} > {{trips.to}}</div>
<div>Depart :{{trips.departTime}}</div>
<div>Arrive :{{trips.arriveTime}}</div>
<div>seats left: {{trips.seats}}</div>
</div>
</div>
</section>
</div>
</body>
</html>
//main module of flight search engine
(function() {
'use strict';
angular.module('flightSearch',['ui-rangeSlider','validation', 'validation.rule']);
}());
/* Styles go here */
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-o-box-sizing:border-box;
box-sizing: border-box;
}
body {
height: 100%;
font-size: 75%;
font-family: "Trebuchet MS", Arial, Tahoma, sans-serif;
color: #333;
padding-top: 10px;
}
.col {
float: left;
}
.col1 {
width: 40%;
}
.col2 {
width: 60%;
}
[class*='col'] {
padding-right: 20px;
}
[class*='col']:last-of-type {
padding-right: 0;
}
.main:after {
content: "";
display: table;
clear: both;
}
.header {
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
color: #363636;
font-family: "Droid Serif", Georgia, serif;
font-size: 2.6em;
line-height: 1.8em;
font-weight: 500;
margin-top: 25px;
margin-bottom: 25px;
padding: 12px 0;
text-align: center;
}
::-webkit-input-placeholder {
/* WebKit browsers */
text-transform: none;
}
:-moz-placeholder {
/* Mozilla Firefox 4 to 18 */
text-transform: none;
}
::-moz-placeholder {
/* Mozilla Firefox 19+ */
text-transform: none;
}
:-ms-input-placeholder {
/* Internet Explorer 10+ */
text-transform: none;
}
.route {
text-transform:uppercase;
margin-left:30%;
}
.container .trip .airModel {
opacity:0.5;
}
input[type='text'] {
text-transform: uppercase;
display: block;
font-family: "Helvetica Neue", Arial, sans-serif;
border-style: solid;
border-width: 1px;
border-color: #dedede;
margin: 10px;
font-size: 1.2em;
padding-left:5px;
line-height:1.5;
width: 70%;
-moz-border-radius: 3px;
-o-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
color: #777;
-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) inset;
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) inset;
transition: border 0.15s linear 0s, box-shadow 0.15s linear 0s, color 0.15s linear 0s;
-webkit-transition: border 0.15s linear 0s, box-shadow 0.15s linear 0s, color 0.15s linear 0s;
-moz-transition: border 0.15s linear 0s, box-shadow 0.15s linear 0s, color 0.15s linear 0s;
-o-transition: border 0.15s linear 0s, box-shadow 0.15s linear 0s, color 0.15s linear 0s;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) inset;
}
.main .sidebar .tab .date {
background: url("https://cdn4.iconfinder.com/data/icons/small-n-flat/24/calendar-128.png") no-repeat rgb(255, 255, 255);
background-size: 22px;
background-position: right;
cursor:pointer;
}
.search, .floatRight button {
margin: 10px 5px 0 10px;
background-color: #999999;
border: 1px solid #999999;
font-size: 18px;
text-transform: uppercase;
font-family: "BrixSansBold", Arial, Helvetica, sans-serif;
padding: 8px 35px;
-moz-border-radius: 2px;
-o-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
color: #fff;
cursor: pointer;
-webkit-font-smoothing: antialiased;
}
.search:hover,.floatRight button:hover {
-webkit-transition: 0.25s;
-moz-transition: 0.25s;
-ms-transition: 0.25s;
-o-transition: 0.25s;
transition: 0.25s;
opacity: 0.9;
color: #fff;
text-decoration: none;
}
label,span.amber{
margin: 10px;
}
.sidebar .tab .rangeSlider {
margin-left:10px;
margin-right:10px;
}
#ui-datepicker-div{
z-index:2 !important;
}
.tabs {
list-style: none;
}
.tabs li {
display: inline;
}
.tab,
.tabs li a {
border: 1px solid #ccc;
}
.tabs li a {
color: black;
float: left;
display: block;
padding: 4px 10px;
margin-left: -1px;
position: relative;
left: 1px;
background: white;
text-decoration: none;
}
.sidebar .tabs li .highlight {
background: #ccc;
}
.group:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
.sidebar {
box-sizing: border-box;
}
.floatRight {
float: right;
}
.floatLeft {
float: left;
}
.container .trip .price {
font-size: 20pt;
}
.trip {
border: 1px solid black;
}
.trip .floatRight button{
margin-top: 44px;
vertical-align: top;
font-size: 12px;
background-color: #2aabd2;
border: 1px solid #2aabd2;
width:120px;
}
img {
width: 100px;
height: 100px;
}
.search-error{
font-size: 21pt;
}
.amber {
color: red;
}
Flight Search Engine, powered by angularjs
=============
What does it do?
----------------
+ Fetches flight data based on a advanced search query provided by user
+ Price Slider filters the searched flight data in Real time
+ integrated with validation framework
+ jquery-ui datepicker has been used for date fields
Test data[following are the static data available in this plunker]
----------------
+ Origin city is "BLR" & Destination city is "DEL", use them interchangeably to test the search
+ Departure date is "30/5/2016" & Return Date is "1/06/2016" use them interchangeably to test the search
+ Any change on the search form requires serach button to be triggered to perform search operation.
+ Price range slider doesnt require any search button triggring , it will filter out data dynamically.
Demo
----------------
[demo @ plunker](http://plnkr.co/edit/p2JMjv?p=info)
{
"data": [{
"airline": "Airindia 737",
"imgURL": "http://www.newsread.in/wp-content/uploads/2015/07/Air-India-Logo-480x470.jpg",
"from": "BLR",
"to": "DEL",
"detail": [{
"date": "2016-05-30",
"price": "3900",
"departTime": "12:00 PM",
"arriveTime": "14:00 PM",
"seats":"10"
}, {
"date": "2016-05-30",
"price": "3000",
"departTime": "17:30 PM",
"arriveTime": "19:30 PM",
"seats":"3"
}, {
"date": "2016-06-01",
"price": "2100",
"departTime": "09:00 AM",
"arriveTime": "11:00 AM",
"seats":"7"
}]
}, {
"airline": "Airindia 737",
"imgURL": "http://www.newsread.in/wp-content/uploads/2015/07/Air-India-Logo-480x470.jpg",
"from": "DEL",
"to": "BLR",
"detail": [{
"date": "2016-05-30",
"price": "3900",
"departTime": "08:10 AM",
"arriveTime": "10:10 AM",
"seats":"5"
}, {
"date": "2016-05-30",
"price": "3000",
"departTime": "15:45 PM",
"arriveTime": "17:45 PM",
"seats":"1"
}, {
"date": "2016-06-01",
"price": "2190",
"departTime": "19:20 PM",
"arriveTime": "21:20 PM",
"seats":"12"
}]
}, {
"airline": "Indigo Airbus",
"imgURL": "https://upload.wikimedia.org/wikipedia/en/9/93/IndiGo_Logo.jpg",
"from": "BLR",
"to": "DEL",
"detail": [{
"date": "2016-05-30",
"price": "3000",
"departTime": "09:00 AM",
"arriveTime": "10:50 AM",
"seats":"3"
}, {
"date": "2016-05-30",
"price": "2910",
"departTime": "14:00 PM",
"arriveTime": "15:50 PM",
"seats":"5"
}, {
"date": "2016-06-01",
"price": "3180",
"departTime": "21:10 AM",
"arriveTime": "23:00 AM",
"seats":"12"
}]
}, {
"airline": "Indigo Airbus",
"imgURL": "https://upload.wikimedia.org/wikipedia/en/9/93/IndiGo_Logo.jpg",
"from": "DEL",
"to": "BLR",
"detail": [{
"date": "2016-05-30",
"price": "2820",
"departTime": "07:50 AM",
"arriveTime": "09:40 AM",
"seats":"7"
}, {
"date": "2016-05-30",
"price": "2901",
"departTime": "11:00 AM",
"arriveTime": "13:10 PM",
"seats":"21"
}, {
"date": "2016-06-01",
"price": "3000",
"departTime": "15:20 PM",
"arriveTime": "17:10 PM",
"seats":"3"
}]
}]
}
// flight search controller,containing entire business logic
angular.module('flightSearch')
.controller('flightSearchCtrl', ['$scope', 'flightService',
function($scope, flightService) {
var data = [];
$scope.origin = "";
$scope.destination = "";
$scope.price = 3000;
$scope.roundTrip = false;
$scope.clear = true;
$scope.count = 1;
//$scope.minPrice = 0;
//$scope.maxPrice = 10000;
$scope.flightData = [];
$scope.bookingStatus = 'BOOK';
// max and min default price value for range slider
$scope.range = {
minPrice: 0,
maxPrice: 3500
};
//filter function for price range slider
$scope.filterRange = function(obj) {
return obj.price > $scope.range.minPrice && obj.price <= $scope.range.maxPrice;
};
//remote data request & processing data for view to render
$scope.search = function(org, dest) {
//get flight details for given search criteria
$scope.submitted = true;
$scope.clear = true; //clear the container before making fresh search
org = org.toUpperCase();
dest = dest.toUpperCase();
// call flightService to get remote data
flightService.search()
.then(function(response) {
data = [];
for (var index = 0; index < response.length; index++) {
var curFlight = response[index];
if (org.includes(curFlight.from) && dest.includes(curFlight.to)) {
filterRecords(curFlight, $scope.departureDate, index, $scope.price, $scope.count);
} else if ($scope.roundTrip && (org.includes(curFlight.to) && dest.includes(curFlight.from))) {
filterRecords(curFlight, $scope.returnDate, index, $scope.price, $scope.count);
}
}
$scope.flightData = data;
$scope.clear = false;
});
};
//private functions
//date equality checker
var dateComparer = function(firstDate, secondDate) {
return (new Date(firstDate).getTime() == new Date(secondDate).getTime());
};
//traverse & pick matching records from JSON array
var filterRecords = function(curFlight, date, curIndex, price,seatsCount) {
var currentRecord = curFlight.detail;
for (var index = 0; index < currentRecord.length; index++) {
var curTrip = currentRecord[index];
if (dateComparer(curTrip.date, date) && parseInt(curTrip.seats) >= seatsCount) {
curTrip.airline = curFlight.airline;
curTrip.imgUrl = curFlight.imgURL;
curTrip.from = curFlight.from;
curTrip.to = curFlight.to;
data.push(curTrip);
}
}
};
}
]);
//Flight service factory to contact remote server and ask for data
(function() {
'use strict';
angular.module('flightSearch')
.factory('flightService', ['$http', function($http) {
var flightService = {};
//private function
//get request to local resource flights.json
function getFlightsData() {
return $http.get('flights.json').then(function(resp) {
return resp.data;
});
}
//public function exposed outside
flightService.search = function(origin, destination) {
return getFlightsData().then(function(response) {
return response.data;
});
};
return flightService;
}]);
}());
//jquery ui datepicker for anugular js
(function() {
angular.module('flightSearch')
.directive("datepicker", function() {
return {
restrict: "A",
require: "ngModel",
link: function(scope, elem, attrs, ngModelCtrl) {
var updateModel = function(dateText) {
scope.$apply(function() {
ngModelCtrl.$setViewValue(dateText);
});
};
var options = {
dateFormat:"yy-mm-dd",
numberOfMonths: 2,
//minDate: 0,
maxDate: 90,
onSelect: function(dateText) {
updateModel(dateText);
}
};
elem.datepicker(options);
}
};
});
})();