A Better Multi-Select with Chosen jQuery Plugin in ColdBox

Let’s improve the user experience for selecting multiple items in a select drop down list by using the chosen jQuery plugin. In this guide we will use the example of selecting multiple suppliers for a product in a ColdBox 4.2.x web application.

Follow these steps to setup the plugin, the entities, and finally making use of the plugin with the select element:

  • Download the chosen jQuery Plugin
  • The entities used
  • Create a new method to retrieve list of ID for select drop down
  • In edit handlers, retrieve list of values for select drop down
  • Import the chosen jQuery plugin scripts and styles
  • Define the select drop down list
  • Use the chosen jQuery plugin

Download the chosen jQuery Plugin

Head over to https://harvesthq.github.io/chosen/ to download the chosen jQuery plugin and save it to a location of your choice on the web server.

The Entities Used

Let’s define the entities used in this guide.

Product Entity


component persistent="true" table="products" {
   property name="productID" fieldtype="id" generator="identity" setter="false";
   property name="name" sqltype="nvarchar" length="150";

   property name="suppliers" fieldtype="many-to-many" cfc="models.suppliers.Supplier" singularname="supplier" fkcolumn="productID" inversejoincolumn="supplierID" linktable="products_suppliers";

}

Supplier Entity


component persistent="true" table="suppliers" {
   property name="supplierID" fieldtype="id" generator="identity" setter="false";
   property name="name" sqltype="nvarchar" length="50";
}

Create new method to retrieve list of ID for select drop down

In the product entity (e.g. models\products.cfc), create a new method to retrieve list of ID for the select drop down list. In this example, we are retrieving the list of suppliers ID for a product. A product has many suppliers. In models\products\Product.cfc add the following code:


public string function getSuppliersIDList() {
   var suppliers = "";
   for ( var i=1; i<=arrayLen(getSuppliers()); ++i ) {
      suppliers = listAppend(suppliers,getSuppliers()[i].getSupplierID());
   }
   return suppliers;
}

Retrieve list of values for select drop down

In the products edit handler (handlers\Products.cfc), we need to retrieve the list of suppliers for populating the select drop down:


component {
   property name="productService" inject="model";
   property name="supplierService" inject="model";

   public void function edit(event){
      var rc = event.getCollection();
      event.paramValue("id","");
      rc.product = productService.get( rc.id );
      rc.suppliers = supplierService.list(sortOrder="name",asQuery=false);
   } //edit
} //component

Import the chosen jQuery Plugin scripts and styles

In the default Main layout (e.g. layouts\Main.cfm), add the following to import the chosen jQuery plugin scripts and styles:


<cfoutput>
   #html.doctype()#
   <html lang="en">
   <head>
   <meta http-equiv="X-UA-Compatible" content="IE=Edge">
   <meta charset="utf-8">
   <title>#getSetting('appName')#</title>
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link href="/path/to/bootstrap.min.css" rel="stylesheet" type="text/css"/ >
   <link rel="stylesheet" href="/path/to/chosen.css" type="text/css"/ >
   </head>
   <body>
      <script src="/path/to/jquery.min.js"></script>
      <script src="/path/to/bootstrap.min.js"></script>
      <script src="/path/to/chosen.jquery.js" type="text/javascript"></script>
   </body>
   </html>
</cfoutput>

Define the select drop down

Edit views\products\edit.cfm and add the following:


<div class="form-group">
   <label for="suppliers" class="col-md-2 control-label">Suppliers &nbsp;</label>
   <div class="col-md-4">
      <select data-placeholder="Click or type to choose suppliers ..."
         name="suppliers" class="chosen-select form-control" multiple="multiple">
         <option value="">&nbsp;</option>
         <cfloop array="#rc.suppliers#" index="supplier">
            <option value="#supplier.getSupplierID()#">#supplier.getName()#</option>
         </cfloop>
      </select>
   </div>
</div>

Ensure the following is added on the select form element:

  • data-placeholder
  • class=”chosen=selelct”
  • multiple=”multiple”

Use the chosen jQuery Plugin

In the same edit view, i.e. views\products\edit.cfm, towards the bottom of the file before the closing body tag, add:


<script>
   <cfoutput>
   //retrieve selected ID for pre-population
   var #toScript(rc.product.getSuppliersIDList(),'lSuppliersID')#;
   var aSuppliersID = lSuppliersID.split(',');
   </cfoutput>

   $(document).ready(function() {
      //use chosenJS for multi select
      $('.chosen-select').chosen({width:'100%'});

      if (lSuppliersID != ''){
         //pre-populate selected supplier for edits
         $('.chosen-select').val(aSuppliersID).trigger('chosen:updated');
      }
   });
</script>

In lines 3-5, we retrieve the list of supplier ID for pre-populating a form when editing (i.e. the suppliers were already selected), and then on line 14 we must trigger an update to add the selected value to the multi-select box. On line 10 we activate the chosen jQuery plugin

You should see something like these if the chosen jQuery plugin is working as expected:

Advertisements

Create a Video Lightbox using Bootstrap, HTML5 and ColdFusion Custom Tag

Let’s create a video lightbox using Bootstrap, HTML5’s video tag and encapsulate it with a ColdFusion custom tag. Here’s what you need to do achieve this:

  • Define the CSS styles
  • Create the ColdFusion custom tag
  • Use the custom tag

Define the CSS styles

Create a new file and save it as videos.css. Enter the following styles and save:


.bs-video{
   margin: 10px 0px 10px 0px;
}

.modal-content {
   margin: 0 auto;
   display: block;
}

@media screen and (min-width: 768px) {
   .modal-dialog {
      width: 750px; /* New width for default modal */
   }
   .modal-sm {
      width: 350px; /* New width for small modal */
   }
}

.videogallery {
   width:900px;
   zoom:1;
}

Create the ColdFusion custom tag

We will use HTML5’s video tag and Boostrap modal to show the video in a popup. Create a new ColdFusion file and save it as video-popup.cfm. Enter the following code for this custom tag:


<cfif thisTag.executionMode EQ "start">
   <cfparam name="attributes.width" default="720">
   <cfparam name="attributes.height" default="405">
   <cfparam name="attributes.path" default="">
   <cfparam name="attributes.desc" default="">
   <cfparam name="attributes.vid" default="">
   <cfparam name="attributes.video" default="">
   <cfparam name="attributes.mediaType" default="video/mp4">
   <cfparam name="attributes.image" default="">

   <cfoutput>
      <div class="bs-video">
         <a href="##videoModal-#attributes.vid#" id="#attributes.vid#" class="btn btn-default modalLink" data-toggle="modal"><img src="#attributes.path#/thumbnails/#attributes.image#" alt="#attributes.desc#"/></a>

         <div id="videoModal-#attributes.vid#" class="modal fade">
            <div class="modal-dialog">
               <div class="modal-content">
                  <div class="modal-header">
                     <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                     <p class="modal-title">#attributes.desc#</p>
                  </div>
                  <div class="modal-body">
                     <video id="video-#attributes.vid#" width="#attributes.width#" height="#attributes.height#" controls>
                        <source src="#attributes.path#/#attributes.video#" type="#attributes.mediaType#">
                        Your browser does not support the video tag. Please upgrade your browser.
                     </video>
                  </div>
               </div><!--- modal-content --->
            </div><!--- modal-dialog --->
         </div><!--- videoModal --->
      </div><!--- bs-video --->
   </cfoutput>
</cfif>

Use the custom tag

Let’s use the custom tag we have just created. Create a new file and call it whatever you want. Let’s start with the following skeleton and add the following:

  • Link to Bootsrap library and styles
  • Import the ColdFusion custom tag
  • Import the styles for the video lightbox, i.e. videos.css

You should now have the following:


<!DOCTYPE html>
<html lang="en">
<head>
   <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
   <cfheader name="X-UA-Compatible" value="IE=Edge" />
   <title>Video Lightbox with HTML5, Bootstrap and ColdFusion Custom Tag</title>
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="stylesheet" href="/path/to/bootstrap.min.css">
   <link rel="stylesheet" href="/path/to/videos.css" type="text/css">
   <script src="/path/to/jquery.min.js"></script>
   <script src="/path/to/bootstrap.min.js"></script>
</head>
<body>

   <cfimport prefix="content" taglib="/path-to/custom-tags/video-tag-folder/" />

</body>
</html>

Now let’s make use of the ColdFusion custom tag we have created to display a video lightbox. In the body after the cfimport tag, add the following code snipet:


<body>
   <cfimport prefix="content" taglib="/path-to/custom-tags/video-tag-folder/" /> 
   <h1>Videos</h1>
   <div class="videogallery">
      <cfoutput>
         <div class="row">
            <div class="col-md-3">
               <content:video-popup path="path-to-video" image="path-video-thumbnail-image#" video="path-to-video-file" type="video/mp4" desc="video-desc" vid="-video-id" />
            </div>
         </div>
      </cfoutput>
   </div>
</body>

 

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