diff --git a/dist/index.html b/dist/index.html index b873b1e..24f2455 100644 --- a/dist/index.html +++ b/dist/index.html @@ -2,19 +2,144 @@ - My JavaScript App + Back Trek + + + + + + +
- +
+

Welcome to Travel Trek

+ + + Add Trip +

-
-
- - - - + +
+
+ + +
+ + + + +
+
+ + + + + + + + + + +
NameContinentCategoryWeeksCost
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + diff --git a/src/app.js b/src/app.js index e7af594..36480bc 100644 --- a/src/app.js +++ b/src/app.js @@ -6,8 +6,220 @@ import _ from 'underscore'; import './css/foundation.css'; import './css/style.css'; +// Our components +import Trip from './app/models/trip'; +import Reservation from './app/models/reservation'; +import TripList from './app/collections/trip_list'; console.log('it loaded!'); -$(document).ready( () => { - $('main').html('

Hello World!

'); +const tripList = new TripList() + + +const TRIP_FIELDS = ['name', 'continent', 'about', 'category', 'weeks', 'cost']; +const RESERVATION_FIELDS = ['name', 'email', 'age']; + +let tripsTemplate; +let tripTemplate; + +// Clear status messages +const clearStatus = function clearStatus() { + $('#status-messages ul').html(''); + $('#status-messages').hide(); +}; + +// Add a new status message +const reportStatus = function reportStatus(status, message) { + console.log(`Reporting ${ status } status: ${ message }`); + + const statusHTML = `
  • ${ message }
  • `; + + // note the symetry with clearStatus() + $('#status-messages ul').append(statusHTML); + $('#status-messages').show(); +}; + +const render = function render(tripList){ + const $tripsTableElement = $('#trip-list'); + $tripsTableElement.html(''); + + tripList.forEach((trip) => { + const generatedHTML = $(tripsTemplate(trip.attributes)); + generatedHTML.on('click',(event) => { + $('#trips').hide(); + $('#trip').show(); + trip.fetch({ + success (model, response){ + renderTrip(model); + } + }); + }) //.on + $tripsTableElement.append(generatedHTML); + }); //tripList +}; // render + + +const renderTrip = function renderTrip(trip){ + const $tripTableElement = $('#trip'); + $tripTableElement.html(''); + console.log(trip.attributes); + + const generatedHTML = tripTemplate(trip.attributes); + $tripTableElement.html(generatedHTML); + + $('#reservation-form').on('submit', addReservHandler); + + $('th.sort').removeClass('current-sort-field'); + $(`th.sort.${ tripList.comparator }`).addClass('current-sort-field'); +}; + +const loadTrips = function loadTrips() { + tripList.fetch({ + success(model, response) { + render(model); + } + }); +}; + +const readTripFormData = function readTripFormData() { + const tripData = {}; + TRIP_FIELDS.forEach((field) => { + const $inputElement = $(`#add-trip-form input[name="${ field }"]`); + const value = $inputElement.val(); + + // Don't take empty strings, so that Backbone can + // fill in default values + if (value != '') { + tripData[field] = value; + } + + $inputElement.val(''); + }); + + console.log(tripData); + return tripData; +}; + +// Reserving a trip +const readReservFormData = function readReservFormData() { + const reservData = {}; + RESERVATION_FIELDS.forEach((field) => { + const $inputReservElement = $(`#reservation-form input[name="${ field }"]`); + const value = $inputReservElement.val(); + + if (value != '') { + reservData[field] = value; + } + + $inputReservElement.val(''); + }); + + console.log("Read reserve data"); + console.log(reservData); + return reservData; +}; + +const handleValidationFailures = function handleValidationFailures(errors) { + for (let field in errors) { + for (let problem of errors[field]) { + reportStatus('error', `${field}: ${problem}`); + } + } +}; + +// Add trip +const addTripHandler = function(event) { + event.preventDefault(); + // $('#status-messages').html('') + + const trip = new Trip(readTripFormData()); + console.log(trip); + + + if (!trip.isValid()) { + handleValidationFailures(trip.validationError); + return; + } + + trip.save({}, { + success: (model, response) => { + tripList.add(model); + console.log('Yay, trip was saved successfully!'); + reportStatus('success', 'Yay, trip was saved successfully!'); + }, + error: (model, response) => { + console.log('Failed to save trip! Server response:'); + console.log(response); + + // After server-side validations failed, we have to remove this bad + // trip from the list + tripList.remove(model); + + handleValidationFailures(response.responseJSON["errors"]); + }, + }); +}; + +// Add Reservation +const addReservHandler = function addReservHandler(event) { + event.preventDefault(); + const reserve = new Reservation(readReservFormData()); + reserve.set('trip_id', $(this).data('tripId')); + + + if (!reserve.isValid()) { + handleValidationFailures(reserve.validationError); + return; + } + + reserve.save({}, { + success: (model, response) => { + console.log('Congratulations, you successfully reserved a trip!'); + + reportStatus('success', 'Congratulations, you successfully reserved a trip!'); + }, + error: (model, response) => { + console.log('Failed to save reservation. Server response: '); + console.log(response); + + handleValidationFailures(response.responseJSON["errors"]); + } + }) +} + +$(document).ready(() => { + tripsTemplate = _.template($('#trips-template').html()); + tripTemplate = _.template($('#trip-template').html()); + + // $('#trips').hide(); + + // Retrieving all trips by clicking the button + $('#load').on('click', function(){ + $('#trips').show(); + loadTrips() + $('#show_form').hide(); + }); + + $('header').on('click', '#add_trip', function() { + $('#status-messages').hide(); + $('#show_form').show(); + + }); + + $('#add-trip-form').on('submit', addTripHandler); + + $('#status-messages button.clear').on('click', (event) => { + $('#status-messages ul').html(''); + $('#status-messages').hide(); + $('#show-form').hide(); + }) + // sorting by header element + tripList.on('sort', render); + TRIP_FIELDS.forEach((field) => { + const headerData = $(`th.sort.${ field }`); + headerData.on('click', (event) => { + tripList.comparator = field; + tripList.sort(); + }); + }); + $('#status-messages button.clear').on('click', clearStatus); }); diff --git a/src/app/collections/trip_list.js b/src/app/collections/trip_list.js new file mode 100644 index 0000000..51fc0db --- /dev/null +++ b/src/app/collections/trip_list.js @@ -0,0 +1,15 @@ +import Backbone from 'backbone'; +import Trip from '../models/trip'; + +const TripList = Backbone.Collection.extend({ + model: Trip, + url: 'https://ada-backtrek-api.herokuapp.com/trips', + + // parse: function (response) { + // return response; + // }, + comparator: 'name' + // +}); + +export default TripList; diff --git a/src/app/models/reservation.js b/src/app/models/reservation.js new file mode 100644 index 0000000..f2cddd3 --- /dev/null +++ b/src/app/models/reservation.js @@ -0,0 +1,25 @@ +import Backbone from 'backbone'; + +const Reservation = Backbone.Model.extend({ + + url: function() { + return `https://ada-backtrek-api.herokuapp.com/trips/${this.get('trip_id')}/reservations`; + }, + + validate(attributes) { + const errors = {}; + if (!attributes.name) { + errors.name = ['cannot be blank']; + } + + if (!attributes.email) { + errors.weeks = ['cannot be blank']; + } + + if (Object.keys(errors).length < 1) { + return false; + } + return errors; + }, +}); +export default Reservation; diff --git a/src/app/models/trip.js b/src/app/models/trip.js new file mode 100644 index 0000000..db7caf0 --- /dev/null +++ b/src/app/models/trip.js @@ -0,0 +1,48 @@ +import Backbone from 'backbone'; + +const Trip = Backbone.Model.extend({ + urlRoot: 'https://ada-backtrek-api.herokuapp.com/trips/', + // defaults: { + // category: 'Unknown', + // continent: 'Unknown', + // about: 'Unknown' + // }, + + validate(attributes) { + + const errors = {}; + if (!attributes.name) { + errors.name = ['cannot be blank']; + } + + if (!attributes.continent) { + errors.continent = ['cannot be blank']; + } + + if (!attributes.about) { + errors.about = ['cannot be blank']; + } + + if (!attributes.category) { + errors.category = ['cannot be blank']; + } + + if (!attributes.weeks) { + errors.weeks = ['cannot be blank']; + } else if (attributes.weeks > 100) { + errors.weeks = ['must be less than 100 weeks']; + } + + if (!attributes.cost) { + errors.cost = ['cannot be blank']; + } + + if (Object.keys(errors).length < 1) { + return false; + } + return errors; + }, + +}); + +export default Trip; diff --git a/src/css/style.css b/src/css/style.css index b7b2d44..12fd86b 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -1,2 +1,150 @@ /* Your styles go here! */ +.header{ + text-align: center; + padding-bottom: 2%; + background-image: url("http://2.bp.blogspot.com/-cIFih6LY4x8/U2i6PX7HYTI/AAAAAAAAFyk/oGQO3pXUlTc/s1600/Twitter-Header-1500x500-Size-18.jpg"); + } + + .header_word{ + padding-top: 5%; + color: #FF5733; + font-weight: 700; + font-family: fantasy; + font-style: italic; + } + #load, #add_trip{ + text-align: center; + border: 1px black solid; + border-radius: .2em ; + background-color: lightblue; + color: black; + margin-top: 2%; + } + + #load:hover, #add_trip:hover, input.button:hover{ + background-color: lightsteelblue; + } + + /*********BODY************/ + + +#trips{ + background-color: AZURE; + color: navy; + font-family: sans-serif; + font-size: 10pt; + + padding-top: 5%; + display: none; +} + +tr th{ + padding-right: 22%; + padding-bottom: 2%; +} + +tr td{ + padding-bottom: 1%; +} + +tr:hover.selected td, +tr:hover td { + background-color: #FFEFC6; + cursor: pointer; +} + + +#trip, #reservation-form{ + border-radius: .6em; + background-color: AZURE; + color: #FF5733; +} + +#trip{ + text-align: center; +} + +.reserve, .tripname, #reservation-form{ + text-align: center; +} + +#reservation-form{ + padding-left: 5%; +} + +.button a, input.button{ + text-align: center; + border: 1px black solid; + border-radius: .2em ; + background-color: lightblue; + color: black; + margin-top: 2%; +} +input.button{ + text-align: center; +} +p{ + color: maroon; +} +b{ + color: navy; +} + +th:hover{ + background-color: blue; + color: white; + cursor: pointer; +} + + +#show_form{ + display: none; + text-align: center; + padding-left: 15%; + width: 80%; +} +/*footer{ + position: absolute; + right: 0; + bottom: 0; + left: 0; + padding: 1rem; + background-color: #efefef; + text-align: center; +}*/ + +/* Styles for error section at the top */ +#status-messages { + background-color: #dfdfdf; + + /* Hide by default */ + display: none; + + border-radius: 1rem; + margin: 1rem; + height: 100%; + padding: .8rem; +} + +#status-messages ul { + list-style: none; + margin-left: 0; +} + +#status-messages button.clear { + margin-top: .2rem; + height: 100%; +} + +#status-messages button.clear img { + width: 2rem; +} + +#status-messages .error { + color: red; +} + +#status-messages .success { + color: green; +}