Quantcast
Channel: Adam Cameron's Dev Blog
Viewing all 1332 articles
Browse latest View live

ColdFusion 11: lists and arrays and empty elements and weird ways to fix stuff

$
0
0
G'day:
Someone has asked me to draw attention to this issue, as they are dissatisfied with the way Adobe have handled it. Personally, I'm ambivalent, but err more towards the community members' positions than Adobe's. The detail of the ticket boils down to this:

Prior to CF10 to turn a repeated form element into an array we used getPageContext().getRequest().getParameterMap().

This worked smoothly this feature was replaced in CF10 with the application flag:
<cfset This.sameFormFieldsAsArray = true> This works nice except it removes empty elements. getPageContext()...getParamterMap() now returns an empty struct.

I made this comment against the ticket a while back:

  • Adam Cameron.
    12:56:36 AM GMT+00:00 Dec 5, 2013
    To be clear, I think there's two things that need addressing here:
    1) restore getPageContext().getRequest().getParameterMap();
    2) if This.sameFormFieldsAsArray is true, make the form scope work as per expectation too.

    -- 
    Adam
    The fix for this has been to fix the getParameterMap() function, but they left the form scope as-is: IE, it loses data about empty form fields. Here's the Adobe justification:

    • Himavanth Rachamsetty
      9:47:20 PM GMT+00:00 Feb 23, 2014
      We didn't change the existing functionality of form scope when This.sameFormFieldsAsArray is true.
      That is because this will create inconsistency between array and comma separated representations of same form fields in FORM scope.

      Instead we have brought back getPageContext().getRequest().getParameterMap().
      You should use that.
      I'm not so happy with this. Basically there's been a bug in ColdFusion's handling of same-named form fields all along: the list of values exposed in the form scope doesn't preserve the existence of form fields that had blank values. This was wrong. This was always wrong. The problem most likely stemmed from the poor implementation of lists in ColdFusion wherein empty values were ignored (this was always stupid), so I guess they didn't bother returning the empty values if they were just going to be lost anyhow.

      Anyway, arrays in ColdFusion do not have this problem, so it's a bit bloody daft to continue to lose data if one doesn't need to.

      The rationalisation that "That is because this will create inconsistency between array and comma separated representations of same form fields in FORM scope." is facile because the two can't coexist anyhow, so it's a non-issue.

      What should be done here is that the list version should be fixed so that it doesn't omit data. Then the list and the array would match.

      But I'd say this issue has not been dealt with satisfactorily. And it's another example of some very torturous logic when it comes to addressing issues (or justifying not addressing issues).

      --
      Adam

      ColdFusion 11: .map() and .reduce()

      $
      0
      0
      G'day:
      More ColdFusion 11 testing. This time I look at the new .map() and .reduce() methods that each of array, struct and lists now have. It's mostly good news.

      ColdFusion has increased its repertoire of object-iterator functions further in ColdFusion 11. In ColdFusion 10 it had the following:
      listEach(), for some reason was not implemented in ColdFusion 10, but is there in ColdFusion 11.

      Just to recap on the each() and filter() functionality, here's a quick example of the array versions of each of them:

      each()


      letters = ["a","b","c","d"];
      arrayEach(letters, function(){
      writeDump(arguments);
      });

      On ColdFusion 10, we get this:

      struct
      1a
      struct
      1b
      struct
      1c
      struct
      1d

      So we see that arrayEach() does what it says on the tin: it calls the callback for each element of the array. And the callback receives the value of each array element. Now on ColdFusion 11, this code yields... bugger all. It doesn't error, but it doesn't do anything. This had me scratching my head for quite a while, but I just cracked it... On ColdFusion 11 one must specify the arguments of the callback (this should not be necessary), thus:

      arrayEach(letters, function(v,i){
      writeDump(arguments);
      });

      Then it works:

      struct
      I1
      Va
      struct
      I2
      Vb
      struct
      I3
      Vc
      struct
      I4
      Vd

      (One doesn't nee to specify both arguments; just the index one is fine. This is a bug, and I will raise it accordingly: 3713035. listEach() has the same problem: one needs to specify the argument in the callback definition, or the function doesn't work).

      Anyway, it's a good enhancement to ColdFusion 11 that the callback also receives the index as well the value.

      filter()

      The filter methods also iterate over the given object and returns a new object. The callback returns a boolean which determines whether the current element is returned in the new object, eg:

      numbers = "1,2,3,4";
      odds = listFilter(numbers, function(v){
      return v MOD 2;
      });
      writeDump([{numbers=numbers},{odds=odds}]);

      Here the callback returns true for each odd list element, so we end up with a list with just the odd numbers in it:

      array
      1
      struct
      NUMBERS1,2,3,4
      2
      struct
      ODDS1,3

      So those are the ones from ColdFusion 10. Old news.

      map()

      The map() functions iterate over the collection (be it a list, array or struct), and returns a new object with an element for each of the ones in the original collection. The callback in this case returns the new element for the new collection, which is derived from the original collection. So it remaps the original collection. Here's examples of each of them:

      listMap()

      This function doesn't work, I'm afraid (or I'm doing something wrong which I cannot identify). We're not off to a good start here. Here's some sample code:

      rainbow    = "Whero,Karaka,Kowhai,Kakariki,Kikorangi,Tawatawa,Mawhero";

      externalList = "";
      reverseRainbow = listMap(rainbow,function(v,i,l){
      var newValue = "#i#:#v.reverse()#";
      externalList = externalList.append(newValue);
      return newValue;
      });
      writeDump([{rainbow=rainbow},{reverseRainbow=reverseRainbow},{externalList=externalList}]);

      externalList = "";
      reverseRainbow = rainbow.map(function(v,i,l){
      var newValue = "#i#:#v.reverse()#";
      externalList = externalList.append(newValue);
      return newValue;
      });
      writeDump([{rainbow=rainbow},{reverseRainbow=reverseRainbow},{externalList=externalList}]);

      This contains the same example using both the listMap() function, and the .map() method. Here's the output:

      array
      1
      struct
      RAINBOWWhero,Karaka,Kowhai,Kakariki,Kikorangi,Tawatawa,Mawhero
      2
      struct
      REVERSERAINBOWWhero,Karaka,Kowhai,Kakariki,Kikorangi,Tawatawa,Mawhero
      3
      struct
      EXTERNALLIST1:orehW,2:akaraK,3:iahwoK,4:ikirakaK,5:ignarokiK,6:awatawaT,7:orehwaM
      array
      1
      struct
      RAINBOWWhero,Karaka,Kowhai,Kakariki,Kikorangi,Tawatawa,Mawhero
      2
      struct
      REVERSERAINBOWWhero,Karaka,Kowhai,Kakariki,Kikorangi,Tawatawa,Mawhero
      3
      struct
      EXTERNALLIST1:orehW,2:akaraK,3:iahwoK,4:ikirakaK,5:ignarokiK,6:awatawaT,7:orehwaM

      I've added in the externalList to show you what reverseRainbowshould look like. listMap() is supposed to work that the callback receives the element value, its index, and the whole list as arguments. Then it uses that information however is appropriate to create and return a new element. Here the new element is very contrived: the element index, and its value (reversed).

      However the original list is just being returned by listMap() here. That's wrong. Bug: 3713038.

      Also note listMap() takes some other arguments I'm not using here: the list delimiter and a flag as to whether to respect empty list items (this is like most/all other list functions). But there's another slight glitch here: those values should also be passed to the callback, as they might be necessary for doing the element remapping. Bug: 3713043.

      It looks like we're off to a shocking start here, but that's the end of the bugs I found.

      arrayMap()

      In this example we can get a better idea of how the mapping process is supposed to work. Here's an example using both the function and the method:

      rainbow    = ["Whero","Karaka","Kowhai","Kakariki","Kikorangi","Tawatawa","Mawhero"];
      colourInList = arrayMap(
      rainbow,
      function(v,i,a){
      return replace(a.toList(), v, ucase(v));
      }
      );
      writeDump([rainbow,colourInList]);


      rainbow.map(function(v,i,a){
      return replace(a.toList(), v, ucase(v));
      });
      writeDump([rainbow,colourInList]);

      array
      1
      array
      1Whero
      2Karaka
      3Kowhai
      4Kakariki
      5Kikorangi
      6Tawatawa
      7Mawhero
      2
      array
      1WHERO,Karaka,Kowhai,Kakariki,Kikorangi,Tawatawa,Mawhero
      2Whero,KARAKA,Kowhai,Kakariki,Kikorangi,Tawatawa,Mawhero
      3Whero,Karaka,KOWHAI,Kakariki,Kikorangi,Tawatawa,Mawhero
      4Whero,Karaka,Kowhai,KAKARIKI,Kikorangi,Tawatawa,Mawhero
      5Whero,Karaka,Kowhai,Kakariki,KIKORANGI,Tawatawa,Mawhero
      6Whero,Karaka,Kowhai,Kakariki,Kikorangi,TAWATAWA,Mawhero
      7Whero,Karaka,Kowhai,Kakariki,Kikorangi,Tawatawa,MAWHERO
      array
      1
      array
      1Whero
      2Karaka
      3Kowhai
      4Kakariki
      5Kikorangi
      6Tawatawa
      7Mawhero
      2
      array
      1WHERO,Karaka,Kowhai,Kakariki,Kikorangi,Tawatawa,Mawhero
      2Whero,KARAKA,Kowhai,Kakariki,Kikorangi,Tawatawa,Mawhero
      3Whero,Karaka,KOWHAI,Kakariki,Kikorangi,Tawatawa,Mawhero
      4Whero,Karaka,Kowhai,KAKARIKI,Kikorangi,Tawatawa,Mawhero
      5Whero,Karaka,Kowhai,Kakariki,KIKORANGI,Tawatawa,Mawhero
      6Whero,Karaka,Kowhai,Kakariki,Kikorangi,TAWATAWA,Mawhero
      7Whero,Karaka,Kowhai,Kakariki,Kikorangi,Tawatawa,MAWHERO

      Here the callback receives the array element value, its index, and the entire array. From this, we convert the array to a list, and then highlight (with capitals) the current element in that list, returning the whole list. So the resultant remapped array contains an element for each original element, but completely different data than the original array; with each new element being that remapping done in the callback. And the method version works exactly the same (I'm both demonstrating and testing here too, hence the double-up).

      If you have a sparse array - one without an element at each index - you need to deal with this by hand. The iteration is index-centric, not element-centric, so each index will have the callback called on it, so you need to deal with the possibility of no value being passed to the callback:

      a = [1];
      a[3] = 3;
      writeDump(a);

      result = a.map(function(v,i,a){
      if (structKeyExists(arguments, "v")){
      return v^2;
      }
      });
      writeDump(result);

      result = a.map(function(v=0,i,a){
      return v^2;
      });
      writeDump(result);

      Output:

      array
      11
      2[undefined array element] Element 2 is undefined in a Java object of type class coldfusion.runtime.Array.
      33
      array
      11
      2[undefined array element] Element 2 is undefined in a Java object of type class coldfusion.runtime.Array.
      39
      array
      11
      20
      39

      Here I am using two different techniques. In the first version I am simply checking to see if the value exists, and only returning a mapped value if so. In the second version I am defaulting the value in the callback definition. It's really situation-dependent as to which approach to take. In this case, the second approach is not really appropriate.

      structMap()

      This is more of the same really. Here the callback receives the key and the value:

      original = {"one"={1="tahi"},"two"={2="rua"},"three"={3="toru"},"four"={4="wha"}};
      fixed = structMap(original, function(k,v){
      return v[v.keyList().first()];
      });
      writeDump([original,fixed]);

      fixed = original.map(function(k,v){
      return v.keyList().first();
      });
      writeDump([original,fixed]);

      array
      1
      struct
      four
      struct
      4wha
      one
      struct
      1tahi
      three
      struct
      3toru
      two
      struct
      2rua
      2
      struct
      fourwha
      onetahi
      threetoru
      tworua
      array
      1
      struct
      four
      struct
      4wha
      one
      struct
      1tahi
      three
      struct
      3toru
      two
      struct
      2rua
      2
      struct
      four4
      one1
      three3
      two2

      Here the value being passed into the callback is the substruct with the digit as a key and the Maori number for a value. I am using that information to map to a struct which has just the Maori version as the new struct's value (the structMap() example); and in the .map() example I'm just mapping to a struct with the digit as values. These are pretty contrived examples, but you hopefully get the idea.

      .reduce()

      The reduce() operation is slightly more complex. Basically it iterates over the collection and from each element of the collection, derives one single value as a result.

      listReduce()

      Here we perform a sum and a product on a list of digits:

      numbers = "1,2,3,4,5,6,7,8,9,10";

      sum = listReduce(
      numbers,
      function(previousValue, value){
      return previousValue + value;
      },
      0
      );
      writeOutput("The sum of the digits #numbers# is #sum#<br>");

      product = numbers.reduce(
      function(previousValue, value){
      return previousValue * value;
      },
      1
      );
      writeOutput("The product of the digits #numbers# is #product#<br>");

      Note that the callback receives two arguments: the previous value, and the current value. And also note the function can accept a starting value too. That said, it doesn't need to take a starting value, but if you don't give it one, you have to deal with not receiving a value for it in the first iteration. One can handle this like this:

      numbers = "1,2,3,4,5,6,7,8,9,10";

      sum = numbers.reduce(function(previousValue, value){
      if (!structKeyExists(arguments, "previousValue")){
      return value;
      }
      return previousValue + value;
      });
      writeOutput("The sum of the digits #numbers# is #sum#<br>");

      Or like this:

      product = numbers.reduce(function(previousValue=1, value){
      return previousValue * value;
      });
      writeOutput("The product of the digits #numbers# is #product#<br>");

      To be honest, given one can default the previousValue argument in the callback definition, I wonder if the listMap() function itself needs to have that initialValue argument? It seems like pointless duplication to me, perhaps? Hopefuly someone who has more experience with these functions (Adam Tuttle and Sean, I am looking at you two), and can advise where one or other approach might be better.

      Oh... and the output from all this (for the sake of completeness):

      The sum of the digits 1,2,3,4,5,6,7,8,9,10 is 55
      The product of the digits 1,2,3,4,5,6,7,8,9,10 is 3628800


      arrayReduce()

      This works the same way:

      rainbow = ["Whero","Karaka","Kowhai","Kakariki","Kikorangi","Tawatawa","Mawhero"];;

      ul = arrayReduce(
      rainbow,
      function(previousValue, value){
      return previousValue & "<li>#value#</li>";
      },
      "<ul>"
      ) & "</ul>";
      writeOutput(ul);

      ol = rainbow.reduce(
      function(previousValue, value){
      return previousValue & "<li>#value#</li>";
      },
      "<ol>"
      ) & "</ol>";
      writeOutput(ol);

      Actually this is probably a good demonstration of the subtle difference between using a starting value and defaulting the previous value. <ul> makes a sensible starting value, perhaps; but does not make sense as a default previous value.

      And the output this time is just some mark-up:

      • Whero
      • Karaka
      • Kowhai
      • Kakariki
      • Kikorangi
      • Tawatawa
      • Mawhero
      1. Whero
      2. Karaka
      3. Kowhai
      4. Kakariki
      5. Kikorangi
      6. Tawatawa
      7. Mawhero
      (and, yes, I know I could have just generated the <li> tags with the reduction, then slapped the <ul> and <ol> around them afterwards, but that's not the point ;-)

      structReduce()

      This example is very similar to the previous one:

      rainbow = {
      "Red"="Whero",
      "Orange"="Karaka",
      "Yellow"="Kowhai",
      "Green"="Kakariki",
      "Blue"="Kikorangi",
      "Indigo"="Tawatawa",
      "Pink"="Mawhero"
      };

      dl = structReduce(
      rainbow,
      function(previousValue, key, value){
      return previousValue & "<dt>#key#</dt><dd>#value#</dd>";
      },
      "<dl>"
      ) & "</dl>";
      writeOutput(dl);

      dl = rainbow.reduce(
      function(previousValue, key, value){
      return previousValue & "<dt>#value#</dt><dd>#key#</dd>";
      },
      "<dl>"
      ) & "</dl>";
      writeOutput(dl);

      Output:
      Blue
      Kikorangi
      Yellow
      Kowhai
      Green
      Kakariki
      Pink
      Mawhero
      Indigo
      Tawatawa
      Orange
      Karaka
      Red
      Whero
      Kikorangi
      Blue
      Kowhai
      Yellow
      Kakariki
      Green
      Mawhero
      Pink
      Tawatawa
      Indigo
      Karaka
      Orange
      Whero
      Red
      Here I just demonstrate how I'm using both the key and value from the struct.

      That's about it.

      Do you know what I am left wondering though... where are the equivalent methods for query objects? They're collections too, after all. I better get a ticket in to get those provided for too.

      Bed time for me. It's been a very bloody long ColdFusion 11 day for me. I'm scooting back to the UK tomorrow... 30-odd hours worth of aircraft and airports. Joy. At least once I get back I then have 1.5h on the train too. This will quite possibly mark the end of the torrent of ColdFusion 11 stuff from me. I've done my best to blog as much as I can whilst I've been off work, but after today I'll be indisposed, in Ireland, or back at work next Monday. So I'll probably drop back to around one article per day, tops.

      --
      Adam

      ColdFusion 11: cfhtmltopdf a non-starter. Literally

      $
      0
      0

      Update

      This sorted itself out after two de-/re-installs. None of which showed any errors, but only the last one installed the PDF stuff. Unimpressed.

      G'day:
      This might be me doing something wrong, or a known issue or something, but I can't get <cfhtmltopdf> to work. Which is annoying because for once in my CF career... I actually need to generate some PDFs. Today. Right now.

      I am about to get on a plane, upon which I will stay for the "best" part of 30h, excepting a coupla hours in Sydney this afternoon, and another hour in Dubai some time tomorrow (or is it today? Yesterday? TZs do my head in). So I decided I'd PDF-up the ColdFusion 11 docs so I can blog some stuff on the plane. And I decided to use <cfhtmltopdf> to do it.

      I'm just working through the docs as I need to (ie: when I get things wrong, I consult the docs to fix them), thinking that predictability of feature syntax is a worthwhile thing to test as well. So I concocted this code:

      param URL.htmlUrl;

      pdfDir = expandPath("./pdfs/");
      pageName = listLast(URL.htmlUrl, "/");
      pdfPath = pdfDir & pageName;
      cfhtmltopdf(source=URL.htmlUrl, destination=pdfPath, overwrite=true);

      (note: I have no idea if that is correct yet, and that's beside the point. But do not take it as valid example code of how to use cfhtmltopdf!)

      I ran that, and got this error:



      The following information is meant for the website developer for debugging purposes.
      Error Occurred While Processing Request

      Error while generating PDF with service manager (http://localhost:8987/PDFgServlet/).

      Reason: Error while handling request for service manager (http://localhost:8987/PDFgServlet/).

      OK. That's nice.

      I had a hunt around in some logs and found this:

      Application.log:
      "Error","http-apr-8500-exec-4","02/26/14","09:24:07","CFADMIN","Error enabling PDF Service Manager."
      "Error","http-apr-8500-exec-4","02/26/14","09:24:07","CFADMIN","coldfusion.document.webkit.PDFgErrorHandler$ConnectionVerificationFailedException: Connection verification failed."

      Coldfusion-out.log:
      Feb 26, 2014 09:24:07 AM Error [http-apr-8500-exec-4] - Connect to localhost:8987 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect http://localhost:8987/PDFgServlet/verify
      Feb 26, 2014 09:24:07 AM Error [http-apr-8500-exec-4] - Error enabling PDF Service Manager.
      Feb 26, 2014 09:24:07 AM Error [http-apr-8500-exec-4] - coldfusion.document.webkit.PDFgErrorHandler$ConnectionVerificationFailedException: Connection verification failed.

      server.log:
      "Information","localhost-startStop-1","02/26/14","09:21:01",,"ColdFusion: application services are now available"
      "Error","Thread-14","02/26/14","09:21:12",,"Connect to localhost:8987 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect http://localhost:8987/PDFgServlet/"
      "Information","http-apr-8500-exec-4","02/26/14","09:21:58",,"Initialize client.properties file"
      "Information","http-apr-8500-exec-2","02/26/14","09:22:07",,"Pool Manager Started"
      "Error","http-apr-8500-exec-7","02/26/14","09:23:03",,"Connect to localhost:8987 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect http://localhost:8987/PDFgServlet/verify"
      "Error","http-apr-8500-exec-4","02/26/14","09:24:07",,"Connect to localhost:8987 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect http://localhost:8987/PDFgServlet/verify"

      The only reference to port 8987 I can see in any config files is this lot:

      cfusion\jetty\etc\jetty.xml:
      <!-- Use this connector if NIO is not available. -->
      <!-- This connector is currently being used for Solr because the
      nio.SelectChannelConnector showed poor performance under WindowsXP
      from a single client with non-persistent connections (35s vs ~3min)
      to complete 10,000 requests)
      -->
      <Call name="addConnector">
      <Arg>
      <New class="org.mortbay.jetty.bio.SocketConnector">
      <Set name="Host">0.0.0.0</Set>
      <Set name="port"><SystemProperty name="jetty.port" default="8987"/></Set>
      <Set name="maxIdleTime">50000</Set>
      <Set name="lowResourceMaxIdleTime">1500</Set>
      </New>
      </Arg>
      </Call>

      cfusion\lib\neo-document.xml:
      <struct type="coldfusion.server.ConfigMap">
      <var name="localhost">
      <struct type="coldfusion.server.ConfigMap">
      <var name="hostname">
      <string>localhost</string>
      </var>
      <var name="ishttps">
      <boolean value="false" />
      </var>
      <var name="port">
      <number>8987.0</number>
      </var>

      cfusion\lib\neo-solr.xml:
      <struct type="coldfusion.server.ConfigMap">
      <var name="solrhome">
      <string>C:\apps\adobe\ColdFusion\11beta\full\cfusion\jetty\multicore</string>
      </var>
      <var name="solrwebapp">
      <string>solr</string>
      </var>
      <var name="solrhost">
      <string>localhost</string>
      </var>
      <var name="solrport">
      <number>8987.0</number>
      </var>

      None of that means anything to me, except I do rather wonder whether it's a good thing that it seems both Solr and the PDF engine are both trying to use that port..?

      In CFAdmin I have this:


      PDF Service Managers
      ActionsNameHost NamePortWeightHttps EnabledConnection Status
      EditVerifyEnableDisableDelete
      localhostlocalhost89872NOError  
      Connection verification failed for PDF Service Manager: localhost
      Check logs for more details.

      (that's after trying to start it).

      If I go into the EDIT option, I cannot make any changes. It's a form, but it won't let me type anything.

      Anyone know what's going on here?

      I verified that nothing else is trying to use port 8987.

      I tried googling, but drew a blank.

      This is, btw, a completely fresh install that I did this morning. According to the installlation log, everything succeeded. I think perhaps it did not.

      --
      Adam

      ColdFusion-UI-the-Right-Way:

      $
      0
      0
      G'day:
      I've just finished another "chapter" on ColdFusion UI the Right Way, this time a quick treatment of <cfajaxproxy>. Once again, I'll repeat it here for the sake of a) promoting CFUItRW; b) adding content to the blog / Google ;-)

      cfajaxproxy

      The <cfajaxproxy> tag provides the ability to create a JavaScript proxy "class" which can be used to create proxy objects which in turn proxy for a ColdFusion CFC back on the ColdFusion server. This means JavaScript code can call methods on the JavaScript object, which will - in effect - execute functionality on the ColdFusion server.

      Here is a very basic demonstration of using <cfajaxproxy> to create a JavaScript proxy object to fetch data from the ColdFusion server:

      Listing 1 : cfajaxproxy.cfm

      <!doctype html>
      <html lang="en">
      <head>
      <meta charset="utf-8">
      <title>&lt;cfajaxproxy&gt; demo</title>
      <script src="http://code.jquery.com/jquery-1.11.0.js"></script>
      <script src="lib/js/renderData.js"></script>
      <cfajaxproxy cfc="DAO" jsclassname="DAOProxy">
      </head>
      <body>
      <div id="data-goes-here"></div>
      <script src="cfajaxproxy.js"></script>
      </body>
      </html>

      Note that even with the <cfajaxproxy> tag, one still needs to use JavaScript to use the proxy:

      Listing 2 : cfajaxproxy.js


      $(document).ready(function(){
      var daoProxy = new DAOProxy();
      var data = daoProxy.getData();
      renderData($("#data-goes-here"), data);
      });

      I am using jQuery here as a matter of convenience; it's not related to the functionality of <cfajaxproxy>.

      With the data returned from DAO.cfc, and rendered with renderData.js (see below for the code for each of these files), this outputs:



      Now here is the same functionality, totally written in simple HTML and JavaScript (using jQuery for AJAX proxying functionality).

      Listing 3 : jquery.html


      <!doctype html>
      <html lang="en">
      <head>
      <meta charset="utf-8">
      <title>jQuery demo</title>
      <script src="http://code.jquery.com/jquery-1.11.0.js"></script>
      <script src="lib/js/renderData.js"></script>
      </head>
      <body>
      <div id="data-goes-here"></div>
      <script src="jquery.js"></script>
      </body>
      </html>

      This listing is virtually identical to the mark-up used in cfajaxproxy.cfm, above, except for not having the call to <cfajaxproxy>.

      Listing 4 : jquery.js


      $(document).ready(function(){
      $.ajax(
      "DAO.cfc?method=getData",
      {
      success : function(json){
      data = JSON.parse(json);
      renderData($("#data-goes-here"), data);
      }
      }
      );
      });

      The JavaScript here uses jQuery to handle an AJAX call back to DAO.cfc the ColdFusion server. When the AJAX call returns the data, it's converted back from JSON to a JavaScript object, then rendered using the same code as the previous example.

      This demonstrates that all <cfajaxproxy> is really doing is making an AJAX call, and deserialising the returned JSON. Which is very easy to do with jQuery and native JavaScript.

      For completeness, here is the code for DAO.cfc and renderData.js, which are used in both examples:

      Listing 5 : DAO.cfc


      component {

      remote struct function getData() returnformat="json" {
      return {
      "labels" = ["Mon","Tue","Wed","Thu","Fri"],
      "records" = [
      {
      "text" ="Apples",
      "values"=[1,2,4,8,4]
      },{
      "text" ="Bananas",
      "values"=[10,8,6,4,6]
      },{
      "text" ="Cherries",
      "values"=[1,3,9,3,1]
      }
      ]
      };
      }

      }

      Listing 6 : renderData.js


      renderData = function(element, data){
      data.labels.forEach(function(day,i){
      element.append("<h2>" + day + "</h2>")
      .append(
      (function(){
      return "<ul>"
      + data.records.reduce(
      function(previousValue,currentValue){
      return previousValue + "<li>" + currentValue.text + ": " + currentValue.values[i] + "</li>";
      },
      ""
      )
      + "</ul>"
      ;
      })()
      );
      ;
      });
      };

      Note that I've factored renderData.js out into a different file purely to simplify the example code. All it does is render the data as per the screen shot above. Also note that I'd generally try to use a templating library here to separate the mark-up and the JavaScript a bit more clearly, but that's not in the scope of this chapter, which is just focusing on the AJAX proxying.

      Resources

      That was it. Pretty simple really. <cfajaxproxy> works fine, but it's really just not necessary in CFML.

      --
      Adam

      ColdFusion 11: Adobe listening to their clients

      $
      0
      0
      G'day (from London again now):
      Earlier in the week I wrote an article "ColdFusion 11: lists and arrays and empty elements and weird ways to fix stuff", which commented on some poor logic / common sense behind the way Adobe had chosen to fix an issue around how the application setting this.sameFormFieldsAsArray works, in that the array it creates ignores empty form values.

      Adobe had claimed "backwards compatibility" on this at the time, but it seems common sense has prevailed: realising that sometimes "being correct" is better than preserving incorrect behaviour for the sake of "backwards compatibility".

      Today Himavanth Rachamsetty has added this comment to the ticket:

      • Himavanth Rachamsetty
        11:42:59 PM GMT+00:00 Feb 27, 2014
        Some background:
        The problem with form scope not retaining the empty elements (when there are multiple elements with same name) was actually fixed once in CF6.
        But it was reverted due to lot of public feedback
        and bugs being logged. 
        (Bugs # 33697, # 34340. These bugs are not available in the public bugtracker now. They were logged in the previous bug tracker.)
        This and the fact that people were always using getPageContext() if they needed the empty elements are the reasons we didn't want to fix this.

        But due to public feedback now, we have decided to sacrifice consistency for the sake of ease-of-use.
        If sameFormFieldsAsArray is set to true, the array in form scope will preserve empty elements.
        If it is false, the list will not preserve empty elements (for backward compatibility).

      It seems we were our own worst enemies back in CFMX 6 days (I was on the Prerelease Programme back then, but cannot remember this issue being discussed... I didn't participate in it much), ensuring we were stuck with bung behaviour with the form field list, but Himavanth has seen there's no real backwards compatibility issue to answer to with the newer array option. I don't believe it's a consistency due over ease of use issue though, TBH: it's more a case of not expanding incorrect behaviour when there's no reason to. The list and array cannot cohabitate (as it's an application-wide setting), so there's nothing to be consistent with.

      I'd love to see the reasoning the community had with those two old bugtracker issues...

      Anyway, good to see Adobe listening to their community / clients!

      --
      Adam

      ColdFusion 11: undocumented new feature regarding

      $
      0
      0
      G'day:
      I can't see this documented anywhere, other than as a passing comment in the bug tracker, so I figured I'd mention it here.

      Last week I raised a ticket "<cfprocessingdirective> not implemented in CFScript", which observed that the generic CFScript equivalent of <cfprocessingdirective> (which should be cfprocessingdirective()) had not been implemented.

      It has indeed not been implemented, but not due to any oversight, but because of this:

      • Awdhesh Kumar
        5:28:51 AM GMT+00:00 Feb 26, 2014
        cfprocessingdirective tag is a special tag and is handled differently because of which it is could not be supported as the generic script syntax of the tag. This tag is used to specify the template encoding. In Splendor, we are auto detecting the page encoding and hence most of the time it will not be required to set processingdirective.
      That's actually fairly significant news!

      So - to be clear - we no longer need to explicitly tell the ColdFusion compiler what the character encoding of a source code file is... it'll work it out for itself. Like how other text-processing applications such as notepad.exe have been able to do for years.

      So... a bit late to the party, but ColdFusion eventually got there.

      I've given this a superficial test, and it seems to work, too. Cool.

      And that's all.

      --
      Adam

      ColdFusion 11: is pretty good!

      $
      0
      0
      G'day:
      I had a bit of a false start with getting cfhtmltopdf() working (see article "ColdFusion 11: cfhtmltopdf() a non-starter. Literally"), but that turned out to be an installer problem, rather than anything wrong with the functionality itself. Once I got it going, I gotta say I'm pretty impressed.

      I simply knocked together this code, and ran it with some common websites to see what the output was like:

      param URL.htmlUrl;

      pdfDir = expandPath("./pdfs/");
      pageName = listLast(URL.htmlUrl, "/");
      pdfPath = pdfDir & pageName & ".pdf";
      try {
      startTime = getTickCount();
      cfhtmltopdf(source=URL.htmlUrl, destination=pdfPath, overwrite=true);
      totalTime =getTickCount() - startTime;
      writeOutput("Execution time: #totalTime#ms<br>");
      }catch (any e){
      writeDump(e);
      }

      The table below details the sites I used, and links through to the generated PDFs (and, for reasons best known to myself, a thumbnail of the generated PDF: I just wanted to mess with <cfpdf>, basically).

      SitePDF
      This blog
      BBC News
      The Guardian
      Pravda
      Al Jazeera
      Asahi
      Xin Hua

      If you can't be arsed looking at the sites, the bottom line is this: the English-language sites all rendered really well. Not perfectly, but definitely good enough to be used in a professional capacity, I think. The Russian-language site (Pravda) and Japanese (Asahi) also generated fine.

      However Chines (Xin Hua) and Arabic (Al Jazeera) sites both generated the structure OK, but the text was lost. This is a bit of a mishit, and should have been spotted by Adobe I think. We live in an increasingly small and polylingual world, and not dealing with - especially - Chinese is poor form.

      I also had a concern about generation times. Here's how long each PDF took to generate:

      SiteDuration
      cfmlblog40sec
      BBC22sec
      Guardian52sec
      Pravda37sec
      Al Jazeera55sec
      Asahi91sec
      Xinhua168sec

      I'm not concerned about the times themselves; although 3min to generate a PDF does seem like a long time... especially as we can only write off about 10sec of that for load time... I'm concerned about the fact that two of those are higher than my request timeout period. It seems that the request timeout doesn't apply to this process? That's not great. I know that the way ColdFusion handles request timeouts is such that some processes seem immune to it. This doesn't make it correct to behave that way. If I specify a request time out, that should be the timeout for a request. Fullstop. It should not be a timeout "in most situations, but not if you use these tags". That's dumb. I think Adobe need to revisit how this is handled.

      Anyhow, I did not exactly push the envelope as far as this functionality goes... I'm not meaning to test the entire thing, attribute by attribute, I just wanted to see how good the PDFs were.

      Whilst the PDFs were good, there are two things about this functionality which are very subpar:

      1. It's enterprise only. Come on Adobe, give us a break. It's bad enough we've gotta pay for CFML without having to pay an Enterprise price ticket to even get the functionality. Enterprise should be about scale, not about hamstringing. You need to provide this functionality to all CF licences, and perhaps throttle it in some way. But not in a way that makes it unusable.
      2. It's... I don't believe I need to say this in regards to any web technology... it's Windows-only. Seriously: fuck the hell right off with that (and I say this as someone who only uses Windows!). What were you thinking? Windows only? In a web environment? You guys are hung-up in the desktop market most of Adobe's functionality is based around. Plus you can't - in the same breath - go "Enterprise-only" and then go on to say "Windows-only". Are you on particularly impure crack?

      Bottom line: Adobe need to sort out Chinese and Arabic (and any other significant language(s) it's missed support for), and maybe look at how long the rendering takes. But overall I think this functionality is good. However they seriously need a kick up the arse for the Enterprise-only and Windows-only thing. That's just stupid. Stupid and out of touch with reality.

      Righto.

      --
      Adam

      ColdFusion 11: Adobe have finally started deprecating / obsoleting stuff in ColdFusion

      $
      0
      0
      G'day:
      This article is basically gonna be a reproduction of something in the ColdFusion 11 docs, just FYI. I found an xls file buried in the docs that lists some stuff that's newly deprecated in CF11, and even some stuff they've finally obsoleted.

      In the docs for ColdFusion 11, there's a page "Deprecated Features", which includes a link to an xlsx file. WhyTF they did it as an Excel file and not just a page in the docs is anyone's guess. I suspect it's lack of brain engagement, but I can't prove it.

      Anyway, for the sake of discussion and general FYI, here is the contents of the file, as HTML:

      Feature NameDeprecated sinceColdFusion 11
      Flash FormsCF11Deprecated
      XML FormsCF11Deprecated
      CFAPPLETCF11Deprecated
      CFSPRYDATASETCF11Deprecated
      LCDS IntegrationCF11Deprecated
      CORBA IntegrationCF11Deprecated
      AIR IntegrationCF11Deprecated
      CFTREE and CFGRID format = "applet"CF11Deprecated
      CFPresentation for flash presentationCF11Deprecated
      CFDocument for flashpaperCF11Deprecated
      ColdFusion actionscript functions (CF.query, CF.http)CF11Deprecated
      cfcache- cachedirectory, timeout attributesColdFusion MXRemoved
      cfcollection - map and repair options of the action attributeColdFusion MX 7Removed
      cferror - monitor option of the exception attributeColdFusion MXRemoved
      cffile - system value for attributes attribute, temporary value for attributes attributeColdFusion MXRemoved
      cfform - passthrough attribute, enableCAB attributeColdFusion MX 7Removed
      cfftp - agentname attributeColdFusion MXRemoved
      cfgraphColdFusion MXRemoved
      cfgraphdataColdFusion MXRemoved
      cfgridupdate - connectString, dbName, dbServer, dbType, provider,providerDSN attributesColdFusion MXRemoved
      cfinput - passthrough attributeColdFusion MX 7Removed
      cfinsert - connectString, dbName, dbServer, dbType, provider,providerDSN attributesColdFusion MXRemoved
      cfldap - filterFile attributeColdFusion MXRemoved
      cflog - date, thread, time attributesColdFusion MXRemoved
      cfquery - connectString, dbName, dbServer, provider, providerDSN,sql attributes. The following dbType attribute values: dynamic, ODBC, Oracle73, Oracle80, Sybase11, OLEDB, DB2ColdFusion MXRemoved
      cfsearch - external, language attributesColdFusion MXRemoved
      cfselect - passthrough attributeColdFusion MX 7Removed
      cfservletColdFusion MXRemoved
      cfservletparamColdFusion MXRemoved
      cfslider - img, imgStyle, grooveColor, refreshLabel,tickmarkimages, tickmarklabels, tickmarkmajor,tickmarkminor attributesColdFusion MXRemoved
      cfstoredproc - connectString, dbName, dbServer, dbtype, provider,providerDSN attributesColdFusion MXRemoved
      cftextinputColdFusion MX 7Removed
      cfupdate - connectString, dbName, dbServer, dbtype, provider,providerDSN attributesColdFusion MXRemoved

      There's a definite retreat from Flash there, isn't there? Good, I say.

      Apparently not a single function has been deprecated or obsoletely though. Which sounds a bit fishy. I also wish they'd just deprecated everything to do with the UI and be done with it (and then remove in CF12, if we ever get there).

      Is there anything else you'd suggest deprecating?

      Do you know what though? I'm very pleased that parameterExists() is still alive and well.

      ;-)

      --
      Adam

      Your ColdFusion bugs - visualised

      $
      0
      0
      G'day:
      I'm stealing some of Ray's code here, and making it more general and exposing it for all to use.


      A few days back (I only noticed it today) Ray posted an article "My ColdFusion Bugs - visualized". In this he has pulled the ColdFusion bugs he's raised with Adobe, and charted their current status (eg: "Fixed", "Closed", "Can't be arsed fixing", "It's meant to be broken", etc). It's mildly interesting... interesting enough for me to pinch the idea and refine it slightly.  Here's the chart for my own bugs:


      In Ray's version he queried the data once, and then just charts those results. In my version I've done two things:
      1. It polls live data every request;
      2. One can specify anyone's "name" to get the bugs for.
      The display above is just being pulled in from my CFMLDeveloper-homed ColdFusion instance... you can browse to it directly via http://adamcameroncoldfusion.cfmldeveloper.com/cfbugs/mine/bugchart.html?name=[your name here], eg: http://adamcameroncoldfusion.cfmldeveloper.com/cfbugs/mine/bugchart.html?name=adam cameron.

      I'm pleased to see Ray (formerly "ColdFusion Jedi") hasn't used <cfchart> for this. Because there's simply no point. HighChartsJS look pretty bloody cool to me. And there's no need to pester the CFML server for UI stuff. Just do it on the UI.

      Anyway, that's it. I'm back to blogging before work & during lunchtime, so it's good to keep these things short.

      --
      Adam

      Of integers and indolence

      $
      0
      0
      G'day:

      Yes, OK, I'm still banging on about integers and ColdFusion bugs. It's my blog and I can talk about whatever I like! ;-)

      You might be aware that I am particularly nonplussed about Adobe's reaction to bug 3712010, which relates to integer validation via isValid(), and how it is broken.

      I started by just being mildly annoyed by how ColdFusion can make such a pig's ear of something that should be simply. But the more Adobe (or, hey, let's say it: Rupesh from Adobe) squirms about the place and tries to justify why they shouldn't just bloody fix a fairly obvious bug, the more annoyed I am getting about this. I dislike having my intelligence insulted, and I also dislike the collective intelligence of the ColdFusion community insulted. Especially by someone who very clearly should not be in the business of insulting anyone's intelligence.

      It's all getting a bit stupid, but one of the comments posted today on this caught my eye and made my blood boil just that bit more. I've asked Carl if I can reproduce his comment, and he has said it's fine. So here it is. I've dollied it up for formatting, but it's othewise unchanged.

      Ray Camden reported this issue back in CF7 before July 31, 2005 (https://bugbase.adobe.com/index.cfm?event=bug&id=3024044) and it was deferred with "not enough time". Since this issue was migrated from the old bugtracker, no votes are listed.

      Ray followed up with a suggested flag or workaround before August 3, 2005 (https://bugbase.adobe.com/index.cfm?event=bug&id=3024112) and it was deferred with "not enough time". Despite this bug being migrated from the old bugtracker, it got two votes.

      ErnestoRA reported a similar issue in CF8 on September 1, 2010 (https://bugbase.adobe.com/index.cfm?event=bug&id=3042116) and it was closed with "NeverFix".

      MisterDai reported similar issues with IsValid in CF8 on September 20, 2010 (https://bugbase.adobe.com/index.cfm?event=bug&id=3042202) and it was deferred with "not enough time". This issue got one vote.

      MaryJo reported similar issues with IsValid in CF9 on April 18, 2012 (https://bugbase.adobe.com/index.cfm?event=bug&id=3169196) and it was closed as "Withdrawn/AsDesigned". This issue had 15 votes and 4 comments from end users.

      Now MaryJo has once again reported this against a BETA version of CF11, and it is again being deferred? Are you kidding? With 10 votes and 14 comments from end users telling you to fix it?

      So after more than eight years of begging, six different bug reports against four (really five if you implicitly include CF10) different releases, more than a dozen unique end users weighing in with comments, and at least 28 votes to fix this issue (with recognition from those commenting/voting that dealing with any backward compatibility issues would be preferable to maintaining the status quo on broken functionality), you still want to push this off to yet another future release? After continually deferring it since 2005?

      This MUST get fixed during the current BETA cycle. Waiting until CF12 (maybe) to get this resolved is ABSOLUTELY UNACCEPTABLE (especially to those of us who have been paying for upgrades to the product over the years).
      So it was basically reported as soon as the function came out (CFMX7 was released in Feb 2007; Ray first raised this no later than July that year). For you, Rupesh, to say "It has been like this forever" (Rupesh Kumar 4:36:34 AM GMT+00:00 Mar 3, 2014)... you're taking the piss. We know that, sunshine. We told you at the time.


      Let's not lose sight of the fact that we're not asking for a function to cure world hunger or to give India an effective bowling attack. We're asking for a function to correctly identify whether ColdFusion considers a value to be usable as an integer to work so that it's a) usable; b) consistent with the rest of ColdFusion. How is this so hard? How does it take EIGHT FUCKING YEARS TO (still not ~) FIX?


      --
      Adam

      ColdFusion 11: Thank-you Carl, Mary-Jo, many other community members and indeed Rupesh

      $
      0
      0
      G'day:
      Integers. We "won". Adobe have reversed their position, and they're gonna fix the isValid("integer") bug:

      • Rupesh Kumar
        9:18:35 PM GMT+00:00 Mar 4, 2014
        Wow - it is indeed incredible. Thanks Carl for digging this out. We will fix it - we would have a application setting which would bring back the old behavior in case some one needs it. I hope nobody needs to use this flag. 

        We will also roll it out in an update for the public beta and we would need your help in verifying and making sure that applications don't break. 

        Thank you everyone for raising this and providing this feedback!

      Good stuff, Rupesh.

      --
      Adam

      CFML is dying. Let's drop it off at Dignitas

      $
      0
      0
      G'day:
      Yes. It's another "CFML is dying" article. We haven't had one for a while. Note that this is not "ColdFusion is dying", this is CFML in general.

      TBH: the headline is slightly tongue-in-cheek, and designed to garner outraged click-throughs whilst the Daily Mail readers amongst us search for the "Comment on this article" button so they can display their mouth-froth to everyone.

      This article actually isn't about CFML. And that's the point.

      I had a meeting with some fellas from the CFML community a coupla months back, and during that meeting an idea was tabled which we'd all independently been thinking about prior to sitting down. I thought this was cool because it added validation to a few ideas I'd had... finding out some of my peers also had the same thoughts. My mind has been stewing over this since, so I've asked if it's OK if I share the idea in this article, so as to get the conversation in the community going. I hasten to add that the content of this article is all my own thoughts on various things we discussed, however I cannot necessarily claim that all the ideas were originally my own.

      So here goes... I think it's time for Railo to put CFML into maintenance mode.

      In the 1990s and probably early 2000s, CFML (under the guidance of Allaire, Macromedia and Adobe) was a technical leader in its field. The field of dynamic tag-based language designed to facilitate the automation of HTML page generation. And I mean the archetypal CFM "template" notion, where a web site is basically made out of a bunch of HTML files with CFM file extensions, with mark-up being generated dynamically with CFML tags.

      Since then the notion of web sites and web applications has changed, grown and become far more important and complex pieces of software. And along with the increasing complexity, CFML has grown in breadth and complexity to facilitate the ever-changing requirements. However, TBH, I don't think the language has really kept apace with where other languages are going. It dwelt too long in the "tags make it easy" mantra, which has always been a load of bullshit anyhow, and was also too focused on adding bells and whistles rather than building a strong language. CFMX 6.x's foray into OO was half-arsed, and almost all features since then have been what ought to have been add-on bundles, not core to the language. Macromedia and Adobe were just going for the shiny shiny to attract the cheque books of the easily-impressed goggle-eyed IT Managers. And as a result, CFML is a bit of a mess.

      Railo came along and is doing a good job of trying to drag CFML into this decade, and I am impressed with their approach and their work. I used to be very sceptical of them, basically thinking they were simply delivering a cheap knock-off of ColdFusion: RyanAir compared to British Airways, if you like. However over the last year or so I have realised they're actually far better at their jobs than the Adobe crew are (for whatever reason. I'm looking at the end result, not how they get there and what constraints are placed upon people), and indeed the future of CFML lies with Railo, not Adobe.

      But.

      I think this is not a good use of Railo's time any more. CFML is dead. CFML is a pariah amongst its peers because of all this emphasis on tags for everything, which results in what looks like playschool code. Because CFML has this reputation of a pretend language for web monkeys, it's never gonna get any decent uptake from new developers, it's never going to be taken seriously again, and it will wither and die off. Well basically as a career-prospect, it will just be "maintenance mode". Adobe killed CFML by focusing on tag-based code, and the CFML developers in the community have killed it by perpetuating this approach to coding.

      So here's my / our idea... retire CFML.

      CFML has a lot of really good functionality. The ease of which one can hit a DB and get a recordset back is fantastic. Making a call to a directory server? Easy. Hell, even reading files is an easy pleasure. What it does is great. How it goes about it is rubbish.

      Another great asset Railo has (and Adobe do too, but I don't care about ColdFusion for the purposes of this article) is that CFML is a dynamic JVM language. This is the current cool (well: one of the current cools... some new supposed coolness comes out every day, it seems). Everyone's decided Java itself is a bit of a behemoth, but the way the JVM works is pretty cool. And obviously now we have a lot of languages out there which - dynamically - compile down to Java byte code.

      Right. So the functionality of CFML is great, and the platform is great. The language is not. Ditch the language.

      I think Railo should create their own JVM-based language, reusing a lot of their existing work for CFML, but making a good language. One that seems modern, and that people might want to use.

      Here's some ideas of what they could do...

      Files

      I propose three different file types: .rclass and .rscript and .rview (all the labels I'm giving stuff is just proof-of-concept and because I need to give labels to things to discuss them: I made up those file extensions just now as I needed to type something). .rclass would be the equivalent of a CFC. But let's just call 'em classes, OK? That's what they are. .rscript would be the equivalent of a CFM file now one might use where a CFC isn't appropriate (that said, this requirement might be moot). And an .rview file is what it suggests: a view.

      Views

      The significant thing here is that an .rview is the only file that will compile tag-based code. Other than that, the language will be entirely script-based. I'll get to that.

      Tags will be treated as a DSL rather than really part of the language itself: basically tags for adding flow-control logic to the view. Any actual data-processing logic will be in the .rclass or .rscript files.

      Tags


      Tags will be written in code as name-spaced custom tags, eg:

      <rv:loop index="i" from="1" to="10>
      <!--- repeated content here --->
      </rv:loop>

      <rv:if myVar EQ "someValue">
      <!--- conditional code --->
      <rv:elseif otherCondition>
      <!--- other conditional code --->
      <rv:else>
      <!--- fallback code --->
      </rv:if>

      Note: there is no attempt to make that XML-compliant as that notion is a thing of the past and was always a dumb idea anyhow.

      Now... CFML taggers will recoil in horror at this one. Almost all tags will be dropped from the DSL. Here's an assessment of the fate of CFML tags:



      CFML tagValid
      cfabortYes
      cfswitch / cfcase / cfdefaultcaseYes
      cfdumpYes
      cfflushYes
      cfif / cfelse / cfelseifYes
      cfimportYes
      cfincludeYes
      cfloop / cfbreak / cfcontinueYes
      cfscriptYes
      cfset / cfparamYes
      cftry / cfthrow / cfcatch / cfrethrow / cffinallyMaybe
      cfexitMaybe
      cfimageMaybe
      cftimer / cftraceMaybe
      cfwebsocketMaybe
      cfajaximport / cfajaxproxy / cfapplet / cfcalendar / cfchart / cfchartdata / cfchartseries / cfclient / cfclientsettings / cfcol / cfdiv / cfform / cfformgroup / cfformitem / cffileupload / cfgrid / cfgridcolumn / cfgridrow / cfgridupdate / cfinput / cflayout / cflayoutarea / cfmap / cfmapitem / cfmediaplayer / cfmenu / cfmenuitem / cfmessagebox / cfpod / cfpresentation / cfpresentationslide / cfpresenter / cfreport / cfreportparam / cfselect / cfslider / cfsprydataset / cftable / cftextarea / cftooltip / cftree / cftreeitem / cfwindow / cfhtmlhead / cflocation / cfprogressbarNo
      cfapplication / cfargument / cfcomponent / cffunction / cfinterface / cfinvoke / cfinvokeargument / cfassociate / cferror / cfobject / cfoutput / cflock / cfprocessingdirective / cfproperty / cfreturn / cfsetting / cfsilent / cfthread / cflog / cfwddx / cfxmlNo
      cfcache / cfobjectcache / cfsavecontentNo
      cfcollection / cfindex / cfsearchNo
      cfcontent / cfcookie / cfheaderNo
      cfdbinfo / cfinsert / cfprocparam / cfprocresult / cfquery / cfqueryparam / cfstoredproc / cftransaction / cfupdateNo
      cfdirectory / cfexecute / cffile / cfprint / cfregistry / cfsharepoint / cfspreadsheet / cfschedule / cfzip / cfzipparamNo
      cfdocument / cfdocumentitem / cfdocumentsection / cfhtmltopdf / cfhtmltopdfitem / cfpdf / cfpdfform / cfpdfformparam / cfpdfparam / cfpdfsubformNo
      cfexchangecalendar / cfexchangeconnection / cfexchangecontact / cfexchangeconversation / cfexchangefilter / cfexchangefolder / cfexchangemail / cfexchangetask / cfimap / cfimapfilter / cfmail / cfmailparam / cfmailpart / cfpop / cffeed / cfftp / cfhttp / cfhttpparam / cfldapNo
      cflogin / cfloginuser / cflogout / cfNTauthenticate / cfoauthNo

      (some of those groupings are a bit arbitrary, I know).

      So basically we keep the flow-control and debugging tags, but axe all the rest. They are all inappropriate for a view.

      There are some tags worth mentioning separately:

      <cfquery>

      Sorry... it doesn't belong in a view. Gone. I don't care that it's "nice" to have one's SQL within tags; there are better ways of doing it.

      <cfoutput>

      This will be implicit in views, so not needed.

      <cftry> etc

      I suspect it is not appropriate to have exception handling in a view: the controller should be handling this? But I've put it as "maybe".

      Now I hasten to add that I am not suggesting any of that functionality is ditched from the language (OK, some of it should be... all the UI stuff for example); I'm just saying it won't be implemented as tags. Instead of <cfexecute> one will use execute() or something like that. But it doesn't belong in a view, so it won't be represented as a tag.

      Language

      OK, so it's all script. But there'll be changes here too.

      Functions

      Built-in functions will also go out the window. All of them. Everything will be a method on a class. Some methods will be static, eg:

      myResult = Query.execute(sql)

      Query here is the class, not an object. myResult will be an object.

      There might be scope for UDFs as functions though, or perhaps they are added as methods on a scope or something. I haven't thought about that.

      Queries

      These'll go too. The result set from a DB call will be an array of structs.

      <cfquery> is gone

      Queries will be executed much as they are now in Railo / ColdFusion 11 with queryExecute(). I would say that the sql argument can either be a string of SQL (as per now), or a reference to a .rscript (or maybe an .rview to facilitate using tags for flow control, but the file extension would need a better name if so) file within which one can have one's SQL / HQL. Having inline SQL in a CFM file as it currently is implemented is convenient, but I think it's actually a pretty grim practice. Just as one ought to minimise how much inline JS and CSS one has in one's files (and markup in a CFC etc), I really feel the same way about SQL.

      Arrays

      It pains me to say it ("Why do ColdFusion arrays start at 1 instead of 0?"), but arrays will be zero-based. It's dumb that that is how modern languages do it, but it's a point of contention and best to follow the herd with this one.

      Data-types in general

      Adobe position CFML as "typeless" which is a bullshit idea and has lead to all sorts of stupidity in ColdFusion. For example this:

      <cfset aNumber = 1>

      Results in aNumber being set as a string. I have no problem with the idea that a numeric value can be automatically converted to a string if it needs to be, but it should be initially set as being numeric, and stay that way until such time as it gets instructed to be otherwise.

      The language ought to be considered "loosely-typed". Railo handles this much better: in the example above, Railo will correctly set aNumber to be numeric.

      Because CFML data needs to interact with strongly-typed languages, I'd like to have more control over this. I'd like to be able to do this:

      <cfset Double aNumber = 1>

      Where this will set aNumber to be a Double (as opposed to an Integer, which might be the best fit from the look of the original value). This will not set aNumber's type in stone, if an operation is done to it which would make it a string or an integer, all good. But at least we can control it.

      Null

      It will be implemented properly, and with full support.

      Booleans

      "Yes" and "No" will play no part in boolean values.

      Literals

      Literals (eg: "G'day world", {key=value} etc) should be objects. So this should be possible:

      shouty = "G'day World".ucase()

      Semi-colons

      Not needed.

      Classes

      Should be able to extend (or be extended by) other JVM-language classes.

      Custom Tags

      Custom tags have been neglected in CFML I think, however they should be the absolute bread and butter of implementing dynamic views. There could be an argument that view shouldn't even have flow control tags; this should all be done with custom tags. I don't know much about the CFC-based implementation of custom tags in Railo, but it sounds like how it should most likely be done. The important thing here is that custom tags ought to return to the forefront of the language.

      Closure-functions / callbacks / etc

      (And by that I mean stuff like each() / map() / reduce() etc). These will be a heavy focus in the language.And function expressions, IIFEs and all that magic will be promoted. If there's any other functional-programming-esque notions the language can leverage (so being a functional / OO hybrid), it'd be good to hear about them. Sean... am looking at you when I say this.

      Async programming

      A while ago Luis suggested adding async capability to "slow" operations like file process, DB processing and the like. He details it in this thread on the Railo Google Group: "Add closure support to blocking operations to allow them to be non-blocking operations". My position on this in the context of CFML was that it was too focused and a broader / more general approach would be better: "Threads, callbacks, closure and a pub". I stand by that for CFML, but if we're creating a new language, perhaps async optionality could be added to functionality as matter of course. For example a Query.execute() call would run by default synchronously, but if success / error handlers are passed in, then it'll be run asynchronously instead.

      Event-driven programming

      I love event-driven programing in JavaScript: listening for events, firing custom events, basically the Observer Pattern strikes a chord with me. I write about this in "Native event framework for ColdFusion 11 ?" I like the idea of a process completing and going "hey, anyone who's interested... I've done some work and here's the result", and any other code which has registered a listener will have their event handler fire, with the result being passed to it. Nice.

      ORM

      ORM will be promoted as being the preferred way of DB interaction, and the implementation will be revamped to have as little ceremony, but as much stability as possible.


      REST

      A good implementation of REST. I am not suggesting Railo's implementation of REST is not good, but my own exposure to REST in CFML is in ColdFusion, and it's a bit of a ball-ache to develop with.

      Server

      I don't want to get into too much detail as to the server implementation as it's not something I have expertise in, but there's a coupla significant elements to consider.

      MVC

      Some manner of convention-based MVC framework should be embedded in the server (not the language), to encourage decent programming practices.

      Package Manager

      Like NPM or Ruby Gems etc. And as someone else has pointed out... the current package admin thing in Railo is nothing like the requirement here.

      Extending the language

      One should be able to extend the language by dropping new classes into a directory structure which when simply exposes them as native functionality to the developer.

      Equally one should be able to override built-in functionality (eg: to fix a bug in isValid(), one can drop in a replacement of isValid() somewhere, and that replacement will be used.

      CLI / REPL

      The server should offer a built-in CLI / REPL capability.

      That's about it at the moment. What do you think?

      --
      Adam

      @cfmlNotifier now sending updates from the CFML Bloggers RSS feed

      Clarification and some notes on yesterday's "CFML is dying. Let's drop it off at Dignitas" article

      $
      0
      0
      G'day:
      I added a coupla things to the "CFML is dying. Let's drop it off at Dignitas" article this morning, after some discussions and more thoughts over night. I'll just summarise them here.


      <cfquery> is gone

      Queries will be executed much as they are now in Railo / ColdFusion 11 with queryExecute(). I would say that the sql argument can either be a string of SQL [...]

      Perhaps instead of representing SQL statements as strings, something like JOOQ could be used? I'm not used it, but it came up in search results when seeing what existing languages did to better represent SQL statements than just in strings.

      Arrays

      It pains me to say it ("Why do ColdFusion arrays start at 1 instead of 0?"), but arrays will be zero-based. It's dumb that that is how modern languages do it, but it's a point of contention and best to follow the herd with this one.

      That said: see the comments against the article, starting with Sean's one, and continuing with my follow-up, and his to mine. The question is... should the actual numeric value of the index be important? Most array looping would be done with an iteration method (like .each()), and generally one just needs to leverage the ordering of an array, not necessarily the numeric value that implements the ordering between one array element and the next. I think perhaps this desire to know the array index comes from a history of using indexed for loops to loop of the index values of the array to then access the elements. That's quite old school. Food for thought.

      Closure-functions / callbacks / etc

      (And by that I mean stuff like each() / map() / reduce() etc). These will be a heavy focus in the language. And function expressions, IIFEs and all that magic will be promoted. If there's any other functional-programming-esque notions the language can leverage (so being a functional / OO hybrid), it'd be good to hear about them.

      Another kinda similar which I might get away with describing under the "functional programming" banner is the concept of generators, which are functions which run to a point (when they encounter a yield statement), then return to the calling code. The next time the function is called it doesn't start afresh, it resumes at the next statement after the yield. This can be leveraged to some quite interesting ends. I thought I had written an article in which I mention these as a suggestion for CFML, but I cannot find it at the mo'.

      I've actually raised a ticket for this with Adobe (3555025) and now Railo (RAILO-2942). There's some discussion on this blog article - "Working with infinite sequences in JavaScript" - by Tim Taubert.

      I also had an interesting conversation on Twitter with Mark Mandel this morning, which I will reproduce here.

      (DE = David Epler; AC = me, MM = Mark Mandel. Both Dave and Mark are people I deeply respect in my sphere of "tech people I know", so they're probably more worth paying attention to than me!)

      DE
      why create another language that runs on JVM? There are already several other good alts.

      AC
      Same reasons there's more than one JVM lang now: each have diff. strengths. This is more shaking off CFML's cruft & do a rebrand

      MM
      I have to ask though - what makes CFML so amazing that is worth investing all that effort into?

      AC
      A lot of what I suggest would be reasonable features for CFML as it stands. Plus some tidy-up.

      MM
      Again, what amazing thing does this give us? what's the win here? Your article needs a WHY not just a WHAT.

      AC
      The "why" is I think Railo will ultimately struggle getting penetration with CFML as it stands. Which I did say. I'm not positioning this as "here's a new language for Mark and David to play with", I'm positioning it as an evolution of "CFML". On the other hand you seem to be suggesting CFML shouldn't be allowed to evolve and progress?

      MM
      No I'm saying that if you can't say why this is somehow better than current offerings, what's the point? Or another angle - what makes you stick with cfml, other than "Moving is too hard because X", and is that a good enough reason?

      AC
      FFS. I'm not suggesting Railo "invents a new language"for my benefit. This is not about me. A concept that seems to be an unendearingly foreign concept to people around here.

      MM
      But your spec is essentially a new lang, so wouldn't it be essentially the same as porting an application to a new lang anyway, no?

      AC
      Well I think the simplicity of how CFML does a bunch of stuff is nice (YMMV), but the outward "skin" of the language is poor and pretty much everything I suggested there was an ehancement of existing functionality (closure shit etc), or stuff I've already raised (or meant to raise) as features for CFML itself. They all seem "good fits". The problem (IMO) CFML has, and has killed its cred, is some of the crufty tag-based & procedural-based approach it has. So basically suggesting a "reskinning". Parsing <rv:if> instead of <cfif> won't be much work, I think. The changes I'm suggesting are mostly retiring stuff. And then augmenting some other stuff.

      MM
      unless I misunderstood, your spec dropped/altered a lot of core functionality. So wouldn't you have to migrate existing apps?

      AC
      This is not like biological evolution. We're not going from Cfml Erectus to Cfml Sapiens. We'd still HAVE Cfml Erectus.

      MM
      Or is this more for existing cfml shops that want more modern features at a much lower learning curve than a whole new lang?

      AC
      Pretty much that. I don't want it to sound like CFFORM for Node.js (etc), but I looked at a bunch of excellent features that JS, Groovy, Ruby etc have, &tried to look @ a "how would this fit into CFML". But @ same time redefining CFML also could be leveraged off the existing Railo code base fairly easily, I think. So building on where Railo is strong, but unhamstringing it from some of the shit they need to do to be CFML-compliant.

      MM
      See that gives us a Why do this :) and a Why to debate. I couldn't find it before.

      AC
      I did think I had said most of that in the article. Funny (haha) that it seems I can articulate it better in 140char chunks :-| maybe to interpret it that way one needed to be reading the article from the perspective of being inside my head?Happens sometimes

      MM
      To recap: For those unable to move, a lower learning curve to modern features for new apps, & a shorter migration for existing.

      AC
      Pretty much.

      MM
      In which case, you're feeling it would be easier to convince upper management that taking the time to change would we worth it?

      AC
      Well if Railo still served CfmlOld as well, shops could simply start to migrate across as they see fit. And greenfields work could just use the new "language".
      I dunno if that helps people see more where I am coming from. One thing I didn't seem to articulate is that when I said Railo should put CFML into maintenance mode, I meant exactly that. I did not mean remove it. So it'd still serve CFML if one had CFM / CFC files, but if one wanted to use the new dialect (and let's look at it as a dialect, not necessarily a new language), then it's there. So at one's whim, one can ease into it. Or dive in.

      I don't really see this as a new language. I see it as a reskinning of the existing language. I happen to really like a lot of what CFML does, I just think it could approach its functionality in a tidier, more contemporary fashion. That's all I'm suggesting here.

      I'm also meaning it as a start of a discussion, not the end of a pronouncement, which some people seem to have taken it as. There's some really good comments against yesterday's article: a mix of "for" and "against", too. All good stuff. Make sure to go read it all if you're interested in this topic.

      Keep the comments/thoughts/suggestions coming!

      --
      Adam

      Railo: small new feature I didn't catch making it into the language

      $
      0
      0
      G'day:
      This is a minor but handy feature in Railo 4.2 that Micha just gave me a heads-up on. One can treat a string as an array:

      s = "G'day world";
      for (i=1; i <= s.length(); i++){
      writeOutput(s[i]);
      }
      writeOutput("<hr>");

      charAfterApostrophe = s[find("'", s)+1];
      writeOutput("charAfterApostrophe: #charAfterApostrophe#");

      Output:

      G'day world

      charAfterApostrophe: d


      Not very exciting, but makes sense, and saves a call to mid() if needing to extract characters from the string.

      --
      Adam

      Java Training

      $
      0
      0
      G'day:
      My mate Mike Henke has passed on some good information, and said he wouldn't mind if I circulated to everyone else too.

      I saw your recent post about CFML dying. Today I hit up webucator for a deal on their online java class for experienced programmers. They offered it for $1000 but I need 2 others to sign up too.

      It starts April 28th 10am-5pm eastern time for 5 days..

      http://www.webucator.com/java/course/java-programming-training-for-experienced-programmers.cfm

      If you want to mention this in a blog post or tweet that would be awesome for CFML-ers looking to learn something else and if they are like me, needing a set, short class to ramp up fast.

      Mike makes a good point that a rapid skill injection like this could be great for people who need help / opportunity to move on from CFML. I'm not necessarily be suggesting a move to Java itself would be a good idea, but there's an awful lot of good techniques to be learned from Java that a CFMLer might not necessarily acquire day to day.

      Even if not moving from CFML, it's just a great opportunity to get some intense exposure to another language.

      And $1000 is a bloody excellent price.

      As per Mike's comment below: he can be contacted on  henke dot mike at gmail dot com.

      --
      Adam

      Unit testing: the importance of testing over assumption

      $
      0
      0
      G'day:
      Today some code! That's better than rhetoric innit? I'm sure it won't be as popular though.

      OK, so I have been too busy recently with ColdFusion 11 and various other odds 'n' sods to either continue writing about unit testing, and also I have been neglecting the CFLib.orgsubmission queue. So today I am remedying both of those in one fell swoop.

      This article is not really "TDD" or "BDD" as the code I am testing is a fait accompli, so the tests are not driving the development. However this is perhaps a good lesson in how to retrofit tests into functionality, and what sort of things to test for. There's a bit of TDD I suppose, because I end up completely refactoring the function, but first create a test bed so that that is a safe thing to do without regressions.

      On CFLib there is already a function firstDayOfWeek(), but someone has just submitted a tweak to it so that it defaults to using today's date if no date is passed into it. Fair enough. Here's the updated code:

      function firstDayOfWeek() {
      var dow = "";
      var dowMod = "";
      var dowMult = "";
      var firstDayOfWeek = "";
      if(arrayLen(arguments) is 0) arguments.date = now();
      date = trim(arguments.date);
      dow = dayOfWeek(date);
      dowMod = decrementValue(dow);
      dowMult = dowMod * -1;
      firstDayOfWeek = dateAdd("d", dowMult, date);

      return firstDayOfWeek;
      }

      Nothing earthshattering there, and I basically scanned down it going, "yep... uh-huh... oh, OK, yeah... cool... yeah that seems OK". And I was about to just approve it. Then I felt guilty and thought "ah... I really should test this. And I'll use TestBox and its BDD-style syntax to do so, and I can get a blog article out of this too.

      It's just as well I did test it, because it has a bug. Can you see it?

      I sat down and looked at the code, and went "right... what do I need to test?". I came up with this lot:
      • if I don't pass in a date, it returns the first day of the current week;
      • if I do pass in a date, it returns the first day of that week;
      • if I pass in the date that is the first day of the week already, it passes back that same date;
      • if I pass it "not a date", it errors predictably.
      I think that about covers the testing need here? OK, so I started writing the tests:

      // TestFirstDayOfWeek.cfc
      component extends="testbox.system.testing.BaseSpec" {

      function beforeAll(){
      include "udfs/firstDayOfWeek.cfm";

      variables.testDate = now();
      variables.testFirstDayOfWeek = dateAdd("d", -(dayOfWeek(variables.testDate)-1), variables.testDate);
      }

      function run(){
      describe("Tests for firstDayOfWeek()", function(){

      it("returns most recent sunday for default date", function(){
      expect(
      dateCompare(variables.testFirstDayOfWeek, firstDayOfWeek(), "d")
      ).toBe(0);
      });

      it("returns most recent sunday for current date", function(){
      expect(
      dateCompare(variables.testFirstDayOfWeek, firstDayOfWeek(variables.testDate), "d")
      ).toBe(0);
      });
      });
      }

      }

      Notes:
      • I've got the UDF saved in a separate file, so just include it at the beginning of the tests;
      • I set some helper variables for using in the tests;
      • I'll get to this.
      I was running the tests as I went, and here's what I got on the test round with these first two tests:


      [furrowed brow]

      Ah. OK, it's this:

      if(arrayLen(arguments) is 0) arguments.date = now();

      There's a logic error in that. It tests the length of the argument array, and if there's no arguments passed, it defaults arguments.date. However my code calls the function thus:

      firstDayOfWeek(variables.testDate)

      So I am passing an argument (so the arrayLen() is 1), and so arguments.date does not get defaulted. But the argument I passed isn'tdate. It's just arguments[1]. So then the code that then assumes there's an arguments.date then fails.

      If the function definition had specified the date argument (which it should have), then this problem would not exist. Or if the logic was more sound it also wouldn't be an issue. If the code requirement is that arguments.date needs to exist (otherwise default it), then test for that, eg:

      if(!structKeyExists(arguments, "date")) arguments.date = now();

      So I had some fixing to do. But first I rounded out the tests:

      // TestFirstDayOfWeek.cfc
      component extends="testbox.system.testing.BaseSpec" {

      function beforeAll(){
      include "udfs/firstDayOfWeek.cfm";

      variables.testDate = now();
      variables.testFirstDayOfWeek = dateAdd("d", -(dayOfWeek(variables.testDate)-1), variables.testDate);
      }

      function run(){
      describe("Tests for firstDayOfWeek()", function(){

      var isRailo = function(){
      return structKeyExists(server, "railo");
      };

      it("returns most recent sunday for default date", function(){
      expect(
      dateCompare(variables.testFirstDayOfWeek, firstDayOfWeek(), "d")
      ).toBe(0);
      });

      it("returns most recent sunday for current date as positional argument", function(){
      expect(
      dateCompare(variables.testFirstDayOfWeek, firstDayOfWeek(variables.testDate), "d")
      ).toBe(0);
      });

      it("returns most recent sunday for current date as named argument", function(){
      expect(
      dateCompare(variables.testFirstDayOfWeek, firstDayOfWeek(date=variables.testDate), "d")
      ).toBe(0);
      });

      it("returns same date if passed-in date is already first day of week", function(){
      expect(
      dateCompare(variables.testFirstDayOfWeek, firstDayOfWeek(date=variables.testFirstDayOfWeek), "d")
      ).toBe(0);
      });

      it(
      title = "gives typeful error if non-date is passed (CF)",
      body = function(){
      expect(function(){
      firstDayOfWeek(date="NOT_A_DATE");
      }).toThrow(regex="The DATE argument passed to the firstDayOfWeek function is not of type date");
      },
      labels = "",
      skip = isRailo()
      );

      it(
      title = "gives typeful error if non-date is passed (Railo)",
      body = function(){
      expect(function(){
      firstDayOfWeek(date="NOT_A_DATE");
      }).toThrow(regex="first Argument \(date\) is of invalid type, can't cast String \[not_a_date] to a value of type \[date]");
      },
      labels = "",
      skip = !isRailo()
      );
      });
      }

      }

      Most of the TestBox constructs I deal with here I've already covered in an earlier article, but I've just linked through to the docs on the first example of each.

      The only new construct I am using here is skip. On a call to it(), one can specify a skip argument which - if true - will not run the test. TBH I don't like how this has been implemented, as I think the logic has been reversed from what is intuitive. It would be more intuitive if the condition predicated whether to run the test (defaulting to true), rather than to skip the test. This is more clear in intent, I think:

      it("gives typeful error if non-date is passed (CF)", function, "", isColdFusion());

      One needs to use a negative-sounding test to make it work: specifying that it is appropriate to run is more natural than specifying that it's not appropriate to run, or it is inappropriate (if you see the subtle difference here). I also think it really breaks the otherwise excellent readability / intuitiveness of this syntax in general. Still: it's a minor thing.

      In this case I am testing for the exception message when one doesn't pass a date to the function. Unfortunately ColdFusion and Railo throw both different exception types and they have different messages too. Also their types are very vague ("expression" on ColdFusion, and I can't remember what on Railo), so I don't really think testing just for an "expression" exception - which could be just about bloody anything - is accurate enough here. So I'm testing that the message is the one one would get if passing an incorrectly typed argument value to a function. Which is what I want to test for here.

      The results on ColdFusion are as follows:


      We expect that second test to fail due to the bug I mentioned above. And I also kinda set the function up a bit on that last test, as I knew it would fail. It has another logic error, in that it requires the date argument to be a date, but it never validates it to be one, instead just letting the internal code fail. This is pretty sloppy in my opinion, so I'm gonna fix it (it's easy: just specify the argument in the function definition).

      There's a coupla slight tweaks to the function before I can approve it:
      1. fix the deal with the positional arguments thing;
      2. also ensure the passed-in argument value is actually a date.
      The fix for this is the same: simply specify the argument in the function definition.

      Then something else occurred to me. There's an awful lot of code there to do something very simple. Contrast the function code:

      function firstDayOfWeek() {
      var dow = "";
      var dowMod = "";
      var dowMult = "";
      var firstDayOfWeek = "";
      if(arrayLen(arguments) is 0) arguments.date = now();
      date = trim(arguments.date);
      dow = dayOfWeek(date);
      dowMod = decrementValue(dow);
      dowMult = dowMod * -1;
      firstDayOfWeek = dateAdd("d", dowMult, date);

      return firstDayOfWeek;
      }}

      With my code to create my expected test result value:

      variables.testFirstDayOfWeek = dateAdd("d", -(dayOfWeek(variables.testDate)-1), variables.testDate);

      That does the same thing. So... erm... let's just do that. I fix the bugs and use the simplified version of the logic, and end up with this:

      date function firstDayOfWeek(date date=now()){
      return dateAdd("d", -(dayOfWeek(date)-1), date);
      }

      That's a helluva lot clearer as to what is going on here, I think.

      And, bless, here are the test results now:


      That's enough effort for a one-line UDF I think.

      But here's the lesson... don't just eyeball yer code and go "looks OK". You could well be wrong. So test it.

      --
      Adam

      Is Adobe knowingly leaving exploits in ColdFusion 9 and 10 unpatched?

      $
      0
      0
      G'day:
      I am very very much leveraging Betteridge's Law of headlines in my article headline above. The answer is - almost certainly - no. However it's perhaps a question that needs to be asked, if some conclusions I'm reading on Twitter today are to be taken seriously.

      I'm trying to get some clarification on this security situation in which is "dealt with" by this addition of the allowedextforinclude setting in neo-runtime.xml (discussed in this article: "ColdFusion 11: preventing files from being included? WTF, Adobe?"). There's a ticket in the bugbase "Either remove allowedextforinclude functionality entirely, or at least implement it so it can be disabled". The agreed-to fix here is as follows:

      • S V Pavankumar
        5:46:57 AM GMT+00:00 Feb 27, 2014
        Added the new change. 

        The new behavior will be
        1) By default only cfm files gets compiled in cfinclude tag and all other file types gets included statically. 
        2) The key allowedextforinclude now can be specified at the application as well. 
        3) Added the setting to Server settings -> settings page 
        4) If wildcard(*) is specified in the value all files will be compiled. Wildcard support is added at both server and application level.

      OK, so it seems it's completely OK to disable this thing.

      But my question remains... what issue are they address here? All they've said is "security". Outwardly this seems very far-fetched, so - for Pete's sake - we need to know what the risk is. If there's a very good reason that most people aren't seeing, we need to know what it its. On the other hand Adobe's conclusions could simply be bullshit, or the approach here could be wrong.

      Equally... how can we make an informed decision as to what do to with this setting without any information as to what the story is? Are we OK disabling it? Are we opening ourselves up for disaster? What's the story?!

      Adobe have been slow to clarify, so I pinged Rakshith etc on Twitter the other day to follow-up, and I got this tangential response from Aaron Foote:


      Well... I looked up the exploits that have been reported in the last coupla years (listed on the ColdFusion security bulletins page). There's a bunch of irrelevant stuff there, and the only stand-out one to me is the one that did the rounds about a year ago in which one can circumvent the ColdFusion Administrator password and get into CFAdmin, then use a scheduled task to upload malicious code onto the CF server. This is a very bad exploit (and is non-theoretical: I have heard of a bunch of people being exploited by this).

      But this has nothing to do with including non-CFML files, so addressing this by pissing about with how <cfinclude> works is a pointless waste of time.

      The fix for this would be twofold:
      1. don't allow the file that saves task output to be set to be a CFM file (which Adobe has done; now one can only write them to .log or .txt files);
      2. don't allow them to be written to simply anywhere that the ColdFusion service has file-system access to. IE: Don't let the scheduled task write data to a web-browseable directory which then can be requested and executed by the exploiter.
      That's the fix for this. Adobe have only done half of it.

      Now... I guess someone at Adobe has possibly found a way that a hacker has found a way to still <cfinclude> these files? I'm buggered if I can work out how. To <cfinclude> the exploited file, one needs to be able to write and execute one's own code on the CF server. And if they can do that... they don't need to mess about writing malicious code to .txt or .log files then including it... they can simply just write the code they want in the first place! IE: if they can write this:

      <!--- fileWrittenByHacker.cfm --->
      <cfinclude template="/some/dodgy/file/written/by/a/malicious/task.txt">

      Then they can write this instead:

      <!--- fileWrittenByHacker.cfm --->
      <!---- here's the dodgy code right here! --->

      So we're not mitigating anything in this scenario by blocking which files we can include.

      Incidentally... with the setting enabled I was still able to upload an execute malicious code on my ColdFusion 11 server, even though I could only save it as a .txt file. And all from within CFAdmin. It took me about 2min to spot the vector, and it worked first time. Now I hasten to add I started this experiment from the point that I already had access to CFAdmin... I assume Adobe have completely patched that security exploit in CF11. But when the next similar exploit security-bypass exploit presents itself, this approach to uploading code to the server via a task and then executing it (despite it having a .txt or .log file extension) is still alive and well.

      But here's the thing. The fix to the ticket I quoted from above includes the capability to manage the setting from within CFAdmin. So if the basis for the exploit presupposes access to CFAdmin... the exploiter can just disable this measure anyhow! So what's the bloody point?

      So if this is all to do with this exploit, as Aaron suggests it might be, I'm buggered if I see how it helps.

      Now I get to the title of the article. Aaron followed up his earlier Twitter message with this one:

      OK, so let's pretend there is a very real-world exploit that Adobe are dealing with here.

      By addressing this in ColdFusion 11... every single production server on the planet is not patched for this. Because no-one is running CF11 yet. We're all running CF9 or CF10 (or older, but earlier versions are out of support, so can't expect patches anyhow). If it's a significant security thing, then where are the patches for ColdFusion 9 and ColdFusion 10?

      So, sorry Aaron, Adobe haven't fixed anything here. Not if there's an actual issue, and not if we ought to be concerned about it. ColdFusion 11 is months away yet. And anyone with any sense wouldn't touch it until it's had the first coupla bugfix / stability patches released for it anyhow, which - following ColdFusion 10's track record - won't be for four months after it's released anyhow. So like Sept 2014 (assuming CF11 gets launched in May, which seems likely).

      The headline I guess becomes relevant now: is Adobe knowingly leaving exploits in ColdFusion 9 and 10 unpatched?

      If Aaron is correct: yes they are.

      --
      Adam

      Shamelessful plug

      $
      0
      0
      G'day:
      I'm not quite sure what I think about this one, and not sure how to word things. But one thing I do know how to articulate: Gavin's a nice bloke.

      Why's Gavin a nice bloke? Well if you've met him, he just is. But I'm making a point of it here because last night (my time) he released this blog post: "Charity Corner - Diabetes and Fly a Foul Mouthed Fusioner Fund". First and foremost Gav should be lauded for putting his time into helping an actual worthy cause, in helping out his local diabetes charity. But he's also decided to promote a particular unworthy cause: me.

      I'll let you read the article, but the gist of the me-centric bit is that I voiced somewhere on social media last week (it was probably Twitter) that I would love to go to CF.Objective() this May, but my financial logistics are such that it just can't be done. Ray Camden immediately said I could room-share with him to chip away at my costs a bit, which is fantastic, but I still can't really justify to myself the rest of the expense (it's over a grand - GBP - just for the flights!).

      Gavin seems to have been mulling over this a bit, and yesterday gave me a nudge regarding what he was intending to blog-post over night. He's doing a bit of a "charity-drive" to try to get me crowd-sponsored to assist with my costs of attending the conference. I don't articulate myself at all well when I'm chuffed at the thought of someone going out of their way to help me, so I'll just say "blimey mate, thanks for thinking that way".

      Anyway, nothing ventured; nothing gained, I guess. So I figured I'd mention it here too. I do feel slightly guilty about doing so though (not so guilty I decided not to, obviously!)

      All I can offer in return is blog-coverage of the thing if I get there, plus buying beer for people once I'm there. Also I will unashamedly say "I can be bought", so if you were to help me get to the gig, I'd be more than happy to direct my blogging attention in a direction of your choosing. Heh: Adobe... here's your chance to get me to say something positive about <cfclient> ;-)

      Anyway, there you go. Hat in hand, pan-handle at the ready.

      And thanks again Gavin (and Ray!). You guys are dudes.

      --
      Adam

      ColdFusion 11: Confusion as to why is not simply an adjunct to

      $
      0
      0
      G'day:
      I had a look at <cfhtmltopdf> a week or so ago in "ColdFusion 11: <cfhtmltopdf> is pretty good!". And as I say in the article title: it works really well (other than a coupla bugs I raised). However one thing I can't work out is why we now have two tags that do the same thing: <cfdocument> and <cfhtmltopdf>.

      They might - under the hood - use different mechanisms to arrive at the same goal, but surely the whole thing about having a tab abstraction layer is that the underlying mechanism ought to be irrelevant: the tags - and indeed the CFML language as a whole - is supposed to abstract the minutiae away from the developer.

      A coupla things that are different about <cfhtmltopdf> are:
      1. it only works on Windows;
      2. it is enterprise only.
      Both of these are a bit shitty (or a lot shitty, really), but they do not - in my mind - justify why <cfhtmltopdf> is a separate tag.

      So, anyway, I asked on Twitter for some sort of explanation:

      And this morning I got a response from Rakshith (well I am presuming he's still the man behind the curtain of the @ColdFusion account?)

      My initial reaction here is in response to this bit "Much easier for CFer to comprehend". And my reaction is "you patronising fuck" (this was close to being the title of this article, but I thought better of it).

      Not only is Rakshith being patronising the basis for his tone seems fairly facile to me.

      Let's accept that the purpose of <cfdocument> and <cfhtmltopdf> is the same: generating a PDF from mark-up. And let's also accept that the reason they're doing this work is that <cfdocument> wasn't that good at what it set out to do. It can be used to create moderately straight forward PDFs, but as soon as the document structure or design got more than superficially complex, it was no good. Fair enough: it was the first iteration of the functionality (although it perhaps should not have taken close to ten years for the second iteration of it to present itself, but hey).

      Let's also accept that there's some differences in approach (the two points I raise above) too.

      This can all still be achieved with a single tag, surely? We already have a precedent set for this with <cfchart>, which not only has two separate engines, but one of them is server-side and one of them is client-side!

      So with <cfdocument>, how come it cannot work like this:
      • if the new engine is disabled, then processing falls back to the original mechanism;
      • if the licence is a standard one, it falls back to the original mechanism;
      • if the box is not a Windows one... it falls back.... etc;
      • if an "engine" attribute is set to "classic" or "pants" or something, it also falls back;
      • otherwise it uses the new engine.
      This also negates a curious "feature"<cfhtmltopdf> has... the underlying engine can be disabled. How is this actually a feature? And... how is this a good thing? What happens if the engine is off and someone tries to use <cfhtmltopdf>? I presume it just errors? Would it not be better to have it fall back to the original mechanism?

      And the same applies to the observation Rakshith made that the new engine can be installed as a separate service. So what? Why does that mean it needs to be a new tag? It doesn't.

      Another thing Rakshith has posted that I just noticed whilst writing this is:

      Right. So there's two observations to make here:
      1. So that's a step backwards, innit? I suppose you've got some work to do in that case, so as to facilitate this.
      2. [shrug]. One can't do everything with the old <cfchart> engine that one can with the new one, and vice-versa.
      Neither of these are good rationalisations as to why this is not an augmentation (or a backwards step, in the case of the first one) of <cfdocument>.

      All adding yet another PDF-generating tag to the language is achieving is demonstrating a lack of thought / planning on the part of Adobe, and cluttering up an already cluttered language.

      I think this needs a bit of a rethink before go-live.

      --
      Adam
      Viewing all 1332 articles
      Browse latest View live