diff --git a/dist/index.html b/dist/index.html index b873b1e..50f3718 100644 --- a/dist/index.html +++ b/dist/index.html @@ -4,17 +4,159 @@ My JavaScript App + + + + + + +
+

BackTREK

+
+
+ + + + +
+

Trips

+ + + + + + + + + + +
NameContinentCategoryWeeksCost
+
+ +
+ + + + + + diff --git a/images/sunset.jpeg b/images/sunset.jpeg new file mode 100644 index 0000000..87de2f0 Binary files /dev/null and b/images/sunset.jpeg differ diff --git a/images/trek_icon.png b/images/trek_icon.png new file mode 100644 index 0000000..884ef33 Binary files /dev/null and b/images/trek_icon.png differ diff --git a/package-lock.json b/package-lock.json index f830985..c24daf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3144,6 +3144,22 @@ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", "dev": true }, + "foundation-sites": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/foundation-sites/-/foundation-sites-6.2.4.tgz", + "integrity": "sha1-VUdGyfPVBb1WRrTcwD+KmUHmf3s=", + "requires": { + "jquery": "2.2.4", + "what-input": "2.1.1" + }, + "dependencies": { + "jquery": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz", + "integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI=" + } + } + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -6890,6 +6906,12 @@ "unpipe": "1.0.0" } }, + "raw-loader": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", + "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", + "dev": true + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -7273,6 +7295,15 @@ "ajv": "5.3.0" } }, + "script-loader": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/script-loader/-/script-loader-0.7.2.tgz", + "integrity": "sha512-UMNLEvgOAQuzK8ji8qIscM3GIrRCWN6MmMXGD4SD5l6cSycgGsCo0tX5xRnfQcoghqct0tjHjcykgI1PyBE2aA==", + "dev": true, + "requires": { + "raw-loader": "0.5.1" + } + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -8410,6 +8441,11 @@ "integrity": "sha1-Dhh4HeYpoYMIzhSBZQ9n/6JpOl0=", "dev": true }, + "what-input": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/what-input/-/what-input-2.1.1.tgz", + "integrity": "sha1-hrbgM41nCp/ZNokrj1EVxP4NHQs=" + }, "whatwg-fetch": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", diff --git a/package.json b/package.json index 5860ddc..e09b5fe 100644 --- a/package.json +++ b/package.json @@ -51,12 +51,14 @@ "jasmine-console-reporter": "^2.0.1", "jasmine-expect": "^3.8.1", "module-resolver": "^1.0.0", + "script-loader": "^0.7.2", "style-loader": "^0.19.0", "webpack": "^3.8.1", "webpack-dev-server": "^2.9.4" }, "dependencies": { "backbone": "^1.3.3", + "foundation-sites": "6.2.4", "jquery": "^3.2.1", "underscore": "^1.8.3" } diff --git a/src/app.js b/src/app.js index e7af594..ae5ef7f 100644 --- a/src/app.js +++ b/src/app.js @@ -1,13 +1,297 @@ // Vendor Modules -import $ from 'jquery'; +// import $ from 'jquery'; + +// load jquery and foundation in the window scope +import 'script-loader!jquery' +import 'script-loader!foundation-sites' + import _ from 'underscore'; // CSS import './css/foundation.css'; import './css/style.css'; +// MODELS +import Trip from './app/models/trip'; +import Reservation from './app/models/reservation'; + +// COLLECTION +import TripList from './app/collections/trip_list'; + +// INITITAL SET UP CHECK console.log('it loaded!'); +// GENERATE TRIPLIST COLLECTION +const tripList = new TripList(); +// console.log('TRIPLIST:'); +// console.log(tripList); + +let tripTemplate; + +const render = function render(tripList) { + console.log('TOP OF RENDER') + console.log(tripList); + const $tripList = $('#trip-list'); + //Clears the list so when you re-render it doesn't print duplicates + $tripList.empty(); + + tripList.forEach((trip) => { + $tripList.append(tripTemplate(trip.attributes)); + console.log(trip.attributes); + }); +}; + +// loadTrips FUNCTION +const loadTrips = function loadTrips() { + tripList.fetch(); + tripList.on('update', render, tripList); +}; + +// getTripDetails FUNCTION +let singleTripTemplate; + +const getTripDetails = function getTripDetails(attrID) { + console.log('View Trip Row clicked'); + + const trip = new Trip({ id: attrID }); + trip.fetch({success: events.renderSingleTrip, error: events.failRenderTrip}); + + console.log(trip) + + // $('#reservation-form').hide(); +}; + +const events = { + renderSingleTrip(trip) { + console.log('RENDERING SINGLE TRIP'); + const $tripDetails = $('#single-trip-details'); + //Clears the list so when you re-render it doesn't print duplicates + $tripDetails.empty(); + $tripDetails.append(singleTripTemplate(trip.attributes)); + console.log(trip.attributes); + }, + failRenderTrip() { + console.log('FAILED TO RENDER SINGLE TRIP INFO'); + $('#single-trip-table').append('ERROR: Trip details were not successfully loaded.'); + }, + succesReservation(reservation, response) { + console.log('RESERVATION SUCCESSFULLY SUBMITTED') + console.log(reservation); + + $('#reservation-form .user-input').val(''); + + $('#statusMessageModal h2').empty(); + $('#statusMessageModal h2').append('Reservation Status'); + $('#statusMessageModal ul').empty(); + $('#statusMessageModal p').empty(); + $('#statusMessageModal p').append('Your trip is now reserved!'); + $('#statusMessageModal').foundation('open'); + }, + failReservation() { + console.log('RESERVATION SUBMISSION FAILURE') + console.log(reservation); + + $('#statusMessageModal h2').empty(); + $('#statusMessageModal h2').append('Reservation Status'); + $('#statusMessageModal ul').empty(); + $('#statusMessageModal p').empty(); + $('#statusMessageModal p').append('Sorry, your request could not be completed.'); + $('#statusMessageModal').foundation('open'); + }, + succesTripAdd(trip, response) { + console.log('TRIP SUCCESSFULLY SUBMITTED') + console.log(trip); + console.log('TRIP SUBMISSION RESPONSE') + console.log(response); + + $('#new-trip-form .user-input').val(''); + + $('#statusMessageModal h2').empty(); + $('#statusMessageModal h2').append('New Trip Submission Status'); + $('#statusMessageModal ul').empty(); + $('#statusMessageModal p').empty(); + $('#statusMessageModal p').append('Your trip is now added!'); + $('#statusMessageModal').foundation('open'); + }, + failTripAdd(trip, response) { + console.log('TRIP SUBMISSION FAILURE') + console.log(trip); + console.log(); + console.log('TRIP SUBMISSION FAILURE RESPONSE') + console.log(response); + console.log(); + + $('#statusMessageModal h2').empty(); + $('#statusMessageModal h2').append('New Trip Submission Status'); + + $('#statusMessageModal p').empty(); + $('#statusMessageModal p').append('Sorry, your request could not be completed.'); + + $('#statusMessageModal ul').empty(); + console.log("Response JSON:"); + console.log(response.responseJSON); + for (let key in response.responseJSON.errors) { + + response.responseJSON.errors[key].forEach((error) => { + $('#status-messages ul').append(`
  • ${key}:${error}
  • `); + }) + } + $('#statusMessageModal').foundation('open'); + }, + sortTrips(event) { + console.log(event); + console.log(this); + //get classes split by white space (regex /\s+/) or split.(' ') + const classes = $(this).attr('class').split(/\s+/); + + const fields = ['name', 'continent', 'category', 'weeks', 'cost']; + //Styling: so clicked column shows what table is sorted by + $('.current-sort-field').removeClass('current-sort-field'); + $(this).addClass('current-sort-field'); + + classes.forEach((className) => { + if (fields.includes(className)) { + if (className === tripList.comparator) { + tripList.models.reverse(); + tripList.trigger('sort', tripList); + } + else { + tripList.comparator = classes[1]; + tripList.sort(); + } + } + }); + }, +}; + +// makeTripReservation FUNCTION +const makeTripReservation = function makeTripReservation(id) { + const trip = tripList.findWhere({id: parseInt(id)}); + + const fields = ['name', 'age', 'email']; + + const reservationData = {}; + fields.forEach( (field) => { + const val = $(`#reservation input[name=${field}]`).val(); + if (val != '') { + reservationData[field] = val; + } + }); + + const reservation = trip.reserve(reservationData); + // Validations: Testing missing parameters + //const reservation = trip.reserve({age: "21", email:"jedrzo@ada.com"}); + // Validations Test with simple error message: + // reservation.on("invalid", function(model, error) { + // $('#statusMessageModal ul').append('
  • Something went wrong!
  • '); + // $('#statusMessageModal').foundation('open'); + // }); + //reservation.save(); + + reservation.on("invalid", function(model, errors) { + console.log('INSIDE .ONinvalidevent'); + $('#statusMessageModal h2').empty(); + $('#statusMessageModal h2').append('Reservation Status'); + $('#statusMessageModal p').empty(); + $('#statusMessageModal p').append('Sorry, your request could not be completed. Please resolve the following:'); + $('#statusMessageModal ul').empty(); + for (let key in errors) { + $('#statusMessageModal ul').append(`
  • ${key.charAt(0).toUpperCase() + key.slice(1)}: ${errors[key]}
  • `); + $('#statusMessageModal').foundation('open'); + }; + }); + + reservation.save({}, { + success: events.succesReservation, + error: events.failReservation, + }); +}; + +//addNewTrip FUNCTION +const addNewTrip = function addNewTrip(details) { + console.log('ADD TRIP BUTTON CLICKED') + + const fields = ['name', 'continent','category', 'weeks', 'cost']; + + const tripData = {}; + + fields.forEach( (field) => { + const val = $(`#new-trip-form input[name=${field}]`).val(); + if (val != '') { + tripData[field] = val; + } + }); + + const trip = new Trip(tripData); + console.log('THIS IS A NEW TRIP'); + console.log(trip); + + trip.on("invalid", function(model, errors) { + console.log('INSIDE TRIP VALIDATION'); + $('#statusMessageModal h2').empty(); + $('#statusMessageModal h2').append('New Trip Submission Status'); + $('#statusMessageModal p').empty(); + $('#statusMessageModal p').append('Sorry, your request could not be completed. Please resolve the following:'); + $('#statusMessageModal ul').empty(); + + for (let key in errors) { + $('#statusMessageModal ul').append(`
  • ${key.charAt(0).toUpperCase() + key.slice(1)}: ${errors[key]}
  • `); + $('#statusMessageModal').foundation('open'); + }; + }); + console.log('IS THIS TRIP INVALID?'); + + trip.save({}, { + success: events.succesTripAdd, + error: events.failTripAdd, + }); +}; + + $(document).ready( () => { - $('main').html('

    Hello World!

    '); + $(document).foundation(); + $('#all-trips-table').hide(); + $('#single-trip-details').hide(); + + tripTemplate = _.template($('#trip-template').html()); + singleTripTemplate = _.template($('#single-trip-template').html()); + + // EVENTS + // To view All Trips + $('#all-trips').on('click', function() { + $('#all-trips-table').show(); + loadTrips(); + }); + + // To view a single Trip + $('#all-trips-table').on('click', '.trip', function() { + $('#single-trip-details').show(); + const tripID = $(this).attr('data-id'); + getTripDetails(tripID); + }); + + // // To Show Trip Reservation Form + // $('#single-trip-template').on('click', '#reserve-trip-button', function() { + // $('#reservation-form').show(); + // }); + + // To Show New Trip Form Modal + $('#single-trip-details').on('submit', '#reservation', function(e) { + e.preventDefault(); + const tripID = $(this).attr('data-id'); + makeTripReservation(tripID); + }); + + // To Add a New TRIP + $('#new-trip-form').on('submit', function(e) { + e.preventDefault(); + addNewTrip(); + }); + + // To Sort Trip Table Columns + $('.sort').click(events.sortTrips); + tripList.on('sort', render, tripList); + + + }); diff --git a/src/app/collections/trip_list.js b/src/app/collections/trip_list.js new file mode 100644 index 0000000..178c320 --- /dev/null +++ b/src/app/collections/trip_list.js @@ -0,0 +1,16 @@ +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(response) { + response.forEach(function(tripAttrs){ + tripAttrs.name = tripAttrs.name.trim(); + }); + return response; + console.log(response); + }, +}); + +export default TripList; diff --git a/src/app/models/reservation.js b/src/app/models/reservation.js new file mode 100644 index 0000000..d110365 --- /dev/null +++ b/src/app/models/reservation.js @@ -0,0 +1,46 @@ +import Backbone from 'backbone'; + +const Reservation = Backbone.Model.extend({ + initialize: function(attributes) { + // just to see what reservation attributes look like from form + console.log(attributes); + }, + validate: function(attributes) { + console.log('What are the attributes in validate'); + console.log(attributes); + // return empty array as test + // return []; + const errors = {}; + + if (!attributes.name) { + errors['name'] = 'cannot be blank'; + } + + if (!attributes.age) { + errors['age'] = "cannot be blank"; + } else if( isNaN(parseInt(attributes.age)) ) { + errors['age'] = "must be a number"; + } else if ( parseInt(attributes.age) < 0 || parseInt(attributes.age) > 150) { + errors['age'] = "must be valid"; + } + + if (!attributes.email) { + errors['email'] = 'cannot be blank'; + } + + console.log('ERRORS:'); + console.log(errors); + + if ( Object.keys(errors).length > 0 ) { + return errors; + } else { + return false; + } + }, + urlRoot() { + return `https://ada-backtrek-api.herokuapp.com/trips/${this.get('trip').id}/reservations`; + } + +}); + +export default Reservation; diff --git a/src/app/models/trip.js b/src/app/models/trip.js new file mode 100644 index 0000000..24caf87 --- /dev/null +++ b/src/app/models/trip.js @@ -0,0 +1,55 @@ +import Backbone from 'backbone'; +import _ from 'underscore'; +import Reservation from './reservation'; + +// 1. Define Model and give it a name +const Trip = Backbone.Model.extend({ + + urlRoot: 'https://ada-backtrek-api.herokuapp.com/trips', + idAttribute: "id", + reserve(details) { + const attrs = _.extend(details, {trip:this}); + const reservation = new Reservation(attrs); + return reservation; + }, + validate: function(attributes) { + const continents = ['Africa', 'Antartica', 'Asia', 'Australasia', 'Europe', 'North America', 'South America']; + + const errors = {}; + + if (!attributes.name) { + errors['name'] = 'cannot be blank'; + } else if (attributes.name === this.get('name')) { + errors['name'] = 'already exists. Please choose another trip name.'; + } + + if (!attributes.continent) { + errors['continent'] = 'cannot be blank'; + } else if (!continents.includes(attributes.continent)) { + errors['continent'] = `must be one of the following continents: ${continents}`; + } + + if (!attributes.category) { + errors['category'] = 'cannot be blank'; + } + + if (!attributes.weeks) { + errors['weeks'] = 'cannot be blank'; + } else if (attributes.weeks < 1) { + errors['weeks'] = 'must be greater than 0.' + } + + if (!attributes.cost) { + errors['cost'] = 'cannot be blank'; + } else if (attributes.cost < 1) { + errors['cost'] = 'must be greater than 0.' + } + + 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..ed5c52e 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -1,2 +1,11 @@ - /* Your styles go here! */ + +/*Table Styling*/ +th.sort { + cursor: pointer; +} + +.current-sort-field { + background-color: darkgrey; + color: white; +}