Skip to main content

The Conversion: Trips for Keeps - Part 3

With the core methodology set up in Part 2 to get the trip data from the AngularJS front end to the CF middle tier, we are ready to save to the database. All of our custom data is stored in Oracle, but the following will apply to most, if not all, relational databases. I try to steer away from doing anything overly fancy to minimize refactoring down the road if platforms should change.

At the moment, the trip function only receives the trip data and breaks it down into a CF structure and then returns that structure back to the application.
 <cffunction name="saveTrip" returnformat="json" returntype="struct" access="remote">  
   <cfset _params = getHttpRequestData()>  
   <cfset _content = deserializeJSON(_params.content)>  
   <cfset _trip = deserializeJSON(_content.objectData)>  
   <cfreturn _trip>  
 </cffunction>  
Now I have a design decision to make. I can either create individual functions to save each aspect of the trip such as the trip details, the sponsors, waivers, participants, etc. or throw everything into one, likely very long, function. Hmmm...

I think for maintenance purposes it would be wiser to break each save element out. This would reciprocate the idea that the getTrip() function will have various functions to get all the elements of a trip. So let's start by creating a function saveTripDetails(). There will be two sections of this function: one to insert and one to update. Technically, I could create a PL/SQL stored procedure and handle both those scenarios there. Since it's the same amount of code, I'm going with the more direct route and do the database operations in Coldfusion. In my view, the fewer layers of code and number of code bases to maintain, the better. That said, if my boss came along and said "You must use PL/SQL for database operations.", I'd be okay with that.

As I began writing the saveTripDetails() function, I realized that trip_id was not included as a returned property in getTrip(). The trip_id will be used to determine of an insert or an update is needed. If the trip_id is 0, an insert will be executed; otherwise an update will be performed.

[...The Next Day...]

So after much testing and debugging, especially for a new trip, we finally have code that works. The saveTrip() function remains small with only one additional line added.
 <cffunction name="saveTrip" returnformat="json" returntype="struct" access="remote">  
   <cfset _params = getHttpRequestData()>  
   <cfset _content = deserializeJSON(_params.content)>  
   <cfset _trip = deserializeJSON(_content.objectData)>  
   <cfset _trip = saveTripDetails(_trip)>  
   <cfreturn _trip>  
 </cffunction>  
The new function, saveTripDetails(), is doing all the work.
 <cffunction name="saveTripDetails" returntype="struct">  
   <cfargument name="_tripDetails" type="struct" required="yes">  
   <cfif _tripDetails.id gt 0>  
     <cfquery datasource="#_ds#">  
       update trips  
       set   trip_name = <cfqueryparam value="#_tripDetails.name#" cfsqltype="cf_sql_varchar">,  
             author_pidm = <cfqueryparam value="#_tripDetails.authorID#" cfsqltype="cf_sql_integer">,  
             destination = <cfqueryparam value="#_tripDetails.destination#" cfsqltype="cf_sql_varchar">,  
             purpose = <cfqueryparam value="#_tripDetails.purpose#" cfsqltype="cf_sql_varchar">,  
             trip_type = <cfqueryparam value="#_tripDetails.type#" cfsqltype="cf_sql_varchar">,  
             comments = <cfqueryparam value="#_tripDetails.comments#" cfsqltype="cf_sql_varchar">,  
             last_update = sysdate,  
             last_update_pidm = <cfqueryparam value="#session.intUserPIDM#" cfsqltype="cf_sql_integer">  
       where trip_id = <cfqueryparam value="#_tripDetails.id#" cfsqltype="cf_sql_integer">  
     </cfquery>  
   </cfif>  
   <cfif _tripDetails.id is 0>  
     <cfquery name="_id" datasource="#_ds#">  
       select trips_seq.nextval as nextId from dual  
     </cfquery>  
     <cfloop query="_id">  
       <cfset _tripDetails.id = nextId>  
     </cfloop>  
     <cfif _tripDetails.id gt 0>  
       <cfquery datasource="#_ds#">  
         insert  
         into  trips (  
             trip_id,  
             author_pidm,  
             trip_name,  
             destination,  
             purpose,  
             trip_type,  
             comments,  
             last_update,  
             last_update_pidm  
         )  
         values (  
             <cfqueryparam value="#_tripDetails.id#" cfsqltype="cf_sql_integer">,  
             <cfqueryparam value="#session.userid#" cfsqltype="cf_sql_integer">,  
             <cfqueryparam value="#_tripDetails.name#" cfsqltype="cf_sql_varchar">,  
             <cfqueryparam value="#_tripDetails.destination#" cfsqltype="cf_sql_varchar">,  
             <cfqueryparam value="#_tripDetails.purpose#" cfsqltype="cf_sql_varchar">,  
             <cfqueryparam value="#_tripDetails.type#" cfsqltype="cf_sql_varchar">,  
             <cfqueryparam value="#_tripDetails.comments#" cfsqltype="cf_sql_varchar">,  
             sysdate,  
             <cfqueryparam value="#session.userid#" cfsqltype="cf_sql_integer">  
         )  
       </cfquery>  
     </cfif>  
   </cfif>  
   <cfreturn _tripDetails>  
 </cffunction>  
There is nothing really notable here as it's pretty standard stuff. The function will check to see if a trip id greater than zero was passed in. If so, then an update is made to the trips table. If the id is zero, the we get a new id by getting the next value in the sequence: select trips_seq.nextval as nextId from dual, assigning that value to the id and executing an insert into the trips table.

The function ends by returning the trip details back to saveTrip().

As I work through the application, I will be adding validation and error handling.  But for now, the groundwork has been laid. The trip.js controller has been modified to handle a parameter of zero for the trip id.
 angular.module('core').controller('TripController',['$scope','dataService','$stateParams','$state', function($scope,dataService,$stateParams,$state){  
   $scope.trip = {};  
   if ($stateParams.id == 0){  
     dataService.call('createNewTrip').then(function(data){  
       $scope.trip = data.data;  
     })  
   }  
   if ($stateParams.id > 0){  
     dataService.call('getTrip&_id=' + $stateParams.id).then(function(data){  
       $scope.trip = data.data;  
     })  
   }  
   dataService.call('getTripTypes').then(function(data){  
     $scope.tripTypes = data.data;  
   })  
   $scope.saveTrip = function(_trip){  
     dataService.process('saveTrip',_trip).then(function(data){  
       $scope.trip = data.data;  
       $state.go('trip',{id: $scope.trip.ID})  
     });  
   }  
 }]);  
I do want to point out the following line after the trip has been saved $state.go('trip',{id: $scope.trip.ID}). What this does is reload the page by going to the trip route and passing in the trip id. When a new trip is saved, it's route ended with a 0. So in order to reestablish the route, I am reloading the page with the new trip id to force the new route. By having the page essentially start over, we can ensure that all the ducks are in a row and immediately notice if there are any data concerns.

Next up is taking a second first look at the trip.html template as well as adding a new trip button on the landing page.

Thanks for reading!  Comments and feedback are always welcome.

Cheers!
[Updated Line Counts]

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...