<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>PHP Cargo Shipping Application</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/css/style.css">
<script language="javascript" type="text/javascript" src="/js/jquery.min.js"></script>
<script language="javascript" type="text/javascript" src="/js/jquery.addons.js"></script>
<script language="javascript" type="text/javascript" src="/js/bootstrap.min.js"></script>
<script language="javascript" type="text/javascript" src="/js/notify.min.js"></script>
<script language="javascript" type="text/javascript" src="/js/lodash.js"></script>
<script language="javascript" type="text/javascript" src="/js/q.js"></script>
<script language="javascript" type="text/javascript" src="/js/moment.min.js"></script>
<script language="javascript" type="text/javascript" src="/js/riot.min.js"></script>
<script language="javascript" type="text/javascript" src="/js/prooph.riot.app.js"></script>
<script language="javascript" type="text/javascript" src="/js/stores.js"></script>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">PHP Cargo Shipping System</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="#booking">Booking App</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container">
<main></main>
<hr>
<footer>
<p>© 2013 - 2015 by Alexander Miertsch. All rights reserved.</p>
</footer>
</div> <!-- /container -->
<script type="text/javascript">
riot.tag("assign-route", "<div if=\"{ !ready }\">
<p class=\"alert alert-info\">Fetching data from server ...</p>
</div>
<div if=\"{ ready }\">
<div class=\"row\">
<div class=\"col-md-12\">
<h2>Cargo from {getLocation(cargo.get('origin')).get('name')} to {getLocation(cargo.get('final_destination')).get('name')}<span class=\"subheading\">TrackingId: { cargo.getId() }</span></h2>
</div>
</div>
<div class=\"row\">
<div class=\"col-md-12\">
<h3>Available routes</h3>
</div>
</div>
<div id=\"route-candidate-list\" >
<route-candidate each=\"{ routeCandidates }\"
routecandidate=\"{ this }\"
trigger_event=\"{ parent.triggerEvent }\"
route_to=\"{ parent.routeTo }\"
get_location=\"{ parent.getLocation }\"></route-candidate>
</div>
</div>
", function (context) {
var self = this,
_checkReady = function() {
if (self.locations.length === 0) {
return;
}
if (! self.cargo) {
return;
}
if (! self.routeCandidates) {
return;
}
self.ready = true;
};
self.trackingId = context.trackingId;
self.locations = [];
self.routeCandidates = null;
self.cargo = null;
self.ready = false;
self.getLocation = function(unLocode) {
return _.find(self.locations, function(location) {
return location.getId() === unLocode;
});
}
self.triggerEvent = function(eventName, eventData) {
context.app.trigger(eventName, eventData);
}
self.routeTo = function(routePath) {
context.app.routeTo(context.router.get(routePath));
}
self.onLocationsRefreshed = function(locations) {
self.locations = locations;
_checkReady();
self.update();
}
self.onCargoRefreshed = function(cargo) {
if (cargo.getId() === self.trackingId) {
self.cargo = cargo;
context.app.trigger('load_cargo_routecandidates', cargo);
}
}
self.onCargoRoutecandidatesLoaded = function(routeCandidates) {
if (_.isEmpty(routeCandidates)) {
return;
}
var first = _.first(routeCandidates);
if (first.getParentId() !== self.trackingId) {
return;
}
self.routeCandidates = routeCandidates;
_checkReady();
self.update();
}
self.on("mount", function() {
context.app.on("locations_refreshed", self.onLocationsRefreshed);
context.app.on("cargo_refreshed", self.onCargoRefreshed);
context.app.on("cargo_routecandidates_loaded", self.onCargoRoutecandidatesLoaded);
context.app.trigger("refresh_locations");
context.app.trigger("refresh_cargo", new Resource(self.trackingId, 'cargo', {}));
});
self.on("unmount", function() {
context.app.off("locations_refreshed", self.onLocationsRefreshed);
context.app.off("cargo_refreshed", self.onCargoRefreshed);
context.app.off("cargo_routecandidates_loaded", self.onCargoRoutecandidatesLoaded);
});
});riot.tag("cargo-list", "<div if=\"{ !ready }\">
<img src=\"/img/ajax-loader.gif\" alt=\"loading ...\" />
</div>
<div if=\"{ ready }\">
<div class=\"row\">
<div class=\"col-md-12\">
<p if=\"{ !cargos.length }\" class=\"alert alert-danger\">The list is empty. How about booking the first <a href=\"{ router.get('cargos/add') }\">cargo</a>?</p>
<ul if=\"{ cargos.length }\" id=\"cargo-list\">
<cargo-list-entry each=\"{ cargos }\" cargo=\"{ this }\" get_location=\"{ parent.getLocation }\" router=\"{ parent.router }\"></cargo-list-entry>
</ul>
</div>
</div>
<div class=\"row\">
<div class=\"col-md-12\">
<p><a id=\"book-cargo\" class=\"btn btn-success btn-lg\" href=\"{ router.get('cargos/add') }\">Book new Cargo »</a></p>
</div>
</div>
</div>
", function (context) {
var self = this,
_checkReady = function() {
if (! self.cargos) {
return;
}
if (! self.locations) {
return;
}
self.ready = true;
self.update();
};
self.ready = false;
self.router = context.router;
self.getLocation = function(unLocode) {
return _.find(self.locations, function(location) {
return location.getId() === unLocode;
})
}
self.onCargoListRefreshed = function(cargos) {
self.cargos = cargos;
_checkReady();
self.update();
}
self.onLocationsRefreshed = function(locations) {
self.locations = locations;
_checkReady();
self.update();
}
self.on('mount', function() {
context.app.on('cargo_list_refreshed', self.onCargoListRefreshed);
context.app.on('locations_refreshed', self.onLocationsRefreshed);
context.app.trigger('refresh_cargo_list');
context.app.trigger('refresh_locations');
})
self.on("unmount", function() {
context.app.off('cargo_list_refreshed', self.onCargoListRefreshed);
context.app.off('locations_refreshed', self.onLocationsRefreshed);
})
});riot.tag("cargo-list-entry", "<li>
<a href=\"{ router.get('cargos/' + cargo.getId()) }\" class=\"cargo-details-link\">{ cargo.getId() }</a> From: { getLocation(cargo.get('origin')).get('name') } To: { getLocation(cargo.get('final_destination')).get('name') }
</li>
", function (context) {
var self = this;
self.cargo = context.cargo;
self.getLocation = context.get_location;
console.log(self.getLocation(self.cargo.get('origin')));
self.router = context.router;
});riot.tag("jb-assign-route", "<h1>Assign Route to Cargo</h1>
", function (context) {
});riot.tag("jb-cargo-list", "<h1>Cargo Booking Application</h1>
<p>Choose a Cargo from the list to view it's routing details or book a new Cargo.</p>
", function (context) {
});riot.tag("jb-new-cargo", "<h1>Book a new Cargo</h1>
<p>In the ChapterFour version of the Shipping System you can specify origin and destination of a Cargo. In a second step, the booking system suggest possible routes and you can assign the new Cargo to one of the routes.</p>
", function (context) {
});riot.tag("jb-show-cargo", "<h1>Cargo Overview</h1>
", function (context) {
});riot.tag("new-cargo", "<div class=\"row\">
<div class=\"col-md-12\">
<form id=\"new-cargo-form\">
<div class=\"form-group\">
<label>Origin</label>
<select name=\"origin\" class=\"form-control\" onchange=\"{ onChangeOrigin }\">
<option each=\"{ locations }\" value=\"{ unLocode }\">{ name }</option>
</select>
</div>
<div class=\"{ form-group: true, has-error: hasError }\">
<label>Destination</label>
<select name=\"final_destination\" class=\"form-control\" onchange=\"{ onChangeDestination }\">
<option each=\"{ locations }\" value=\"{ unLocode }\">{ name }</option>
</select>
<span if=\"{ hasError }\" class=\"help-block\">Origin and Destination must not be the same location!</span>
</div>
<div class=\"form-group\">
<input type=\"submit\" name=\"save\" value=\"assign to itinerary\" class=\"{ btn: true, btn-success: true, disabled: !isValid }\" onclick=\"{ onSave }\">
</div>
</form>
</div>
</div>
", function (context) {
var self = this,
_checkIsValid = function () {
self.hasError = false;
self.isValid = false;
if (!_.isEmpty(self.origin.value) && !_.isEmpty(self.final_destination.value)) {
if (self.origin.value === self.final_destination.value) {
self.hasError = true;
self.isValid = false;
return;
}
self.isValid = true;
}
};
self.locations = [];
self.hasError = false;
self.isValid = false;
self.locations.push({
unLocode : '',
name : 'Fetching locations from server ...'
});
self.onChangeOrigin = function (e) {
_checkIsValid();
}
self.onChangeDestination = function (e) {
_checkIsValid();
}
self.onSave = function (e) {
e.preventDefault();
_checkIsValid();
if (! self.isValid) {
return;
}
context.app.one('cargo_created', function(cargo) {
window.location.hash = context.router.get('cargos/' + cargo.getId() + '/assign_route');
});
context.app.trigger('create_cargo', {origin: self.origin.value, destination: self.final_destination.value});
}
self.on('mount', function () {
context.app.one("locations_refreshed", function(locations) {
self.locations = [];
self.locations.push({
unLocode : '',
name : 'Select a location'
})
_.forEach(locations, function (location) {
self.locations.push(location.getProps())
});
self.update();
});
context.app.trigger("refresh_locations");
});
});riot.tag("route-candidate-leg", "<div class=\"row highlight\">
<div class=\"col-md-12\">
<strong>{context.get_location(leg.load_location).get('name')}</strong> {print_time(leg.load_time)} <i class=\"glyphicon glyphicon-arrow-right\"></i> <strong>{context.get_location(leg.unload_location).get('name')}</strong> {print_time(leg.unload_time)}
</div>
</div>
", function (context) {
var self = this;
self.leg = context.leg;
self.context = context;
self.print_time = function(timeStr) {
return moment(timeStr).format('YYYY/MM/DD HH:mm');
}
});riot.tag("route-candidate", "<div class=\"row\">
<div class=\"col-md-12\">
<div class=\"panel panel-default itinerary\">
<div class=\"panel-body\">
<route-candidate-leg each=\"{ routeCandidate.get('legs') }\" leg=\"{ this }\" get_location=\"{ parent.context.get_location }\"></route-candidate-leg>
<p> </p>
<div class=\"row\">
<div class=\"col-md-12\">
<a href=\"#\"
class=\"btn btn-success assign-cargo-btn\" onclick=\"{ onAssignCargo }\">Assign to Cargo</a>
</div>
</div>
</div>
</div>
</div>
</div>
", function (context) {
var self = this;
self.context = context;
self.routeCandidate = context.routecandidate;
self.onAssignCargo = function(e) {
e.preventDefault();
context.trigger_event('assign_route_candidate_to_cargo', self.routeCandidate);
context.route_to('');
}
});riot.tag("show-cargo", "<div if=\"{ !ready }\">
<img src=\"/img/ajax-loader.gif\" alt=\"loading ...\" />
</div>
<div if=\"{ ready }\">
<div class=\"row\">
<div class=\"col-md-12\">
<h2>Cargo from { getLocation(cargo.get('origin')).get('name') } ({ cargo.getId() })</h2>
</div>
</div>
<div class=\"row\">
<div class=\"col-md-12\">
<h3>route specification</h3>
</div>
</div>
<div class=\"row\">
<div class=\"col-md-6 alert-info\">
<strong>From: </strong>{ getLocation(cargo.get('origin')).get('name') }
</div>
<div class=\"col-md-6 alert-info\">
<strong>To: </strong>{ getLocation(cargo.get('final_destination')).get('name') }
</div>
</div>
<div class=\"row\">
<div class=\"col-md-12\">
<h3>Itinerary</h3>
</div>
</div>
<div class=\"row\">
<div class=\"col-md-12\">
<div class=\"panel panel-default itinerary\">
<div class=\"panel-body\">
<p class=\"alert alert-warning\" if=\"{ cargo.get('legs').length == 0 }\"><i class=\"glyphicon glyphicon-warning-sign\"></i> Cargo is not assigned to a route! <a href=\"{ router.get('cargos/' + cargo.getId() + '/assign_route') }\" class=\"btn btn-primary\">assign now</a></p>
<div if=\"{ cargo.get('legs').length > 0 }\">
<route-candidate-leg each=\"{ cargo.get('legs') }\" leg=\"{ this }\" get_location=\"{ parent.getLocation }\"></route-candidate-leg>
</div>
</div>
</div>
</div>
</div>
</div>
", function (context) {
var self = this,
_checkReady = function() {
if (! self.cargo) {
return;
}
self.ready = true;
};
self.ready = false;
self.trackingId = context.trackingId;
self.router = context.router;
self.getLocation = function(unLocode) {
return _.find(self.locations, function(location) {
return location.getId() === unLocode;
})
}
self.onCargoRefreshed = function(cargo) {
if (! cargo.getId() === self.trackingId) {
return;
}
self.cargo = cargo;
_checkReady();
self.update();
}
self.onLocationsRefreshed = function(locations) {
self.locations = locations;
_checkReady();
self.update();
}
self.on("mount", function() {
context.app.on('cargo_refreshed', self.onCargoRefreshed);
context.app.on('locations_refreshed', self.onLocationsRefreshed);
context.app.trigger('refresh_cargo', new Resource(self.trackingId, 'cargo', {}));
context.app.trigger('refresh_locations');
});
self.on("unmount", function() {
context.app.off('cargo_refreshed', self.onCargoRefreshed);
context.app.off('locations_refreshed', self.onLocationsRefreshed);
})
});riot.tag("booking-app", "<div class=\"jumbotron\">
<jb-cargo-list if=\"{ activeMode == 'cargo-list' }\"></jb-cargo-list>
<jb-show-cargo if=\"{ activeMode == 'show-cargo' }\"></jb-show-cargo>
<jb-new-cargo if=\"{ activeMode == 'new-cargo' }\"></jb-new-cargo>
<jb-assign-route if=\"{ activeMode == 'assign-route' }\"></jb-assign-route>
</div>
<div id=\"booking-app-container\">
<img src=\"/img/ajax-loader.gif\" alt=\"loading ...\" />
</div>
<footer>
<div class=\"row\">
<div class=\"col-md-12\">
<a href=\"{ router.get('') }\" class=\"btn btn-default icon icon-align-left icon-arrow-left pull-right\">back to overview</a>
</div>
</div>
</footer>
", function (context) {
var self = this,
renderCargoList = function (mixins) {
context.app.renderInto(self, "#booking-app-container", "cargo-list", mixins);
self.activeMode = "cargo-list";
self.update();
},
renderNewCargo = function (mixins) {
context.app.renderInto(self, "#booking-app-container", "new-cargo", mixins);
self.activeMode = "new-cargo";
self.update();
},
renderShowCargo = function (mixins) {
context.app.renderInto(self, "#booking-app-container", "show-cargo", mixins);
self.activeMode = "show-cargo";
self.update();
},
renderAssignRoute = function (mixins) {
context.app.renderInto(self, "#booking-app-container", "assign-route", mixins);
self.activeMode = "assign-route";
self.update();
},
_routeListener = function (con) {
var rm = con.routeMatch;
if (! rm[0]) {
renderCargoList({router : con.router});
return;
}
if (rm[0] == "cargos") {
if (! rm[1]) {
renderCargoList({router : con.router});
return;
}
if (rm[1] == "add") {
renderNewCargo({router : con.router});
return;
}
if (! rm[2]) {
renderShowCargo({router : con.router, trackingId : rm[1]});
}
if (rm[2] == "assign_route") {
renderAssignRoute({router : con.router, trackingId : rm[1]})
}
}
};
self.router = context.router;
self.on("mount", function () {
context.router.on("route", _routeListener);
})
});riot.tag("home", "<div class=\"jumbotron\">
<h1>Welcome to the PHP DDD Cargo Sample</h1>
<p><strong>Congratulations!</strong> You have successfully installed the Container Shipping System. You are currently running the <strong>{chapter}</strong> Version of the Application.</p>
<p><a class=\"btn btn-success btn-lg\" href=\"https://github.com/prooph/php-ddd-cargo-sample\" target=\"_blank\">Fork PHP DDD Cargo Sample on GitHub »</a></p>
</div>
<div class=\"row\">
<div class=\"col-md-12\">
<div class=\"panel panel-default\">
<div class=\"panel-heading\">
<h3 class=\"panel-title\">Goal of the Sample Application</h3>
</div>
<div class=\"panel-body\">
<p>The Cargo Shipping System is based on the example used in Eric Evans book Domain-Driven Design: <i>Tackling Complexity in the Heart of Software</i>. It\'s a PHP Version of the Cargo Sample to show you how DDD can be applied to a PHP Project.</p>
</div>
</div>
</div>
</div>
", function (context) {
var self = this;
self.chapter = context.app.config.chapter;
});riot.tag("main", "<div class=\"content\">
</div>
", function(context) {
var self = this,
_routeListener = function (con) {
var rm = con.routeMatch;
if (!rm[0]) {
con.app.renderInto(self, ".content", "home");
con.app.removeRouterForRoutePath(["booking"]);
return;
}
if (rm[0] == "booking") {
var subRouter = con.router.getSubRouter("booking");
con.app.renderInto(self, ".content", "booking-app", {router : subRouter});
subRouter.route(rm, con);
return;
}
};
context.router.on("route", _routeListener);
});
var CargoUI = ProophRiot.App.create({
config : {
url : {
apiRoot: '/api/',
forResource : function (resource) {
return this.apiRoot + this.resourcePlural(resource.getName()) + '/' + resource.getId();
},
forResourceCollection : function (resourceName) {
return this.apiRoot + this.resourcePlural(resourceName);
},
resourcePlural: function(resourceName) {
return resourceName + 's';
},
forChildResource : function(childResource) {
return this.apiRoot + this.resourcePlural(childResource.getParentName())
+ '/' + childResource.getParentId()
+ '/' + this.resourcePlural(childResource.getName())
+ '/' + childResource.getId()
},
forChildResourceCollection : function(parentResource, childResourceName) {
return this.apiRoot + this.resourcePlural(parentResource.getName())
+ '/' + parentResource.getId()
+ '/' + this.resourcePlural(childResourceName);
}
},
chapter: "ChapterFour"
}
});
var locationsStore = new LocationStore(CargoUI);
var cargoStore = new CargoStore(CargoUI);
var routeCandidateStore = new RouteCandidateStore(CargoUI);
$(function () {
CargoUI.bootstrap("main").ready();
});
</script>
</body>
</html>