Using jQuery UI Autocomplete with hidden ID in ColdBox

Previously in How to use jQuery UI Autocomplete with ColdBox and Bootstrap the value used was singular and also the record identifier. However what if we want to submit the identifier but show another text value for the auto-suggest to the user, for example, if we want to show the client’s full name for auto suggest, but submit the client’s ID.

The environment used for this example is:

  • ColdBox 3.8
  • BootStrap 3.1.1
  • ColdFusion 10

The steps are essentially the same  (the differences are in the helper function and front end UI), i.e.

  • Reference jQuery UI in the main layout
  • Create a helper function to retrieve the required data
  • Use autocomplete on the view page

1 Reference the jQuery UI in the main layout

Open layouts\Main.cfm and add the following reference to the style sheet and JavaScript for jQuery UI (if not already present), i.e.

<head>
  <link href="/path/to/bootstrap/bootstrap.min.css" rel="stylesheet" type="text/css"/ >
  <link href="/path/to/jquery-ui/themes/smoothness/jquery-ui.min.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <script src="/path/to/jquery/jquery.11.1.js"></script>
  <script src="/path/to/jquery/ui/jquery-ui.min.js"></script>
  <script src="/path/to/bootstrap/bootstrap.min.js"></script>
</body>

2 Create a helper function to retrieve the required data

Load includes\helpers\ApplicationHelper.cfm and add the following function:

<cffunction name="getClients" access="remote" output="no">
  <cfscript>
    var qGet = "";
    var clientsList = [];
    var stClients = "";
  </cfscript>
  <cfquery name="qGet" datasource="clients">
    SELECT
      clientid, title, firstname, lastname
    FROM clients
  </cfquery>
  <cfscript>
    //convert query to array
    for (var row in qGet) {
      stClients.label = row.title & ' ' & row.firstname & ' ' & row.lastname;
      stClients.value = row.clientid;
    }
    ArrayAppend(clientsList, stClients);
    return clientsList;
  </cfscript>
</cffunction>

3 Use jQuery UI Autocomplete on the view page

In the view page, for example, the edit.cfm where autocomplete is needed, add the following code:

//somewhere in the form section, add this:
<input type="text" name="clientname" id="clientname" class="form-control client-suggest" value="">
<input type="hidden" name="clientid" id="clientid" class="form-control" value="">

<script>
<cfoutput>
  //retrieve list of clients from db, getClients is a function in the application helper
  var #toScript(getClients(), "clientsList")#;
</cfoutput>

$(document).ready(function() {
   var suggestInput = function (sourceValues, elemClassName){
      $('input.' + elemClassName).each(function(){
         var formElemName = $(this).attr('name');
         var hiddenElemID = formElemName + 'id';

         //on auto suggest
         $(this).autocomplete({
            source: sourceValues,
            select: function(event, ui) {
               selectedObj = ui.item; 
               $(this).val(selectedObj.label);
               $('#'+ hiddenElemID).val(selectedObj.value); 
               return false; 
            },
            change: function(event,ui) {
               var formElemVal = $(this).val();
               if (formElemVal === ''){
                  //clear id from hidden field if input is blank
                  $('#'+ hiddenElemID).val('');
               } //if
            }//change
         }); //autocomplete

         //clear id from hidden field if input is blank
         $('#' + formElemName).blur(function(){
            if ( $('#' + formElemName).val() == ''){
               $('#' + hiddenElemID).val(''); 
            }
         });
      } //each
   } //suggestInput

   if ($('.client-suggest').length){
      //activate auto suggest for client input box 
      //(i.e. the input with class of 'client-suggest')
      suggestInput(clientsList,'client-suggest');
}); //document.ready
</script>

Here we use the ColdFusion toScript function to convert ColdFusion value to JavaScript saving it as clientsList. getClients is the ColdBox helper function which we added to ApplicationHelper.cfm.

The client name input box will sugget the client’s full name (including title), but upon form submission, the client’s ID will be submitted.

Advertisements

ColdBox MailService – Passing Complex Values to the Body

In a previous blog post on using the ColdBox MailService we made use of the service’s SetBodyTokens method to pass through simple values, that is something like this:


/* define the token for use in the email view.
this is the dynamic content for the html email body */

email.setBodyTokens({
  title = emailTitle,
  firstName = arguments.user.getFirstName(),
  url = baseURL & "/reports/list"
});

Then in the view used to display the email body content, any placeholder variables will be replaced with the passed in value. That is @title@ will be replace with the value passed in. So if a value of Cool Programmer was passed in, then @title@ will now show Cool Programmer.

Using the SetBodyTokens method means that we can not passed in complex values like an array or a struct. To overcome this issue we will instead use the MailService’s SetBody method to pass in the value via the args parameter to the email body’s view template.

Firstly, in the handler where the MailService is invoke and where the sendUserDetails function is defined, add the following dependency injections at the top of the file:


property name="mailService" inject="coldbox:plugin:MailService";
property name="renderer" inject="coldbox:plugin:Renderer";
property name="configBean" inject="coldbox:configBean";

Then add the following function to gather the information and send the email in this same file, as follows:


public void function sendUserDetails(event){ 
 var email = MailService.newMail().config(
   from=configBean.getKey('emails').senderEmail,
   to=configBean.getKey('emails').recipientEmail,
   subject= "User Details"
 );

 var stInfo = {};
 stInfo.title = "Cool Programmer";
 stInfo.firstname = "John";
 stInfo.lastname = "Doe";
 stInfo.email = "John.Doe@supercool.com";

 // generate html content for email from template
 email.setBody( renderer.renderView( view='userDetails', args=stInfo ));
 email.setType("HTML");

 // Send the email. MailResult is a boolean.
 mailResult = mailService.send(email);

} //sendUserDetails

In the view for the email body (i.e.  view=”userDetails”), the passed in values are in the args scope, so to access them we refer to each variables as args.variable-name (i.e. replace stInfo with argsargs.firstname). Here’s a partial code snippet to illustrate:


<p>
#args.firstname# #args.lastname#<br>
#args.title#<br>
#args.email#
</p>

A Subquery Using ColdBox’s Detached Criteria Builder

Let’s say we want to create the following SQL query with a subquery using ColdBox’s Detached Criteria Builder:


select *
from reports r
where r.report_id not in (
   select report_id
   from confirmations c
   inner join staffs s on s.staff_id = c.staff_id
   inner join teams t on t.team_id = s.team_id
   where t.teamName = 'Developers'
)

In our objects world we have the following corresponding entities:

  • report
  • confirmation
  • staff
  • team

And the following relationships between them:

  • A report can have one to many confirmations submitted by staff
  • A confirmation belongs to a staff
  • A staff belongs to a team

Then our ORM query for the above SQL query using ColdBox’s Detached Criteria Builder would be as follows:


cr = reportService.newCriteria();
cr.add(
   cr.createSubcriteria('report','subrpt')
   .withProjections(property="report_id")
   .createAlias('subrpt.confirmations','subconf')
   .createAlias('subconf.staffs','substaffs')
   .createAlias('substaffs.teams','subteams')
   .isEq('subteams.teamName','Developers')
   .propertyNotIn("report_id")
);

Notes

  • confirmations is the relationship between the report and confirmation entities
  • staffs is the relationship between the confirmation and staff entities
  • teams is the relationship between the staff and team entities

Further Learning

For more information on ColdBox Criteria Builder and Detached Criteria Builder, please visit these links:

Using the ColdBox MailService Plugin

Need to send out emails in your ColdBox application? Then the ColdBox MailService plugin is your friend. Here are the steps:

  1. Inject the mail service
  2. Create the email method
  3. Create the html email view
  4. Using the email method

Inject the Mail Service

Before you can use the mail service, we need to inject it into the handler component where we will define the mail method like so:


component accessors="true" {
  property name="mailService" inject="coldbox:plugin:MailService";
  property name="renderer" inject="coldbox:plugin:Renderer";
  property name="configBean" inject="coldbox:configBean";
  property name="userService"  inject="model:userService@solitary";
} //component

Notes:

  • We first inject the MailService plugin
  • Then the Renderer plugin so we can render the email message
  • Finally we inject the configBean to bring in any configurations from the ConfigurationCFC (coldbox.cfc)

Create the email method

In the same handler component, create an email method, e.g. notifyUser. Here’s an example to illustrate:


private void function notifyUser(required user) {
  var baseURL = len(configBean.getKey('sesBaseURL')) ? configBean.getKey('sesBaseURL') : configBean.getKey('htmlBaseURL');
  var emailTitle = "New Records Available";
  rc.emailView = 'notifications/newEntry';

  // Create a new mail object
  local.email = MailService.newMail().config(
    from=configBean.getKey('sysEmail'),
    to=arguments.user.getEmail(),
    subject= emailTitle
  );

  /* define the token for use in the email view. 
     this is the dynamic content for the html email body */
  local.email.setBodyTokens({
    title = emailTitle,
    firstName = arguments.user.getFirstName(),
    url = baseURL & "/reports/list"
  });

  // Add HTML email
  local.email.addMailPart(charset='utf-8',type='text/html',body=renderer.renderView(view=rc.emailView));

  // Send the email. MailResult is a boolean.
  local.mailResult = mailService.send(local.Email);
} //notifyUser

Notes:

  • We use the injected configBean to retrieve settings from the ConfigurationCFC (coldbox.cfc) and set the base URL
  • Then we create a new mail message using MailService’s newMail method
  • Then we define the token for the body and add the html email. The content of the html email is defined in the views folder. In the above example, a view template called newEntry.cfm should be created and saved under the views/notifications sub-folder
  • Finally we send it using the MailService plugin’s send method.

Create the email view

Under the views folder, create a subfolder called notifications. Then in this new subfolder create a new file called newEntry.cfm and add the following code snippet:


<html>
<head>
<title>@TITLE@</title>
</head>
<body>
  <p>Dear @FIRSTNAME@,</p>
  <p>New records are now available. Please <a href="@URL@">logon</a> to action them.</p>
  <p>Regards,</p>
  <p>System Admin</p>
</body>
</html>

Notes:

  • Any @key@ will be replace by the values defined in the MailService’s new email setBodyTokens method in the notifyUser method.
  • So in this instance @TITLE@ will be replaced with the value: New Records Available

Using the email method

Here’s the scenario. In your application you have a method which uploads a file and emailing all application users to notify them of new records. Here the notifyUser method is called and used.

And an example code snippet to illustrate.


public void function upload(event){
  uploadResult = getPlugin("FileUtils").uploadFile(fileField="csvFile",destination=expandPath('files'),nameConflict="Overwrite");
  if (!StructIsEmpty(uploadResult) ){
    users = userService.findAllWhere(criteria={isActive=true});
    if (!ArrayIsEmpty(users)){
      for (var i=1; i<=ArrayLen(users); i++){
        notifyUser(users[i]);
      }
    } //if !ArrayIsEmpty()
  }
} //upload

Traversing Multiple ORM Associations with ColdBox CriteriaBuilder

Let’s say you have the following entities:

  • Report
  • Confirmation
  • Staff
  • Team

And the following associations between the entities:

  • A report can have one to many confirmations submitted by staff
  • A confirmation belongs to a staff
  • A staff belongs to a team

And let’s say you are querying the Report entity but also want to show Confirmation, Staff and Team entity details in a ColdBox view. How would you do use ORM associations and ColdBox’s CriteriaBuilder to navigate multiple associations and bring back the required information?

Here’s how we can achieve that goal by using the ColdBox CriteriaBuilder’s CreateAlias method:

  • Create a link from Report to Confirmation with CreateAlias
  • Create a second link from Confirmation to Staff with CreateAlias
  • Lastly, create a third link from Staff to Team with CreateAlias
  • Link all of the above together by chaining the CreateAlias, i.e. CreateAlias().CreateAlias().CreateAlias()

Here are some example code snippets to illustrate:

1. Entities and their Associations

Report Entity


component persistent="true" extends="<path-to-base-entity>" table="reports" { 
 property name="report_id" fieldtype="id" generator="identity" setter="false";
 property name="startDate" ormtype="date";
 property name="endDate" ormtype="date";

 property name="confirmations" fieldtype="one-to-many" inverse="true" cfc="<path-to-confirmation-model>" singularname="confirmation" fkcolumn=report_id" cascade="delete"; 
 
 public Report function init(){      
        return this;
 }
} //component

Confirmation Entity


component persistent="true" extends="<path-to-base-entity>" table="confirmations" { 
 property name="confirmation_id" fieldtype="id" generator="identity" setter="false";
 property name="comments" sqltype="nvarchar" length="800";

 property name="staffs" singularname="staff" fieldtype="many-to-one" cfc="<path-to-user-model>" fkcolumn="staff_id";
 
 public Confirmation function init(){
    staffs = [];    
    return this;
 }
} //component

Staff Entity


component persistent="true" extends="<path-to-base-entity-model> " table="staffs" {
  property name="staff_id"  fieldtype="id" generator="identity" setter="false";
  property name="firstname" sqltype="nvarchar" length="100";
  property name="lastname" sqltype="nvarchar" length="100";

  property name="teams" singularname="team" fieldtype="many-to-one" cfc="<path-to-team-model>" fkcolumn="team_id" foreignkeyname="FK_staff_team_id_teams";

  public User function init(){
    teams = [];
    return this;
  }
} //component

Team Entity


component persistent="true" extends="<path-to-base-entity-model>" table="teams" {
  property name="team_id" fieldtype="id" generator="identity" setter="false";
  property name="teamName" sqltype="nvarchar" length="50";

  public Team function init(){
    return this;
  }
} //component

2. Querying with CriteriaBuilder and CreateAlias

In your Report entity handler Report.cfc, maybe you have an action that retrieve the information for the Report entity listing view. Let’s say this action is called list. Here’s the code to retrieve the information from the Report, Confirmation, User and Team entities:


/* Report.cfc */
component accessors="true" {
   property name="reportService"    inject="model:ReportService";
   property name="sessionStorage"   inject="coldbox:plugin:SessionStorage";

   function preHandler(event,action){
        
   }
   public void function list(event){
      var rc = event.getCollection();
      var cr = reportService.newCriteria();

      cr.createAlias('confirmations','c').createAlias('c.users','u').createAlias('u.teams','t');
      rc.reports = cr.list(asQuery=false);

   } //list

}//component 

Note:

  • We are chaining the CreateAlias method to link the entities via their associations, so we can traverse the entities and display the required information.

3. Displaying the Information

Ok, we should now be able to retrieve all the information we needed using ColdBox CriteriaBuilder. Let’s see how we can access and display them. And let’s say we want to display the following information:

  • Report ID
  • Report Start Date
  • Staff First and Last name
  • Team Name
  • Confirmation comments

<cfoutput>
  <table>
    <thead>
      <tr align="left">
        <th>ID</th>
        <th>Start Date</th>
        <th>Staff Name</th>
        <th>Team</th>
        <th>Comments</th>
      </tr>
    </thead>
    <tbody>
      <cfloop array="#rc.reports#" index="report">
        <tr>
          <td>#report.getReport_id()#</td>
          <td>#DateFormat(report.getStartDate(),"dd/mm/yyyy")#</td>
          <td>#report.getStaffs().getFirstname()# #report.getStaffs().getLastname()#</td>
          <td>#report.getTeams().getTeamName()#</td>
          <td>#report.getConfirmations().getComments()#</td>
        </tr>
      </cfloop>
    </tbody>
  </table>
</cfoutput>

Notes

  • Please note the getters and setters are automatically created.
  • Staffs, Teams and Confirmations are ORM associations, and hence their respective getters are:
    • getStaffs()
    • getTeams()
    • getConfirmations()

Quick Tip on Uploading a CSV file in ColdBox

So you know how to upload files using uploadify, jQuery, AJAX. But how do we do that in ColdBox? Well it’s actually not that hard. ColdBox has a file utility plugin that handles interacting with files. Let’s demonstrate by showing how to upload a csv file.

Here’s a broad overview of the steps:

  • Download the FileUtils plugin
  • Add a mime type definition in ConfigurationCFC
  • Create the view
  • Create the handler
  • Add the route
  • Test the upload

Environment

  • ColdBox 3.8
  • ColdFusion 10
  • BootStrap 3
  • Windows OS

Download FileUtils Plugin

In preparation for the file upload, we will first need to download the FileUtils plugins from ForgeBox. Once downloaded, unzip and place the filebrowser folder in the modules folder. Here’s what we should have now:

cb-file-upload-2

Add Mime Type Definition to ConfigurationCFC

Let’s place the csv mime type definition in the ConfigurationCFC (i.e. configs/ColdBox.cfc):


/* ColdBox.cfc */
// custom settings
settings = {
   validMimeTypes =  {
      'text/plain': {extension: 'csv'}
   }
};

Please note this mime type will also pick up any other plain text files. So you will need to use other checks to ensure the uploaded file is a csv.

Create the View

We are going to create a simple view to upload the csv file. I will assume the layout is created. If not, check out this article on ColdBox Layouts. Then in the views folder, create the reports folder and the new file index.cfm, and then add the following code:

<!--- views/reports/index.cfm --->
<cfoutput>
<div class="panel panel-primary">
  <div class="panel-heading"><h3 class="panel-title"><span class="glyphicon glyphicon-bell" aria-hidden="true"></span> Upload Reports</h3></div>
  <div class="row">
    <div class="col-md-12">#getPlugin("MessageBox").renderit()#</div>
  </div>
  <div class="panel-body table-responsive">
    <form name="reportForm" method="post" action="#event.buildLink('reports.upload')#" enctype="multipart/form-data">
      <div class="form-group">
        <div class="col-md-3">
          <label for="csvFile">Select csv file to upload</label>
          <input type="file" id="csvFile" name="csvFile" class="fillable" accept="text/csv">
        </div>
      </div>
      <br>
      <div class="form-group">
        <div class="col-md-3">
          <button type="button" class="btn btn-default" onClick="location.href='#event.buildLink('main.index')#'">Cancel</button>
          <button type="submit" class="btn btn-primary">Upload</button>
        </div>
      </div>
    </form>
  </div> <!--- panel-body --->
</div> <!--- panel --->
</cfoutput>

Important notes:

  • Must add the enctype attribute to the form element, i.e. enctype=”multipart/form-data”
  • Set the form’s action to reports.upload and ensure you create the upload action method in the Reports handler, i.e. public void function upload() in handlers/Reports.cfc

Create the Handler

In the handlers folder, create a new ColdFusion component and name it Reports.cfc. Edit this file and add the following skeleton code to start with:


component accessors="true" {
  /* handlers/Reports.cfc */
  property name="sessionStorage"    inject="coldbox:plugin:SessionStorage";
  function preHandler(event,action){

  }

  public void function index(){
    event.setView("reports/index");
  }

} //component

In the index function above we’re loading the upload form on line 9 by explicitly setting the view to use as index.cfm in the views/reports folder.

In the same file after the index function, create a new function upload and add the following code:


public void function upload(event){
  var rc = event.getCollection();
  var uploadResult = "";
  var actualMimeType = "";
  event.paramValue("csvFile","");

  if( !isNull(rc.csvFile) ){
    actualMimeType = FileGetMimeType(rc.csvFile, true);
    if (StructKeyExists(getSetting('validMimeTypes'), actualMimeType)) {
      uploadResult = getPlugin("FileUtils").uploadFile(fileField="csvFile",destination=expandPath('includes\files'),nameConflict="Overwrite");

      if (!StructIsEmpty(uploadResult) && uploadResult.clientFileExt is 'csv'){
        getPlugin("messagebox").setMessage("info","File #uploadResult.clientFile# was successfully uploaded.");
      }
      else {
        getPlugin("FileUtils").removeFile( expandPath('includes\files\' & uploadResult.serverFile) );
        getPlugin("messagebox").setMessage("error","Invalid file type. Only CSV files are allowed.");
      }
    }
    else {
      getPlugin("messagebox").setMessage("error","Invalid file type. Only CSV files are allowed.");
    }
  } 
  else {
    getPlugin("messagebox").setMessage("error","No File was selected for uploading.");
  }
  setNextEvent("reports.index");
} //save

Important notes:

  • On line 8 we use FileGetMimeType to determine the actual mime type of the uploaded file.
  • On line 9, we check it agains the defined mime type in ConfigurationCFC to ensure it matches before proceeding
  • On line 10, we use the ColdBox File Utility plugin’s uploadFile method to upload the file. This will return the cffile variable
  • On line 12, if upload was successful (i.e. uploadResult contains the results from the file upload), then we set a message to display to the user to indicate file was successfully uploaded
  • Otherwise on lines 16 and 17, we delete the file if didn’t match the expected mime type, and display a message to the user

Add the Route

Edit configs/Routes.cfm and add the following routes for the new view and handler for uploading the csv file:


// Your Application Routes
with(pattern="/reports", handler="reports")
  .addRoute(pattern="/upload", action="upload")
.endWith();

Test the Upload

OK. So we’re now ready to test what we have done. Let’s browse the main page and refresh the application so the new configurations in ConfigurationCFC and new route take effect, i.e. do this:

index.cfm?fwreinit=1

Now load the upload page, i.e.

index.cfm/reports

You should see the form for uploading the csv file shown below, and the following after you’ve successfully uploaded the csv file:

cb-file-upload-1

That’s it! You’ve just created all the necessary bits to upload a file in ColdBox.

Further Learning and Info

To learn more about the File Utility plugin for the ColdBox platform, please check out the links below:

 

ColdBox Basic – Event Handlers

ColdBox event handlers are the controllers in the MVC design pattern. They are ColdFusion components which are responsible for handling requests coming into the application from either a FORM, URL, REST or remote sources (Flex/Air/SOAP). These event handlers carry the task of controlling your application flow, calling business logic, and preparing a display to a user.

Environment

  • ColdBox 3.8
  • Windows OS

ColdBox Framework Request Context

  • The incoming URL, form and Remote variables are merged into a single structure called the request collection (i.e rc) structure. This structure resides inside the request context object.
  • Internally, a second collection called the private request collection (i.e. prc) is created to store data and objects that have no outside effect.

Event Handlers Location and Calling

1. Location

  • All event handlers should be saved in the handlers folder
  • For large applications, it is best to create packages or sub-folders under the handlers folder for better maintenance and URL experience
  • Can also declare a HandlersExternalLocation setting in the ConfigurationCFC (ColdBox.cfc)

2. Calling Events

Without SES routing, use the following event syntax notation format:

  • no event : Default event by convention is main.index
  • event={handler} : Default action method by convention is index()
  • event={handler}.{method} : Explicit handler + action method
  • event={package}.{handler}.{method} : Packaged notation
  • event={module}:{package}.{handler}.{method} : Module Notation

With SES routing, use the following event syntax notation format:

  • no event : Default event by convention is main.index
  • {handler} : Default action method by convention is index()
  • {handler}/{method} : Explicit handler + action method
  • {package}.{handler}/{method} : Packaged notation
  • {module}:{package}.{handler}/{method} : Module Notation

Here are a few simple examples to illustrate:

//No Packages
index.cfm?event=clients.list

//With SES routing
index.cfm/clients/list

component name="clients" {
   function list(event,rc,prc){
      return "I am the client handler's list action!";
   }
}

//With 'public' Package
index.cfm?event=public.clients.list

//With SES Routing
index.cfm/public.clients/list

OK. Now that we know something about ColdBox event handlers, let’s create an event handler for the client model and some of its actions:

  • list
  • add/edit
  • save
  • remove

Create the Handler

In the handlers folder, create a new ColdFusion component and save it as Clients.cfc. Edit this new component and add the following skeleton code:

component accessors="true" {
   //dependency injection
   property name="clientService" inject="model:ClientService";

   function preHandler(event,action){
      //preHandler stuff goes here
   }

   public void function index(){
      //this is the default action
      setNextEvent("clients/list");
   }
}//component

Create the List Action

This handler return all instances from the client model. Here’s a code sample to illustrate:

public void function list(event){
   var rc = event.getCollection();
   event.paramValue("client_id","");

   rc.clients = clientService.list(sortOrder="lastName desc",asQuery=false);
} //list

Notes:

  • On line 2, we use the getCollection function to retrieve the request collection. It is also possible to just pass in the request collection as an argument to the action function, i.e. function list(event,rc,prc). In that case, line 2 won’t be necessary.
  • On line 3, we use event.paramValue to specify a default value for client_id. This is similar to the cfparam tag
  • On line 5, we are using the Base ORM service list function to return all instances of the client entity (i.e. all records in the clients table).

Create the Add/Edit Action

In this handler action, we are retrieving a particular record by its primary identifier, usually the primary key, to add or edit.

public void function edit(event,rc,prc){
   rc.client = clientService.get( event.getValue("client_id","") );
} //edit

Notes

  • On line 2 we use the Base ORM Service get function to retrieve a particular instance from the client model
  • Also on line 2 we use event.getValue to set a default value for client_id if it doesn’t exists
  • If client_id is blank than it is an add action, otherwise we are performing an edit

Create the Save Action

public void function save(event,rc,prc){
   var client= populateModel( clientService.get(event.getValue("client_id","") ));
   var vResults = validateModel(client);
   
   if( !isNull(client) ) { 
      // validate model       
      if( vResults.hasErrors() ){ 
         getPlugin("MessageBox").error( messageArray=vResults.getAllErrors()); 
         setNextEvent("clients.edit/" & rc.client_id); 
      } 
      else { 
         // save the model
         clientService.save(client); 
         getPlugin("messagebox").setMessage("info","Client #rc.firstname# #rc.lastname# was successfully #rc.context#.");
         setNextEvent("clients.list");
      } 
      else { 
         getPlugin("messagebox").setMessage("info","Client was not found."); 
      }
}//save 

Notes

  • On line 2 we use the PopulateModel function to populate the client model using data from the request collection by matching the form element namw to the model property
  • So for example if the form has an input element name firstname, and the model has a property called firstname, then the values of firstname from the form scope will be placed in the model’s firstname property
  • It is also possible to use each property’s setter to set the property value if you have form element names that don’t match with the model property name, e.g. client.SetFirstname(rc.first_name)
  • Hence it is important that the form element name matches the model property if you want to use PopulateModel function
  • On line 3 we use the validateModel function to perform validation based on the constraints defined for the model. If it fails, we will display a message for the user to fix before proceeding to save the model
  • On line 8 we display the error to the user if the model validation fails using the MessageBox plugin
  • On line 13 the model validation succeeds so we proceed to save the model (i.e. write the update to the database) using the Base ORM service save function
  • On line 14 we redirect to the client listing page after the save was successful using the setNextEvent function
  • rc.context is set on the edit view and signifies whether it is an add or edit action. This is done by checking to see whether client_id is blank. If it is, then it’s an add action, so rc.context stores the value of added. Otherwise it is set to saved.

Create the Remove Action

public void function remove(event,rc,prc){
   var client = clientService.get( event.getValue("client_id","") );
   
   if (event.getHTTPMethod() == "GET") {
      getPlugin("messagebox").setMessage("error","Invalid action!");
   }
   else {
      if( !isNull(client)){
         clientService.delete(client);
         getPlugin("messagebox").setMessage("info","#client.getFirstname()# #client.getLastname()# was successfully deleted.");
      }
      else {
         getPlugin("messagebox").setMessage("error","We are unable to locate the client you are trying to remove, please try again.");
      }
   }
   setNextEvent("clients.list");
} //remove

Notes

  • On line 2 we retrieve the instance we want to delete using the Base ORM service get function and the primary key, i.e. client_id
  • On line 4 we check to ensure that it is an HTTP POST method to prevent unauthorised use of HTTP methods like GET
  • On line 9 we remove this instance from the model using the Base ORM service delete function

The Full Handler Code

The final code for the handler Clients.cfc should look like this:

component accessors="true" {
   /* handlers/Clients.cfc */
   property name="clientService" inject="model:ClientService";

   function preHandler(event,action){

   }

   public void function index(){
      setNextEvent("clients/list");
   }

   public void function list(event){
      var rc = event.getCollection();
      event.paramValue("client_id","");

      rc.clients = clientService.list(sortOrder="lastName desc",asQuery=false);
   } //list

   public void function edit(event,rc,prc){
      rc.client = clientService.get( event.getValue("client_id","") );
   } //edit

   public void function save(event,rc,prc){
      var client= populateModel( clientService.get(event.getValue("client_id","") ));
      var vResults = validateModel(client);
   
      if( !isNull(client) ) { 
         // validate model       
         if( vResults.hasErrors() ){ 
            getPlugin("MessageBox").error( messageArray=vResults.getAllErrors()); 
            setNextEvent("clients.edit/" & rc.client_id); 
         } 
         else { 
            // save the model
            clientService.save(client); 
            getPlugin("messagebox").setMessage("info","Client #rc.firstname# #rc.lastname# was successfully #rc.context#.");
            setNextEvent("clients.list");
         } 
      else { 
         getPlugin("messagebox").setMessage("info","Client was not found."); 
      }
    }//save

    public void function remove(event,rc,prc){
      var client = clientService.get( event.getValue("client_id","") );
      
      if (event.getHTTPMethod == "GET"){
         getPlugin("messagebox").setMessage("error","Invalid action!");
      }
      else {
         if( !isNull(client)){
            clientService.delete(client);
            getPlugin("messagebox").setMessage("info","#client.getFirstname()# #client.getLastname()# was successfully deleted.");
         }
         else {
            getPlugin("messagebox").setMessage("error","We are unable to locate the client you are trying to remove, please try again.");
         }
      }
      setNextEvent("clients.list");
   } //remove
 }//component

Further Information

For more detailed information on ColdBox’s Base ORM Service, PopulateModel and ValidateModel functions, dependency injection and event handlers, please click on one of the following links: