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.

How to Show Required Icon for Mandatory Fields

Here’s a quick tip on how to show the required icon for mandatory fields when using FormValidationJS for Bootstrap forms.

In your application’s style sheet ensure you add the following:


/* Adjust feedback icon position */
#<your-form-id> .has-feedback .form-control-feedback {
   top: 0;
   right: -15px;
}
/* for required select element, a padding-right of 42.5px is added; we want to over-ride this value */
   .has-feedback .form-control {
   padding-right: 12px;
}
.required .control-label:after {
   color: #d00;
   content: "*";
   position: absolute;
   margin-left: 1px;
   top:6px;
   font-family: 'Glyphicons Halflings';
   font-weight: normal;
   font-size: 10px;
}

Then in the page where you have a form that needs client side validation using FormValidation, ensure you have the following:


$('#<your-form-id>').formValidation({
   framework: 'bootstrap',
   icon: {
      valid: 'glyphicon glyphicon-ok',
      invalid: 'glyphicon glyphicon-remove',
      validating: 'glyphicon glyphicon-refresh'
   },
   fields: {
      firstname: {
         validators: {
            notEmpty: {
               message: 'First Name is required and cannot be empty'
            }
         }
      },//firstname
      lastname: {
         validators: {
            notEmpty: {
               message: 'Last Name is required and cannot be empty'
            }
         }
      }//lastname
   }//fields
});

In the actual form, ensure you have something similar to this, in particular the class=”form-group required” part:


<form id="<your-form-id>" class="form-horizontal" role="form" method="post" action="">
   <div class="form-group required">
      <label for="firstname" class="col-md-3 control-label">First Name:</label>
      <div class="col-md-3">
         <input type="text" name="firstname" class="form-control" placeholder="First Name">
      </div>
   </div>
   <div class="form-group required">
      <label for="lastname" class="col-md-3 control-label">Last Name:</label>
      <div class="col-md-3">
         <input type="text" name="lastname" class="form-control" placeholder="LastName">
      </div>
   </div>
</form>

Lastly, don’t forget to link to the style sheet (including the application’s styles) and scripts for FormValidation JS and jQuery in the appropriate places (in the head section for styles, and body for scripts):


<html>
<head>
<title>Page title</title>
<link href="<path-to-formvalidation>/formValidation.min.css" rel="stylesheet" />
<link href="<path-to-application-stylesheet>/your-app-name.css" rel="stylesheet" />
</head>
<body>
   <script src="<path-to-jquery>/jquery.min.js"></script>
   <script src="<path-to-formvalidation>/formValidation.min.js"></script>
   <script src="<path-to-formvalidation>/framework/bootstrap.min.js"></script>
</body>
</html>

 

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 – Views

ColdBox views are HTML or XML content that can be rendered within a layout or on its own. They can be rendered on demand (explicit) or set by an event handler (implicit). Views can also produce and display any type of content apart from HTML such as JSON, XML, WDDX, PDF and much more, via the view renderer. All views and layouts have direct reference to the request collections so it’s incredibly easy to retrieve and place data into them.

Environment

  • ColdBox 3.8
  • Bootstrap 3.1
  • ColdFusion 10
  • Windows OS

Implicit Views

By default ColdBox looks for the view according to the executing event’s syntax. So if the incoming event is clients.list and no view is explicitly set in the model’s action handler, then ColdBox will look for a view in the views/clients folder called list.cfm. Let’s create a simple listing view, i.e. add the following code to list.cfm:

<cfoutput>
<div class="panel panel-primary">
  <div class="panel-heading">
    <h3 class="panel-title">Clients</h3>
  </div>
  <div class="row">
    <div class="col-md-12">#getPlugin("MessageBox").renderit()#</div>
  </div>
  <div class="panel-body table-responsive">
    <table name="clients" id="clients" class="table table-striped table-bordered table-hover" width="100%">
      <thead>
        <tr align="left">
          <th>First Name</th>
          <th>Last Name</th>
          <th>DOB</th>
        </tr>
      </thead>
      <tbody>
        <cfloop from="1" to="#arrayLen(rc.clients)#" index="i">
          <tr>
            <td>#rc.clients[i].getFirstname()#</td>
            <td>#rc.clients[i].getLastname()#</td>
            <td>#dateFormat(rc.clients[i].getDOB(), "dd/mm/yyyy")#</td>
          </tr>
        </cfloop>
      </tbody>
    </table>
  </div> <!--- panel-body --->
</div> <!--- panel-primary --->
</cfoutput>

Important Notes:

  • With implicit views, the name of the view will ALWAYS be in lower case.
  • Create SES URL Mappings with explicit event declarations so case and location can be controlled.
  • Using implicit views will result in losing fine rendering control.
  • The above code assumes that there is a list function in the client handler (i.e. handlers/Clients.cfc), and that the returned data are stored in rc.clients

You can control the case-sensitivity of implicit views by creating a setting called: caseSensitiveImplicitViews in the ColdBox configuration file: configs/ColdBox.cfc, i.e.

settings = {
   //this means that case matters for implicit views
   caseSensitiveImplicitViews = true
};

Setting Views Explicity and with Specific Layout

Sometime we may want to control which view and layout are used to render the content. In this scenario we will need to explicitly employ the SetView method in the handler to specify the view and layout to use. Here’s a code snippet to illustrate:


public void function modal(event){
   var rc = event.getCollection();
   rc.phone = phoneService.get( rc.phone_id );
   event.setView(name="phones/modal",layout="Modal");
}

Notes:

  • The above method would reside in the phone model handler, i.e. handlers/Phones.cfc.
  • The modal view would be in this location: views/phones/modal.cfm
  • If you want to only explicitly set the view, then use this (i.e. omit the layout parameter):
    event.setView(name=”phones/modal”);

Views with No Layout

If we do not want to render the view within a layout, use the noLayout parameter, for example:

event.setView(name=”phones/modal”, noLayout=true);

Render Views On-Demand

We can render views on-demand using the renderView method. An example of this was provided earlier with the article on ColdBox Basic – Layouts when we customise the default Main layout. Here’s a partial code snippet to illustrate:


<!---Container And Views --->
<div class="container-fluid">
  <div class="navbar navbar-inverse">
    <nav role="navigation">#renderView('tags/nav',cache=true,cacheTimeout='30')#</nav>
  </div>
  <!--- body content --->
  <div>#renderView()#</div>
  <footer class="footer">#renderView('tags/footer',cache=true,cacheTimeout='30')#</footer>
</div>

Displaying Other Content Types

Let’s say we want to produce and display pdf content. We can do this using the renderData and renderView methods. Here’s an example code snippet to illustrate:


public void function pdf(event,rc,prc){
   event.paramValue("client_id","");
   rc.clients = clientService.list(sortOrder="lastName desc",asQuery=false);
   event.renderData(data=renderView("clients/pdf"), type="pdf");
} //pdf

 

Using Multiple Bootstrap Modals in ColdBox

In a ColdBox application we may have a listing page where we want to add an edit link which should open in a new popup window when clicked to show that record’s details. We can achieve this using Bootstrap modal. Here’s what we will need to do:

  • Create a new layout
  • Create the listing view
  • Create the edit view
  • Use setView to specify the new layout
  • Test the Bootstrap modal

Create the new layout

Let’s create a new layout for displaying contents with a Bootstrap modal. In this layout we only need the body content. So let’s strip out the header, navigations and footer. Under <app-name>\layouts, create a new file called Modal.cfm, and in this file add the following contents and save it:


<cfoutput>
   <div>#renderView()#</div>
</cfoutput>

Create the listing view

Now let’s create the listing view, e.g. <app-name>\invoices\views\list.cfm. Here’s the important fragment from list.cfm:


<thead>
  <tr align="left">
     <th>Date</th>
     <th>Amount</th>
     <th>Note</th>
     <th width="125" class="center">Actions</th>
  </tr>
</thead>
<tbody>
  <cfloop from="1" to="#arrayLen(rc.invoices)#" index="i">
    <tr>
      <td>#DateFormat(rc.invoices[i].getInvoiceDate(),"dd/mm/yy")#</td>
      <td>#DollarFormat(rc.invoices[i].getInvoiceAmt())#</td>
      <td>#rc.invoices[i].getInvoiceNote()#</td>
      <td width="150" class="center">
        <a href="#event.buildLink('')#">Edit</a>
      </td>
    </tr>
  </cfloop>
</tbody>

To turn the Edit link into a Bootstrap modal popup, change it to this:


<a href="#event.buildLink('your-edit-handler-with-params ')#"  title="Edit Invoice"
  data-toggle="modal" data-target="#invoiceRemoteModal" class="edit action">
  <span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
</a>

The important parts above are:

  • data-toggle=”modal”
  • data-target=”#invoiceRemoteModal”

And also towards the bottom of list.cfm, add the following:


<!-- Modal -->
<!-- launches edit view in a Bootstrap modal -->
<div class="modal fade" id="invoiceRemoteModal" tabindex="-1" role="dialog" aria-labelledby="editInvoiceLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content"></div>
  </div>
</div>

Please note that the id must match the data-target specified earlier in the Edit link. In this case the id of invoiceRemoteModal matches the data-target of invoiceRemoteModal.

Create the edit view

Now let’s create the edit view page (e.g. views\invoices\edit.cfm) (i.e. the content that the Bootstrap remote modal will load when you click the Edit link):


<cfoutput>
  <div class="modal-content">
    <div class="modal-header bg-primary">
      <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
      <h4 class="modal-title" id="editInvoiceLabel">Edit Invoice</h4>
    </div>
    <div class="modal-body">
       <!--- the edit view contents goes here --->
    </div> <!--- modal-body --->
    <div class="modal-footer">
    </div>
  </div><!--- modal content --->
</cfoutput>

Here’s the content for the modal-body:


<form id="frmInvoiceEdit" class="form-horizontal" method="post" action="#event.buildLink('invoices.save')#">
  <div class="form-group required">
    <label for="invoiceType" class="col-md-4 control-label">Invoice Type &nbsp;</label>
    <div class="col-md-3">
      <select name="invoiceType" class="form-control">
        <option value=""></option>
        <option value="Client" <cfif rc.invoice.getInvoiceType() eq "Client">selected</cfif>>Client</option>
        <option value="Supplier" <cfif rc.invoice.getInvoiceType() eq "Supplier">selected</cfif>>Supplier</option>
      </select>
    </div>
  </div>
  <div class="form-group required">
    <label for="invoiceDate" class="col-md-4 control-label">Invoice Date &nbsp;</label>
    <div class="col-md-5 date">
      <div class="input-group input-append date" id="dpEditInvoiceDate">
        <input type="text" class="form-control" name="invoiceDate"  value="#dateFormat(rc.invoice.getInvoiceDate(), 'dd/mm/yyyy')#" placeholder="dd/mm/yyyy"/>
        <span class="input-group-addon add-on"><span class="glyphicon glyphicon-calendar"></span></span>
      </div>
    </div>
  </div>
  <div class="form-group required">
    <label for="invoiceAmt" class="col-md-4 control-label">Invoice Amount ($)&nbsp;</label>
    <div class="col-md-7">
      <input type="text" name="invoiceAmt" class="form-control" value="#rc.invoice.getInvoiceAmt()#" size="35%">
    </div>
  </div>
  <div class="form-group required">
    <label for="invoiceNote" class="col-md-4 control-label">Invoice Note &nbsp;</label>
    <div class="col-md-7">
      <input type="text" name="invoiceNote" class="form-control" maxlength="150" value="#rc.invoice.getInvoiceNote()#" size="45%">
    </div>
  </div>
  <br>
  <div class="form-group">
    <div class="col-md-7 col-md-offset-3">
      <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
      <button type="submit" class="btn btn-primary">Save</button>
    </div>
  </div>
  <div class="form-group">
    <div class="col-md-7">
      <input type="hidden" name="invoice_id" id="invoice_id" value="#rc.invoice.getInvoice_id()#" />
    </div>
  </div>
</form>

Use setView to specify the new layout

In the edit method (action) for invoices handler, ensure you specifically set the Modal layout using the event.setView method, otherwise the default layout will be used, e.g.


public void function edit(event){
  var rc = event.getCollection();
  event.getValue("invoice_id","");
  rc.invoice = invoiceService.get( rc.invoice_id );
  event.setView(name="invoices/edit", layout="Modal");
}

Test the Bootstrap Modal

Ok, if everything was done correctly, you should see something similar to this:

date-multi-modal-1b

When you click on the Edit action (first icon) for the first invoice, it brings up the details for that invoice. However when you click to edit the second invoice, it shows the details of the first invoice. It looks like the data are not cleared after the modal closes. So to fix this issue, we need to clear the data when the modal closes by calling the removeData method. Open up list.cfm and add the following towards the end of the file:


<script>
  $(document).ready(function() {
    $('#invoiceRemoteModal').on('hidden.bs.modal', function(e) {
      $(this).removeData();
    });
  });//document
</script>

Now you should see the correct information when you click the Edit action on each individual invoice to bring up their details in a Bootstrap modal:

date-multi-modal-4a

date-multi-modal-5a

You tested it by clicking on the various form input, and discover that the datepicker no longer works after the initial click. So what is need is to close and re-initialise the datepicker when the modal closes and opens. Edit list.cfm and add the following code fragment replacing the existing fragment. That is, it should now look like this:


<script>
  $(document).ready(function() {
    $('#invoiceRemoteModal').on('hidden.bs.modal', function(e) {
      $(this).removeData();
      $("#dpEditInvoiceDate").datepicker( "destroy" );
    });
    $('#invoiceRemoteModal').on('shown.bs.modal', function (e) {
      $("#dpEditInvoiceDate").datepicker({});
    })
  });//document
</script>

And now the datepicker for the Invoice Date input field should launch every time you click it.

date-multi-modal-3

Date field validation with constraints and formvalidationJS in ColdBox

When developing web forms or applications, server and client side user input validations should be on every developers’ mind. In this quick guide we will provide a simple code example to illustrate how we can use ColdBox’s domain model constraint on the server side, and formvalidationJS and momentJS on the client side to perform some basic date field validations.

Firstly, let’s tackle server side validation using domain model constraints in ColdBox. Let’s create the Coldbox domain model for clients and add the constraints for the date of birth (dob) field:


component persistent="true" extends="BaseEntity" table="clients" {
  property name="client_id" fieldtype="id" generator="identity" setter="false";
  property name="title" sqltype="nvarchar" length="10";
  property name="firstname" sqltype="nvarchar" length="100";
  property name="lastname" sqltype="nvarchar" length="100";
  property name="dob" ormtype="date";
  property name="gender" sqltype="nvarchar" length="6";

  //validations
  this.constraints = {
    firstname = {required=true, requiredMessage="First Name is required"},
    lastname = {required=true, requiredMessage="Last Name is required"},
    gender = {required=true, requiredMessage="Gender is required"},
    dob = {
      required=true, requiredMessage="DOB is required",
      type="date", typeMessage="DOB must be a valid date",
      discrete="lte:#now()#", discreteMessage="DOB cannot be in the future"}
   } //constraints
}

The dob field has three constraints: required, type and discrete. That is, dob is required, has to be of type date and cannot be a date that is in the future. We are adding a custom message when the constraint fails. To add your own custom messages for specific constraints, use this constraint message convention:

{constraintName}Message = "My Custom Message";

So in our code above, we have the following custom messages for the constraints of required, type, and discrete:

  • requiredMessage=”DOB is required”
  • typeMessage=”DOB must be a valid date”
  • discreteMessage=”DOB cannot be in the future”

For more information on constraints, head over to the ColdBox wiki validation page.

Let’s test the dob field constraints before we add the client side validations. Here’s the error message if you select a future date for the dob field and submitted the update:

dob-model-future-constraint-error

Now let’s add client side validations for the dob field. We will need to do the following:

  • Add references to formvalidationJS and momentJS in the Main layout (layouts/Main.cfm)
  • In the view page where the dob field is used, we will need to add the necessary script to validate it using formvalidationJS and momentJS

In the head and body of layouts/Main.cfm, add the following to reference the required JavaScript libaries and frameworks:


<head>
<link href="/path/to/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css"/ >
<link href="/path/to/bootstrap/bootstrap-dialog.min.css" rel="stylesheet" type="text/css" />
<link href="/path/to/bootstrap/formvalidation/css/formValidation.min.css" rel="stylesheet" />
<link href="/path/to/bootstrap/bootstrap-datepicker/css/bootstrap-datepicker.min.css" rel="stylesheet" />
<link href="/path/to/bootstrap/bootstrap-datepicker/css/bootstrap-datepicker3.min.css" rel="stylesheet" />
</head>
<body>

<script src="/path/to/jquery/jquery.min.js"></script>
<script src="/path/to/bootstrap/js/bootstrap.min.js"></script>
<script src="/path/to/bootstrap/formvalidation/js/formValidation.min.js"></script>
<script src="/path/to/bootstrap/formvalidation/js/framework/bootstrap.min.js"></script>
<script src="/path/to/bootstrap/bootstrap-datepicker/js/bootstrap-datepicker.min.js"></script>
<script src="/path/to/moment.js"></script>

</body>

Then in the add/edit view page (i.e. edit.cfm) for the client domain model, towards the bottom of the page, add the part in the script section to perform client side validation using formvalidationJS and momentJS:


<form class="form-horizontal" role="form" id="frmClientEdit" method="post" action="#event.buildLink('clients.save')#">
 <div class="form-group required">
   <label for="dob" class="col-md-3 control-label">DOB &nbsp;</label>
   <div class="col-md-2 date">
    <div class="input-group input-append date" id="dpDOB">
     <input type="text" class="form-control" name="dob"  value="#dateFormat(rc.client.getDOB(), 'dd/mm/yyyy')#" placeholder="dd/mm/yyyy"/>
     <span class="input-group-addon add-on"><span class="glyphicon glyphicon-calendar"></span></span>
    </div>
   </div>
 </div>
</form>

<script>
$(document).ready(function() {
  $('#dpDOB').datepicker({
        format: 'dd/mm/yyyy'
  })
  .on('changeDate', function(e) {
        // Revalidate the dob field
        $('#frmClientEdit').formValidation('revalidateField', 'dob');
  });
  $('#frmClientEdit').formValidation({
    framework: 'bootstrap',
      icon: {
      valid: 'glyphicon glyphicon-ok',
      invalid: 'glyphicon glyphicon-remove',
      validating: 'glyphicon glyphicon-refresh'
    },
    fields: {
      dob: {
        validators: {
          notEmpty: {
            message: 'DOB is required and cannot be empty'
          },
          date: {
            message: 'The value is not a valid date',
            format: 'DD/MM/YYYY'
          },
          callback: {
            message: 'This date is in the future and invalid',
            callback: function(value, validator) {
              var m = new moment(value, 'DD/MM/YYYY', true);
              var now = moment();
              if (!m.isValid()) {
                return false;
              }
              return m.isBefore(now);
           } //callback
         } //validators
       } //dob
     }//fields
   }); //formvalidation
}); //document

To ensure that dob is not a date in the future, we use formvaliationJScallback feature in conjunction with momentJS. We compare the selected date value with current date and time. If it is greater, an error message is displayed to alert the user.

We’ve also add the re-validation for the dob field so that formvalidationJS re-validate this field when users clicks the calendar to re-select the dob. The result for validation when it fails and succeeds are:

dob-client-side-future-date-error-1

dob-client-side-future-date-error-2

ColdBox Basic – Layouts

According to the ColdBox wiki:

“A layout is simply an HTML file that acts as your shell where views can be rendered in and exists in the layouts folder of your application. The layout is a complete html document and you basically describe where views will be rendered. Much easier to build and simpler to maintain”.

Here are some notes about layouts (source: ColdBox wiki):

  • it can be composed of multiple views and one main view
  • can have as many as needed in your application
  • are easy to override or assign to different parts of your application
  • all layouts reside in the layouts folder
  • can also consume other layouts, and hence
  • can create nested layouts very easily via the renderLayout() method

In this exercise, we will use the Advanced Script ColdBox application template and:

  • Modify the Main layout to separate out the content for the navigation, body and footer
  • Create a navigatioin to access users and roles

Please note that the default event by convention is main.index and the default layout for an application is  main.cfm.

However, it is possible to choose a different default layout that can be used to render all your views in. This is done by setting that default layout in your Configuration CFC (i.e. in config/ColdBox.cfc) in the layoutSettings structure:

//Layout Settings
layoutSettings = {
	defaultLayout = "your-default-layout.cfm"
}

It is also possible to override layouts on-the-fly using the setLayout method from the event object, i.e.

event.setLayout( name )

Main Layout

Let’s edit layouts/Main.cfm and delete everything inside the body tag and replace it with the following:

<body>

<!---Container And Views --->
<div class="container-fluid">
 <div class="navbar navbar-inverse">
 <nav role="navigation">#renderView('tags/nav',cache=true,cacheTimeout='30')#</nav>
 </div>
 <!--- body content --->
 <div>#renderView()#</div>
 <footer class="footer">#renderView('tags/footer',cache=true,cacheTimeout='30')#</footer>
</div>

</body>

As you can see, the navigation, body content and footer for a page are now separated. The benefit of separating the navigation and footer is that we can reuse these same views in all other layouts. We then use the renderView() method to output the contents of the views (nav and footer) and the body content.

Let’s create a separate view for he nav and footer views.  Firstly under the views folder, create the tags subfolder. Within the tags subfolder, create the following files:

  • nav.cfm
  • footer.cfm

nav.cfm

<cfoutput>
<!---Top NavBar --->
<div class="navbar navbar-inverse navbar-fixed-top">
 <div class="navbar-inner">
   <!---Brand --->
   <div class="container">
     <!---Responsive Design --->
     <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
       <span class="icon-bar"></span>
       <span class="icon-bar"></span>
       <span class="icon-bar"></span>
     </a>
 
     <ul class="nav navbar-nav">
       <li class="active"><a href="#event.buildLink('')#">Home</a></li>
       <li class="dropdown">
         <a href="#event.buildLink('')#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Manage <span class="caret"></span></a>
         <ul class="dropdown-menu">
           <li><a href="#event.buildLink('security/users/list')#"><i class="icon-user"></i> Users</a></li>
           <li><a href="#event.buildLink('security/roles/list')#"><i class="icon-tags"></i> Roles</a></li>
         </ul>
       </li>
     </ul>
 
     <!---About --->
     <ul class="nav navbar-nav pull-right">
       <li class="dropdown">
         <a href="##" class="dropdown-toggle" data-toggle="dropdown">
         <i class="icon-info-sign icon-white"></i> About <b class="caret"></b>
         </a>
         <ul id="actions-submenu" class="dropdown-menu">
           <li><a href="#event.buildLink('security.logout')#"><i class="icon-fire"></i> Logout</a></li> 
         </ul>
       </li>
     </ul>
    </div> <!---end container --->
  </div> <!---end navbar-inner --->
</div> <!---end navbar --->
</cfoutput>

footer.cfm


 <p class="pull-right">
 <a href="##"><i class="icon-arrow-up"></i> Back to top</a>
 </p>
 <p>
 <a href="http://www.coldbox.org">ColdBox Platform</a> is a copyright-trademark software by
 <a href="http://www.ortussolutions.com">Ortus Solutions, Corp</a>
 </p>

Main View

Open main view (views/main/index.cfm). Remove all the content and add the following:


 <div class="row">
 <div class="col-md-12"><h1 align="center">Home Page</h1></div>
 </div>

Browse the main page. It should now look like this:

new-home-1

And the new navigation:


new-home-nav-2

Further Learning

For more detailed information  and discussion on layouts, please check out the ColdBox Layouts and Views page.