Skinning Bootstrap Tabs

Bootstrap is the number one downloaded project on GitHub today. A small, lightweight framework for layout, Bootstrap is a combination of CSS, HTML format, and JavaScript that allows one to create very dynamic, responsive web sites and applications. This isn't a new concept, being the same sort of thing that jQueryUI and Ext JS are conceptually designed for. Each has their merits, and differences. Those libraries primarily utilize JavaScript for designing and configuring layouts and components, whereas Bootstrap focuses primarily on HTML and CSS (and a small JS file for event handling) for building these things.

One of the things that sets Bootstrap apart is the ease with which one can "skin" the library. Bootstrap uses LESS for building it's CSS files. LESS allows you to define variables and functions, that are then utilized in style declarations. Once compiled, those variables and functions are parsed in the definitions, to create your CSS. By changing a few variables, you can completely change the look and feel of your application.

In this example, I'm going to change the default Bootstrap tab display. When you see the default Bootstrap tabs, they aren't always easily identified as a tabbed interface, until you mouse over a tab. My clients have become used to the contrast provided by the layouts of the other libraries I've mentioned, having seen those interfaces time and again. Here, we'll update the Bootstrap tabs so that they appear more like those used by jQueryUI.

Basic File Structure

I, personally, don't like to change a libraries files. Even though you can do so, I find it better to create an "override" file that will add or change a definition. That way, if I update that library I'm not trying to find and replicate my changes. First, I start off by organizing my assets. I personally like to separate my CSS from my images from my scripts. I also like to create sub-folders under each, to further organize my assets for easy change, management, and retrieval. From Bootstrap's Getting Started page, I downloaded both the basic download, as well as the "source" download. I moved the .less files out of the css folder in the "source" distribution, placing them in /assets/css/bootstrap. I then moved the .js files out of the js folder in the "basic" distribution, placing them in /assets/js/bootstrap. Last, I took the .png files, out of the img folder in either distro, and put them in to the /assets/img/bootstrap folder. The last thing I'm going to do is create a new sub-folder to the css folder, titled core. Within this folder I'm going to create three files: core.less, overrides.less, and variables.less.

Getting started with LESS

We're going to start off by doing something really simple. First I told you that I didn't want to change the core Bootstrap files, but instead create "overrides". Since we changed the basic file structure of the Bootstrap distro, the first thing we'll need to do is correct the pathing to the image sprites. What we're going to do, rather than including the Bootstrap css directly, is create our own "core" file. "Core" needs to include Bootstrap, and then our overrides. We do this by adding the following lines to our core.less.

view plain print about
1@import "../bootstrap/bootstrap.less";
2
3@import "overrides.less";

These statements will include these two files, when core.less is compiled. If you go back and look at bootstrap.less, you will see that it is nothing but import statements as well.We want to create variables that we might need to reuse elsewhere. This is the general purpose of our variables.less file. If we open Bootstrap's variables.less file (/assets/css/bootstrap), we can search through it and find two specific variables: @iconSpritePath, and @iconWhiteSpritePath. Copy and paste these two lines into our variables.less file, then adjust their paths to coincide with our new file locations.

view plain print about
1// Sprite icons path
2@iconSpritePath: "/assets/img/bootstrap/glyphicons-halflings.png";
3@iconWhiteSpritePath: "/assets/img/bootstrap/glyphicons-halflings-white.png";

Finally, we need to use these updated variables in our overrides.less file, to override the proper style declarations. First we add the import statement that includes our variables.less file. Then, we find the declarations, in the sprites.less file (/assets/css/bootstrap), that currently define these paths (you can search for the variable names). Copy and paste these two declarations into our overrides.less file, and then take out all of the definition items except for those that define the background images. Our override only needs to "override" the background image paths, keeping everthing else.

view plain print about
1@import "variables.less";
2
3/* Icon Sprite Path Overrides */
4[class^="icon-"],
5[class*=" icon-"] {
6 background-image: url("@{iconSpritePath}");
7}
8
9/* White icons with optional class, or on hover/focus/active states of certain elements */
10.icon-white,
11.nav-pills >
.active > a > [class^="icon-"],
12.nav-pills > .active > a > [class*=" icon-"],
13.nav-list > .active > a > [class^="icon-"],
14.nav-list > .active > a > [class*=" icon-"],
15.navbar-inverse .nav > .active > a > [class^="icon-"],
16.navbar-inverse .nav > .active > a > [class*=" icon-"],
17.dropdown-menu > li > a:hover > [class^="icon-"],
18.dropdown-menu > li > a:focus > [class^="icon-"],
19.dropdown-menu > li > a:hover > [class*=" icon-"],
20.dropdown-menu > li > a:focus > [class*=" icon-"],
21.dropdown-menu > .active > a > [class^="icon-"],
22.dropdown-menu > .active > a > [class*=" icon-"],
23.dropdown-submenu:hover > a > [class^="icon-"],
24.dropdown-submenu:focus > a > [class^="icon-"],
25.dropdown-submenu:hover > a > [class*=" icon-"],
26.dropdown-submenu:focus > a > [class*=" icon-"] {
27 background-image: url("@{iconWhiteSpritePath}");
28}

Ok, now that you have the mechanics out of the way, all you need to do is compile your file. If you use a dynamic server side language (ColdFusion for instance), then you might be using an asset management framework that will automatically compile LESS files for you at runtime (I like cfStatic). But, you can always get a standalone compiler that will process your files manually. There are many freeware programs available on the web. I Googled "Windows LESS compiler", and came up with WinLess. I was able to plugin the folder name, select my core.less file (you only need to compile the one, as it includes the rest), and hit "compile" to get my core.css file created.

I unselected the Minify option, so that could see the underlying result in an easy to read format. For production code, I suggest having a minified and unminified version, using the unminified for debugging purposes. If you check your new, unminified core.css file, you can go all the way to the bottom and see your two overriding style declarations, verifying that they now reflect the updated sprite paths.

Creating Tabs

OK, we have the basics out of the way. Now lets make some tabs. First, lets write up an index.html that will be prepared for Bootstrap, using our new all-in-one css file.

view plain print about
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Bootstrap Themed Nav</title>
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <meta name="description" content="">
8 <meta name="author" content="">
9 <link href="assets/css/core/core.css" rel="stylesheet">
10 <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
11 <!--[if lt IE 9]>
12 <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
13 <![endif]-->

14 </head>
15 <body>
16 <div class="container">
17
18 </div>
19 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
20 <script src="assets/js/bootstrap/bootstrap.min.js"></script>
21 </body>
22</html>

The next thing we need is the proper markup to create a tabbed interface within Bootstrap.

view plain print about
1<ul id="myTab" class="nav nav-tabs">
2 <li class="active"><a data-toggle="tab" href="#home">Home</a></li>
3 <li><a data-toggle="tab" href="#profile">Profile</a></li>
4 <li><a data-toggle="tab" href="#tab3">Tab3</a></li>
5 <li><a data-toggle="tab" href="#tab4">Tab 4</a></li>
6</ul>
7<div id="myTabContent" class="tab-content">
8 <div id="home" class="tab-pane fade active in">
9 ...
10 </div>
11 <div id="profile" class="tab-pane fade">
12 ...
13 </div>
14 <div id="tab3" class="tab-pane fade">
15 ...
16 </div>
17 <div id="tab4" class="tab-pane fade">
18 ...
19 </div>
20</div>

This produces a basic Bootstrap tabbed interface. No additional JavaScript is required here, the base bootstrap.js will handle all the basic event binding necessary for a fully functional interface. But, as you can see, there's not much in the way of contrast, identifying tab separation, nor is your tabbed interface really separated from the rest of your page. What's needed now is to create that contrast and separation.

Skinning Tabs

We'll look to jQueryUI's tab representation, as a guide for how we wish ours to look. We won't get an exact match, but we can get really close. First, let's start with the new stuff. jQueryUI's tab sets have a border around the entire interface, separating the tabs from the rest of page flow. Bootstrap doesn't have this, so we'll make it. We'll add a new div around the nav-tabs unordered list and tab-conent div, and give it a class: nav-tabs-container. Next, we'll add a style declaration to our overrides.less file.

view plain print about
1/* Add a tab container class, for a border around the entire tabset */
2.nav-tabs-container {
3 padding: 3px;
4 border: 1px solid @grayLight;
5 .border-radius(4px);
6}

Here's what we're doing here. We gave the container a 3 pixel padding all the way around. We gave it a one pixel solid light gray border, using the color variable in Bootstrap's variables.less file. Last, we gave it rounded corners with a four pixel radius (for those browsers that support it) by calling on the .border-radius() LESS function that's defined in Bootstrap's mixins.less file.

Our next step is to see what makes jQueryUI's tab interface different. We make a list of the basic elements.

  • The tab bar (ul with a class of nav-tabs) has:
    • a one pixel gray border with rounded corners
    • padding on the left, top, and right of a few pixels
    • a gradient background of a gray that is darker than the tabs
  • The tabs (list item tags containing anchor tags) have:
    • active tab (li with a class of active) has:
      • a white background
      • a one pixel gray border with rounded corners
      • no bottom border
    • inactive tabs (li's without classes) have gray backgrounds, of a lighter gray than the nav-tabs
    • active and inactive tabs have text that is gray

At this point, our next step is to find the original Bootstrap declarations for tabs. These can be found in the navs.less file (/assets/css/bootstrap). Copy and paste these declarations into your overrides.less file. From here, it's a matter of removing lines that won't change, adjusting lines that meet your requirements, and adding any new code needed to finalize those requirements. I'm not going to go over every line of code, instead giving you the full set, but we'll hit the high parts. First, the code:

view plain print about
1/* Tab Theming Overrides */
2// TABS
3// ----
4
5/* Add a tab container class, for a border around the entire tabset */
6.nav-tabs-container {
7 padding: 3px;
8 border: 1px solid @grayLight;
9 .border-radius(4px);
10}
11
12// Give the tabs something to sit on
13.nav-tabs {
14 border: 1px solid @grayLight;
15 padding: 4px 3px 0 3px;
16 margin-bottom: 5px;
17 .border-radius(4px);
18 #gradient >
.vertical(@grayLighter, @grayLight);
19}
20
21// Actual tabs (as links)
22.nav-tabs > li > a {
23 line-height: 10px;
24 background-color: @grayLighter;
25 border: none;
26 outline: 0;
27 &:hover,
28 &:focus {
29 border-style: solid;
30 border-width: 1px 1px 0;
31 border-color: @grayLight @grayLight transparent;
32 }
33 &:link,
34 &:active,
35 &:visited,
36 &:hover,
37 &:focus {
38 color: @gray;
39 }
40}
41
42.nav-tabs > li:not(.active) > a {
43 &:hover,
44 &:focus {
45 padding: 7px 11px;
46 }
47}
48// Active state, and it's :hover/:focus to override normal :hover/:focus
49.nav-tabs > .active > a,
50.nav-tabs > .active > a:hover,
51.nav-tabs > .active > a:focus {
52 border-style: solid;
53 border-width: 1px 1px 0;
54 border-color: @grayLight @grayLight transparent;
55}

We mentioned earlier the use of variables within LESS. Throughout this block of code you will see multiple references to various colors identified in the base Bootstrap variables.less file. We also previously utilized the .border-radius() method from Bootstrap's mixins.less file. We use this again, within this block of code, as well as other mixin methods, such as the .vertical() method we're using in our gradient definition. It's a good idea to look at the mixins.less and variables.less, to get ideas as to what's available to you. You will also notice nested declarations (such as the link psuedo selectors) that will automatically build out with LESS.

That's it! I hope you found this post helpful and informative. Any feedback, please leave me a comment. Here's what I hope you'll takeaway from this post:

  • Bootstrap is a powerful layout framework
  • Bootstrap is easy to modify/skin by making minor modifications using LESS
  • LESS is a great way to create dynamic, adjustable CSS
  • LESS can be a lot of fun

ColdFusion 10 File Uploads and MIME Types

Quick Note: On one project that I'm on, we're in the process of moving from ColdFusion 9 to 10. During some regression testing, one of the testers began receiving errors on file uploads. When looking at the code, I saw this:

view plain print about
1fileUploadResult = fileUpload(arguments.tierCSSDirectory, "header_background_image", "image/*", "overwrite");

Now, I remembered that there were changes to MIME type checking in CF 10, but I wasn't entirely sure. This code, written by one of our developer's several months ago, would allow any "image" MIME type. What we discovered is that we couldn't do this kind of wildcard mapping under CF 10, that we now had to list out each accepted MIME type as a comma delimited list.

Just thought I'd share.

ColdFusion Position - Denver, Colorado - Zen Planner

Ben Pate, of Zen Planner, contacted me yesterday about a new position they have, in their office in Denver, Colorado. A quick look shows a fun company to work for, with a great list of benefits. Check out their job posting and, if you're interested, drop Ben an email.

2012 In Review, and the View for 2013

2012 was....unexpected. January really kicked it off when my immediate supervisor left, to take a position with another company. When this happened my employer approached me about taking on his position. Now, I had left a management position to come to this company, so that I could again focus on writing code, so this meant stepping back into a management role. This is also the third time this particular scenario has played out in my career. Luckily, this position still allows me to write code (at least for the moment), I'm just putting in more time.

Time. The one thing we all need, and the one thing we can't make. I've put in a lot of time at the office, for both my day job and side contracts. You do what you have to do to get things done, and there are some things I'm trying to get done. That said, 2013 will have me dialing it back a bit. I have some current obligations, but I'm gonna drop back some after that, and put some time towards more important things, like my Open Source projects, development mentoring, my health, and (most importantly) my family.

In 2012 I started riding a bike (bicycle) again. Nothing fancy, just an 18 speed Mongoose from Walmart. What was important was that it got me active again. Current work load has put a temporary damper on riding, but I was doing 10+ miles a day. Time to get back into the groove. Not only is it healthy, but it's fun.

One thing I want to do this year is become more involved beyond my desk. It's time to rejoin the world. I want to start speaking to the development community again (which I started in 2012, and want to continue). I want to find a civic organization to become a part of. I want to get more active with the American Legion and the VFW. And, I want start telling Washington how I really feel about the job they're (not) doing.

2012 was the year I moved all of my Open Source projects to GitHub. Each of those projects saw some movement last year, and I'm looking to get some more spun up in 2013. Still a huge focus on dealing with ColdFusion json data, but I do have some JQuery utility bits out there, as well as starting to work on some Bootstrap components, and the Google Maps custom tag.

2012 was also a year for consulting. I've done some minor consulting in the past, but 2012 saw me traveling for consults. In one project, I consulted with a niche market company in creating an MSOC platform for their growing business, to sell and host low cost, high impact sites for their industry. For three days we hashed out exactly what it was they were trying to accomplish, going over the details of systems and code architecture, scalability, and standards. With one developer they built (from scratch), tested, and launched their new platform in six months, with their own custom CMS, templating engine and more. I enjoy consulting, particularly in addressing architecture challenges, and hope to do more of that in 2013.

So, I know some of the things I would like to do in 2013. I could map it all out, but that lacks flexibility. I didn't plan for all of the things that came my way in 2012, but most of it was for the better. May 2013 be a wonderful, and prosperous, new adventure for everyone.

KnockoutJS Starter - A Review

A few weeks back, Packt Publishing contacted me about reviewing one of their new titles, KnockoutJS Starter. Now, I'm in the middle of two contracts, plus my day job, and the holidays right now, so the idea of a "quick review" wasn't all that appealing. That said, I was interested in the material, and this is one of a new "Starter" line of books that Packt has started publishing. Basically small, quick primers that get you up and running with something new.

So, here's the "quick review". KnockoutJS Starter is by Eric M. Barnard. My e-book copy says it's 69 pages long, but the first 12 are the TOC, credits, and format info, so you take out the resources in the back of the book too and you're talking about 55 pages of meat. And there is meat.

The "Starter" books kind of come off as a printed blog series, sometimes, but Eric has done a pretty good job here, for the most part. The first half of the book takes you through installing the files, and setting up a quick sample app to take and edit inventory information. Now, it's all client side, with no data to start with, but line for line copy of the code will get you working. And, it was pretty slick how KnockoutJS ties data and interface together fairly seamlessly. And Eric explains how those connections are made very well.

While the base example, in the beginning of the book, is pretty straight forward, the second half could use some help. The second half goes beyond the basics, trying to describe Subscribables, Observables, and Bindings and Handlers. I was able to get through it, having some prior knowledge conceptually, but some of it is still confusing. The right idea was there, but the execution could use some polish and follow-up.

Ultimately, it was still a good introduction to KnockoutJS. If you aren't familiar with the library, I would take a good look at Eric and Packt's "Starter" book, and dive right in.

Mura Shared ORM Model - A Follow-Up

So, in my last post I talked about setting up a shared ORM model within Mura CMS. But, you always find the kicker after-the-fact. Mine's not a biggie though. I had setup a CF mapping to myModel

view plain print about
1<cfset this.mappings["/myModel"] = variables.mapPrefix & variables.BaseDir & "/mySite/includes/themes/mySite/model">

Turns out this wasn't the best location. I was pushing to the themes directory, because that was what the client wanted ("What the client wants....") Sometimes, you find a valid reason to deviate.

The Mura {site}/includes folder has it's own Application.cfc, with this wonderful tidbit of code in it:

view plain print about
1<cfif not listFindNoCase("styles.js.cfm,templates.js.cfm,editor.css.cfm,default.js.cfm,config.js.cfm",listLast(cgi.SCRIPT_NAME,"/"))>
2        <cfoutput>Access Restricted.</cfoutput>
3        <cfabort>
4        </cfif>

Now, most of the time this won't matter to you. Until you need to hit a remote method, of your model, for an Ajax request. Then...well, you see what happens. Now, you could probably change the Application.cfc, but I don't know if Mura won't overwrite this file on some future update, so it's better to just move my model into my {site} directory, as a sibling of the includes folder.

Creating a shared ORM model in Mura CMS

So, I'm working a new project, and using Mura CMS. We decided to write our modules as plugins, and are using the MuraFW/1 plugin template to do so. Thing is, each plugin really needs to share a common model that uses ColdFusion's ORM.

The MuraFW/1 plugin template has facility for defining a cfclocation for ORM configuration, but that location is relative to the plugin itself, so this took a little hunting. Finally, what made the most sense was to define ORM for the Mura site (as a whole), and let that definition propogate down to the plugins.

For those who are unfamiliar with Mura, it's a very nice CMS, and very extensible, but incredibly undocumented. There is some documentation, but it's spotty, and there are articles on their site that are quite dated. (I know this is something they are working on, so I'll leave it at that.) Luckily, I do write code for a living, so I just started pulling on strings until I found the thread I needed.

The MuraFW/1 template's application.cfc includes the core application settings:

view plain print about
1include '../../config/applicationSettings.cfm';

That works to our advantage, as anything defined in the core app then becomes available to the plugin. Looking at that file, I started looking for anything related to ORM. The first bit I came upon showed me this:

view plain print about
1<cfset this.ormenabled = properties.getProperty("ormenabled","true") />

From there, I had to figure out where ormenabled would come from. I discovered that all of those properties are defined in the /config/settings.ini.cfm file. So, if I added the right properties, I should have my orm configuration. Here is a list of the properties supported by the applicationSettings.cfm at this time:

  • ormenabled
  • ormdbcreate
  • ormcfclocation
  • ormflushAtRequestEnd
  • ormeventhandling
  • ormautomanageSession
  • ormsavemapping
  • ormskipCFCwitherror
  • ormuseDBforMapping
  • ormautogenmap
  • ormlogsql

So, knowing this, I started looking at my model. First, I placed the model in my theme:

/mysite/includes/themes/mysite/model

This was a requirement of the client, so that was ok for me. Except now, I needed a reference to that directory. A mapping. Mura allows custom CF mappings, by making changes to the /config/mappings.cfm file:

view plain print about
1<cfset this.mappings["/myModel"] = variables.mapPrefix & variables.BaseDir & "/mysite/includes/themes/mysite/model">

Now that I had my mapping, I needed to setup the ORM settings in that settings.ini.cfm file:

view plain print about
1ormenabled=true
2ormcfclocation=/myModel
3ormflushAtRequestEnd=false
4ormautomanageSession=false
5ormeventhandling=true

The only thing I was missing was my ORM Event Handler. While the settings allowed me to enable ormeventhandling, they did not allow me to define the handler. For that, I added the setting in my plugin Application.cfc files, right after the initial includes:

view plain print about
1this.ormsettings.eventhandler = "myModel.aop.GlobalEventHandler";

I reloaded my application, then updated my plugins (in the Mura Plugin admin) for good measure. Presto! Shared ORM! Hopefully this will help others from spinning their wheels.

Side Note: During this process, I also updated to the Mura v6 Preview. Wow! What an improvement. And the Bootstrap usage should make layout and theming much easier.

VFS and Zip Files...Kinda

There are several posts, out there in the wild, about using ColdFusion's built-in VFS support for working with zip files. I posted the other day (and fairly quickly removed it), about using the FileMove() function to rename a zip file and all of it's contents. What I discovered, though, was that this wasn't possible. Even though the documentation states that FileMove() and FileCopy() support the in-memory file system, I found that zip files are not included in that support. Luckily there was the CFScript Community Components project, for working with zip files within script. And, though zip files were unsupported, "ram://" was still a viable option:

  /**
   *  Rename a zip file, and the internal files as well
   *  @access private
   *  @returntype boolean
   *  @output false
   */
  function RenameZipAndContents (required string srcPath, string destPath=ARGUMENTS.srcPath, required string srcFileRoot, required string newFileRoot) {
    // Create the zip object
    var zip = CreateObject("component", "com.ccc.zip").Init();
    // Create a temp directory in RAM
    var vfsDir = "ram:///" & ARGUMENTS.newFileRoot & "/";
    DirectoryCreate(LOCAL.vfsDir & "zip");
    // Unzip the source file to the RAM directory
    zip.unzip(destination=LOCAL.vfsDir & "zip", file=ARGUMENTS.srcPath & ARGUMENTS.srcFileRoot & ".zip");
    // Get a list of the files
    var files = DirectoryList(LOCAL.vfsDir & "zip", false, "query");
    // Loop the file list
    for (LOCAL.file in LOCAL.files) {
      // Get the file extension
      var ext = Right(LOCAL.file.name, 4);
      // Rename the file
      FileMove(LOCAL.vfsDir & "zip/" & LOCAL.file.name, LOCAL.vfsDir & "zip/" & ARGUMENTS.newFileRoot & LOCAL.ext);
      // Zip up renamed file in new zip
      zip.zip(file=ARGUMENTS.srcPath & ARGUMENTS.srcFileRoot & ".zip", source=LOCAL.vfsDir & "zip/" & ARGUMENTS.newFileRoot & LOCAL.ext);
      // Delete original file from zip
      zip.delete(file=ARGUMENTS.srcPath & ARGUMENTS.srcFileRoot & ".zip", entrypath=LOCAL.file.name);
    }
    // Delete the temp RAM directory
    DirectoryDelete(LOCAL.vfsDir, true);
    // Rename the original zip file
    FileMove(ARGUMENTS.srcPath & ARGUMENTS.srcFileRoot & ".zip", ARGUMENTS.destPath & ARGUMENTS.newFileRoot & ".zip");

return true; }

Yes, that's a lot of jumping through hoops, and the multiple zip operations really drag out something that should be pretty simple. I'm not sure why zip isn't supported by these operations. Maybe some future revision will correct that.

Bootstrap Tree

It'll be a little while before I add this to the projects link on the site, but yesterday I pushed a new project up to GitHub. Bootstrap Tree is a lightweight Tree component, for use with the Bootstrap framework. This is still a work in progress, and very bare bones at the moment, but basically you write some formatted HTML (nested lists with data attributes), include the css and js (very small) and it works. Currently you'll have to download the code for the example, but I welcome any feedback, suggestions, comments, etc.

I can think of several things to add to this project, but what do you look for in a tree component?

ColdFusion Roadmap

Shilpi, the 'Security Czar' of Adobe's ColdFusion Engineering team, recently posted about the Roadmap for future ColdFusion Server Devlopment, post CF 10. This links to an abbreviated set of slides, presented in a PDF document, that layout the focus on next two versions of CF.

There are some things you might easily miss, in reading over the slides. First thing I picked out was in "Splendor"'s Other focus areas section, you see Pluggable Framework mentioned. Hmmmmmm...

Mobile, Social, HTML 5 and the Cloud are big topics here, but I also noted that "Dazzle" has Customizable Enterprise Video Portal mentioned. Interesting...

Another one that's easy to miss? How about the timeline? According to the last slide, "Splendor" looks like a 2013 release, while "Dazzle" is slated for 2014. That's a much more progressive release cycle than we're used to seeing.

The biggest, immediate take away here, is the fact that Adobe is obviously investing time and resources in to continuing to produce and progress the ColdFusion platform. That's a win.

Previous Entries / More Entries