Skip to main content

The Conversion: Trips for Keeps - Part 1

With the development of the landing page, we are ready to add the trip details page. This page will be used to edit the trip information, manage sponsors, activities, and waivers. Due to the amount of data displayed on the page, I think that the participants listing will get their own page.

We've already, in the last post, added the trip route to the router.  But just as a refresher, it looks like the following:
 angular.module('core').config(function($stateProvider, $urlRouterProvider) {  
   $urlRouterProvider.otherwise("/trips/landing");  
   $stateProvider  
     .state('landing', {url: "/trips/landing", templateUrl: "apps/trips/templates/landing.html"})  
     .state('trip', {url: "/trips/trip/:id", templateUrl: "apps/trips/templates/trip.html"})  
 });  
The next steps are to create the trip template and controller files.  Because a parameter is being passed to the trip controller, the $stateParams need to be injected.  What does that mean?  Well, it's a complicated but short way of saying that we have to let the controller know what it can use.  And we do this by adding to the object array and function parameters just we did with the dataService.
 angular.module('core').controller('TripController',['$scope','dataService','$stateParams', function($scope,dataService,$stateParams){  
   $scope.trip = {};  
   dataService.call('getTrip&_id=' + $stateParams.id).then(function(data){  
     $scope.trip = data.data;  
   })  
 }]);  
The $stateParams stores the parameters passed into the route. In this case, we need the id of the trip being selected by the user.  The id will be passed to the getTrip function via the dataService. The next step is to create the CF function.
 <cffunction name="getTrip" returnformat="json" returntype="struct" access="remote">  
   <cfargument name="_id" required="yes" type="string">  
   <cfset _trip = structNew()>  
   <cfquery name="_qTrip" datasource="#_ds#">  
     select trip_name,  
         author_pidm,  
         destination,  
         trip_type,  
         purpose,  
         comments  
     from trips t  
     where  t.trip_id = <cfqueryparam value="#_id#" cfsqltype="cf_sql_integer">  
   </cfquery>  
   <cfloop query="_qTrip">  
     <cfset _trip.name = trip_name>  
     <cfset _trip.authorID = author_pidm>  
     <cfset _trip.destination = destination>  
     <cfset _trip.type = trip_type>  
     <cfset _trip.comments = comments>  
     <cfset _trip.purpose = purpose>  
   </cfloop>  
   <cfreturn _trip>  
 </cffunction>  
This is the beginning of the trip function as there will be a lot more data elements to gather. But in my development pattern, I like to test things out before writing too much code. In this round of testing and after a bunch of "why isn't this working?!?!" I discovered I had forgotten to add the trip.js controller to the main.cfm page.

So now it's time to begin building out the trip.html template. The editing paradigm that I've been messing around with of late is simply get the JSON object from the CF, edit the object via the model directly on the template, and then pass the object back to CF.  Rinse and repeat.
 <div ng-controller="TripController as ctrlTrip">  
   <h2>{{trip.NAME}}<br><small>{{trip.DESTINATION}}</h2>  
   <div class="input-group" style="padding-bottom:10px">  
     <span class="input-group-addon" style="width:125px">Name</span>  
     <input class="form-control" ng-model="trip.NAME" style="width:400px">  
   </div>  
   <div class="input-group" style="padding-bottom:10px">  
     <span class="input-group-addon" style="width:125px">Destination</span>  
     <textarea class="form-control" ng-model="trip.DESTINATION" style="width:400px"></textarea>  
   </div>  
   <div class="input-group" style="padding-bottom:10px">  
     <span class="input-group-addon" style="width:125px">Comments</span>  
     <textarea class="form-control" ng-model="trip.COMMENTS" style="width:400px"></textarea>  
   </div>  
   <div class="input-group" style="padding-bottom:10px">  
     <span class="input-group-addon" style="width:125px">Trip Type</span>  
     <select class="form-control" style="width:300px" ng-model="trip.TYPE">  
       <option ng-repeat="tripType in tripTypes" value="{{tripType.CODE}}">{{tripType.NAME}}</option>  
     </select>  
   </div>  
   <button class="btn btn-primary" ng-click="saveTrip(trip)">Save Trip</button>  
 </div>  
With a very basic template in place, which will be expanded to accommodate the other aspects of a trip, we can begin written the save code, both in the dataService and the CFC. Since there are going so many many values that need to be saved, it doesn't make sense to pass those values via the url string. Doing so would required breaking down the object into strings, etc.

We are going to pass the trip object to the dataService, and then the dataService is going to pass the object to Coldfusion. The first thing to do is create the dataService method to accept the object. Until now, I have always made a new method for each object that needs processing to happen. However, as I was sitting here looking at the code, I realized I could abstract this just a little further and make it a bit more generic, like the this.call() method.

So I have changed this:
   //params template  
   this.saveTrip = function(_trip){  
     var _params = {trip: JSON.stringify(_trip)}  
     return $http({  
       method: 'POST',  
       url: 'trips.cfc?method=saveTrip',  
       headers: {  
         'Content-Type': undefined  
       },  
       data: _params  
     }).then(function successCallback(response){  
         console.log(response.data);  
         return response;  
       },  
       function errorCallback(response){  
         console.log('ruh-roh')  
       }  
     );  
   }  
to this:
   this.process = function(_method, _data){  
     var _params = {objectData: JSON.stringify(_data)}  
     return $http({  
       method: 'POST',  
       url: 'trips.cfc?method=' + _method,  
       headers: {  
         'Content-Type': undefined  
       },  
       data: _params  
     }).then(function successCallback(response){  
         console.log(response.data);  
         return response;  
       },  
       function errorCallback(response){  
         console.log('ruh-roh')  
       }  
     );  
   }  
Notice the subtle differences.  Rather than doing a this.saveTrip and passing the trip object, we are going to process something, this.process, via some method, _method.  In both cases, the object gets stringified into the _params variable. Next we are going to post to the trips.cfc. For the this.saveTrip we explicitly state the saveTrip method as part of the url.  But for the this.process method, we put the _method value into the url. With this simple level of abstraction, we are going to save a lot of time coding, debugging and maintaining.

Now technically, we could abstract this.process() even further and allow arguments for the CFC and path. But to me this is over-abstraction - if that's a thing. I think it's important to maintain context toward the application when developing. Just like a database can be normalized to the point of being a bunch of tables with columns and columns of numbers, code can be abstracted to the point that it requires layers and layers of coding to get Hello World on the screen. Abstraction is a good thing because it yields us libraries like AngularJS. But too much of a good thing can be unproductive.

Ok, rant over - back to the code. Just one final contrast for this post concerning the dataService call.

dataService.saveTrip(_trip).then(function(data){});
dataService.process('saveTrip',_trip).then(function(data){});

Both make sense and are easy to understand. However, the latter one is going to save quite a bit of development time.

Hmmm...I hadn't expected this post to be as long as it is. So a Part 2 will be forthcoming and focus on the data coming into Coldfusion and saving it back to the database.

Cheers!

Comments

Popular posts from this blog

The Conversion: MAIN.CFM

Within the current development pattern I have for our portal system, the main.cfm is the starting point for an application. For the most part, this is really boilerplate and serves to get the application off the ground. The way the portal works is that the user lands on the main menu page and provide with a menu of options. Each option is called a function and is assigned a unique id. When the user clicks on a menu option, the page is reloaded with the function id. The process checks to see if the user is actually allowed to have access to the function. It then accordingly looks up the path and displays the page via a . In the case of the TRIPS application, the included file is the main.cfm page for the application. So far the project looks like this: we have the folder structure defined with no files aside from the main.cfm. The main.cfm contains a few javascript includes, the overarching controller for the application as well as the route viewer. The next blog post will foc...

The Conversion: Lay of the Landing Page - Part 1

In the last post  the groundwork has been laid for real development to begin. The data service, router, and landing pages have been created and are ready for code. The last thing to add for this part of the process is the Coldfusion component. A data folder has been added to store any cfcs relevant to the TRIPS application. The first is to write the CFFUNCTION that will return the list of trips. My development pattern for creating CF functions is fairly routine.  First, decide on a name, parameters and return type.  Sometimes the naming of a function is the hardest part.  I've settled on actionDataObject.  So in this case, the function will be named: getTrips.    At moment, the landing controller (landing.js) is empty. angular.module('core').controller('LandingController',['$scope','dataService', function($scope,dataService){ }]); The basic task of this controller is getting the list of trips.  First is the establishment of an empty tr...

Dynamically Loading an AngularJS UI.Router

When starting out on my AngularJS journey, I couldn't get a good handle on the router native to the framework. So I adopted the use of the wonderful angular-ui / ui-router.  During the past few years of development, I've honed in (for better or worse) my paradigm for setting new applications and nearly every AngularJS app has a routes.js file. Without going into background, I wanted a way to load the ui-router dynamically. Typically, the routes gets defined in a .js file and typically looks something like this: angular.module('core').config(function($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise("/landing/201820"); $stateProvider .state('landing', {url: "/landing/:term", templateUrl: "apps/noted/templates/landing.html"}) .state('uploadCalendar', {url: "/uploadCalendar", templateUrl: "apps/noted/templates/uploadCalendar.html"}) .state('noteTakers...