CFClient: The Agony & The Ecstasy -- Making It Purty

CFClient: The Agony & The Ecstasy -- Making It Purty

Posted by Brad Wood
Jan 09, 2015 10:32:00 UTC

In my last entry, I discussed my decision to  create a "CFClient Sampler" app that would simultaneously allow me to play with each mobile client API, all the while providing the community with some nature of blog-based documentary on my attempt.  With a solid proof of concept under my belt (and on GitHub) I pushed forward with two goals in mind this time:

  1. Pick another API to play with
  2. Figure out some organization for the code before it got out of hand
  3. Ok, I guess there was a third goal too:  Make it not so ugly.

I'll start with the last one, which was to make the app not look like a middle schooler banging something out with Microsoft FrontPage.  Quite frankly, I suck at UI stuff.  I'm a "function over form" guy and I'm quite happy architecting the back end of an application far far away from the perils of CSS, responsive layouts, and viewports.   For this I used my phone-a-friend and dialed up jQuery Mobile.  JQM has been around for a while and it doesn't make web pages that very unique (kind of like the BootStrap cookie cutter sites) but it's stupid simple to setup and covers every major navigation, button, control, and layout concern I'll be detailing with.  

jQuery Mobile

For anyone not familiar with JQM, it's a library that sits atop regular jQuery and whips your boring markup into shape with some basic extra attributes.  It comes with a JS and CSS file.  Feeling particularly lazy, I just copied and pasted the same one Ram used for the Expense Tracker app.

<link rel="stylesheet" href="css/jquery.mobile-1.4.2.min.css" ></link>
<script src="js/jquery-2.1.0.min.js" ></script>
<script src="js/jquery.mobile-1.4.2.min.js" ></script>

For a one-page app, you wrap each page and page-element in some boilerplate markup and JQM will know what to do with it.  Every "page" gets this wrapped around it

<div data-role="page" id="uniqueID">
Page Content...
</div>

Nested inside each each "page" can be a header:..

<div data-role="header">
My Header
</div>		

...content...

<div data-role="content">	
  My Content
</div>			

...and footer.

<div data-role="footer">
  My Footer 
</div>

Buttons and tiles are generally created with unordered lists or anchors.  Here's the raw elements:

And this is what we get once the JQM files are included:

I can live with that.  There's even theming support built in via ThemeRoller with an out-of-the-box "light" and "dark" theme to choose from.   JQM used to have a bunch of different themes it shipped with, but the later versions just have these two.

Features

JQM has a ton of documentation blogs and Stack Overflow answers on the Internet.  You can check it out for more info, but I'll make a quick list here of the specific features I found useful or at least had to Google for a bit to get working correctly.

  • By default, the footer "floats" up at the bottom of the content.  You can make it stick to the bottom of the screen by adding data-position="fixed" to each footer div.
  • I was able to easily add "Back" buttons to every page (except Home) by just adding data-add-back-btn="true" to each header div.  Note, in older versions of JQM this had to be added to the page div.
  • You can easily add nice little icons like the gear on my "Settings" button with the data-icon="gear" attribute on the button tag.
  • Adding icons outside of buttons is a bit of a pain.  The gear I added to the header of the settings page required a span container and some custom styling to make it behave.
  • Navigation within the JQM "pages" just requires an anchor tag whose href points to "#pageID"
  • The link on my name in the footer that open by blog was a bit trickier since PhoneGap wanted to open it in the embedded WebKit browser by default.  
    navigator.app.loadUrl( 'http://www.codersrevolution.com', { openExternal:true });
    Update: Adam Tuttle pointed out this won't work on Android. new code is:
    window.open( 'http://www.codersrevolution.com', '_system' );
    Note, this requires you to add the "InAppBrowser" feature to PhoneGap to work!
  • Buttons default to being the width of the screen.  Add data-inline="true" to shrink them down to size and be inline.
  • Switching to the "dark" theme above is as simple as adding data-theme="b" to each page div.
  •  The navigation controls on the page are just an unordered list with data-role="listview" added.

Event API

The API I tackled today was the Event API which is pretty shallow, but that was good since I wasted a lot of time on other stuff.  It basically allows for a number of callbacks to listen to events such as

First things first, you must add the Event API to the list of "features" in PhoneGap.  Right click on your project, then properties.  Click the PhoneGap tab, then "New..." then "Feature", then tick the "Events" checkbox.   I have screenshots of this on my last entry so check those out to see what I'm describing.

I was going to make the "back" button navigate backwards, but it appears JQM or PhoneGap already handles that on my Android phone.  Instead, I wired up the menu button instead to navigate to my "Settings" page.

// Android user presses "menu" button
function onEventMenu() {
	$("body").pagecontainer("change", "##settings" );
}
	
cfclient.events.onMenuButton( 'onEventMenu' );

I also added a little Alert popup when your battery gets low.  You'll never see this unless your battery hits the low threshold while the app is open.  I re-used the same warning for those batter low levels.

// Fires on battery low warning 
function onBatteryLow() {
	alert( "Hey, plug your device in!" );
}

cfclient.events.onBatteryLow( 'onBatteryLow' );
cfclient.events.onBatteryCritical( 'onBatteryLow' );

Most of the rest of the events I couldn't test without an emulator since my phone does have a pause, resume, or search button.

Update: Adam Tuttle pointed out "pause" and "resume" are not media control buttons but related to when the app switches from background to foreground.  I have now wired these events up and on my Android my app is paused when I lock the phone and resumes when I unlock it.

// On Android, fires when I lock my phone
cfclient.events.onPause( function() {
	console.log( 'paused' );
} );

// On Android, fires when I unlock my phone
cfclient.events.onResume( function() {
	console.log( 'resumed' );
} );

Code Organization

Once I got the JQM mobile thing squared away I was pretty excited to figure out some organization to my code-- if for nothing else than to remove some of the really redundant code such as identical headers and footers.  Up until this point, pretty much all my code has been in my index.cfm file roughly like this:

<html>
  A bunch of markup here
</html>

<cfclientsettings enabledeviceapi="true">
<cfclient>
   Use the client APIs and bind it to the markup above
</cfclient>

My first thought was to break up an include for the standard header, footer, and each JQM "page".  I could even get fancy and put a .cfm for each "page" by convention, query them out, loop over, and include them all with the header and footer.  That way it would be easy to add a new page and I wouldn't need to copy and paste any of the boilerplate around.  I'm not a big custom tag user but without the usual tools I'm used to in the ColdBox MVC Platform for code reuse, I decided to use them so I could  easily pass attributes in to say, make an optional bit in the header, etc.

  • I created a header.cfm that I could easily call as <cf_header>  It had an optional icon attribute so the settings page could have <cf_header icon="gear"> 
  • I created a footer.cfm that I could easily call as <cf_footer>
  • I created a page.cfm that included both the header and footer and cfincluded the actual page content based on an id you passed in.  <cf_page id="home">

I was pretty pleased with this.  All redundancy was gone, new pages only needed JUST the markup that represented the contents of the page and they would be wrapped in all the boilerplate necessary.  The only markup left in index.cfm was the outermost HTML stuff and this:

<cf_page id="home" backButton="false">
<cf_page id="connection">
<cf_page id="notification">
<cf_page id="setting" headerIcon="gear">

Next I would find a way to even make that list dynamic based on the files in my "pages" directory.  I had been testing this in Chrome as I went, just hitting the local ColdFusion server directly and it was working swimmingly.  

Organization Fail

My swimming code sank as soon as I tried to compile the app to give it a spin on my actual phone.  Note, I didn't expect any of this dynamic custom tag stuff to execute on the phone.  It was there to keep my source organized and it only needed to run once when the app was compiled to generate a single page full of all the markup.  

error : C:\Users\Brad\Documents\GitHub\CFClient-Sampler\index.cfm,index.cfm could not be compiled, check C:\Users\Brad\Documents\GitHub\CFClient-Sampler\index.cfm for errors or code outside cfclient block.

Not very specific... I finally began wildly commenting out sections of code just to get the darn thing to compile.  (I used to debug Internet Explorer 4 like this).  I finally figured out, you apparently can't have any CFML code outside of <cfclient>.  

This is a valid CFClient app:

<html />

<cfclient></cfclient>

This is NOT

<cfoutput>
  <html />
</cfoutput>

<cfclient></cfclient>

I don't know why it ran without issue when I hit my local server directory, but the same code refuses to compile.  Looking at the stack trace, CF Builder doesn't just hit my wife site over HTTP to get the HTML from it.  It compiles it via some RDS endpoint.  This was a major setback indeed.  I re-read all the docs on cfclient and I couldn't find anything about this. The limitations on what CFML one can use inside of CFClient are pretty substantial and there are a lot of limitations on what will work.  However, THIS CFML was outside the CFClient block and only there to help me assemble the markup for my one-page app.

Trying Again

Not to be one easily dissuaded, I tried a second approach which followed the same organization as above, but moved all the custom tag calls inside the CFClient blocks.  I was a little scared to do this because HTML output from within CFClient is appended to the DOM in unpredictable orders according to the highly asynchronous stuff going on.  The docs warned against doing this at all, but said to try adding <cfflush> after any markup you output to keep it in the correct order.  

This proved to be quite hard to debug since I couldn't simply view source any longer to see the output of my code.  The HTML I output was escaped and included in a huge block of minified JavaScript in the page. I had to work through a bunch of weird errors such as this Null Pointer Exception when including an empty .cfm file:

java.lang.NullPointerException
    at coldfusion.compiler.ParseException.add_escapes(ParseException.java:297)
    at coldfusion.compiler.ParseException.getTokenText(ParseException.java:112)
    at coldfusion.rds.PGBuildServlet$JSBuildOperator.groupFilesAndErrorDescriptions(PGBuildServlet.java:478)
    at coldfusion.rds.PGBuildServlet$JSBuildOperator.processCmd(PGBuildServlet.java:360)
    at coldfusion.rds.PGBuildServlet.processCmd(PGBuildServlet.java:110)
    at coldfusion.rds.RdsServlet.doPost(RdsServlet.java:114)
    ...

eventually had to add <cfclient> blocks around ALL my .cfm files including header.cfm, footer.cfm, etc.  I guess you can't include a non cfclient file into cfclient, but there's not actually a check and error message for that.  At first it looked like this approach was going to work.  I was getting all the includes going and HTML was being appended to the DOM but then I got to the part of page.cfm that uses the passed in ID to cfinclude the actual page inside of its JQM boilerplate.

<cfinclude template="#ID#.cfm" >

Nope.

error : C:\Users\Brad\Documents\GitHub\CFClient-Sampler\index.cfm,Invalid CFML construct found on line 106 at column 2.Expression at line 108 has to be constant value. error on line: 106 column: 2

The important part is "Expression ... has to be constant value. "  Sure  enough, clearly documented in the Wiki is stated:

In the <cfinclude> tag, dynamic template name(##) is not supported

<cfclient>
      <cfset x=“abc.cfm”>
      <cfinclude template=“#x#”>
</cfclient>

Defeat

Well, criminy, they've thought of everything!  This basically defeated the whole point of what I was trying to do in order to not duplicate any code.  I could include files, but nothing dynamic.  Once again CFClient was there to snatch defeat from the jaws of victory.  It was quite late at this point so defeated, I reverted all my code to just have one giant block of static HTML in my index.cfm.  I'll have to ponder this some more, but unless some of these restrictions get lifted I can't imagine how I would come up with any good design pattern for breaking up my markup for a large one-page app.  

One idea I had involved assembling the files outside of ColdFusion as part of an Ant build or even a custom CommandBox command.  I'd actually like to do some automation around the build process, but I have no way how to hook into the ColdFusion Builder's mobile build stuff from the command line, so that might be the focus of a later blog entry.  All the code is still in index.cfm for now.  There's not really that much in the <cfclient> block for now so I didn't even bother messing with it.

Not All Bad

Even though my attempts at organizing the code were a failure, I'm still well pleased with how my app is looking and the Event API was crazy easy to implement.  As always, the code  thus far is committed and pushed to GitHub.  I've also cut a tagged release for the code and a compiled APK for this version.  You see it all in the 0.2 release.

https://github.com/bdw429s/CFClient-Sampler/releases/tag/0.2

Error translating markup widget: The content slug 'cfclient-series-list' does not exist lucee.runtime.exp.CustomTypeException: The content slug 'cfclient-series-list' does not exist at lucee.runtime.tag.Throw._doStartTag(Throw.java:212) at lucee.runtime.tag.Throw.doStartTag(Throw.java:201) at throw_cfm$cf.udfCall(/throw.cfm:11) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.functions.system.CFFunction.call(CFFunction.java:109) at widgets.contentstore_cfc$cf.udfCall(/contentbox/widgets/ContentStore.cfc:60) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:685) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:1930) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:900) at lucee.runtime.functions.dynamicEvaluation.Invoke.call(Invoke.java:49) at models.content.renderers.widgetrenderer_cfc$cf.udfCall(/contentbox/models/content/renderers/WidgetRenderer.cfc:233) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:802) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at models.content.renderers.widgetrenderer_cfc$cf.udfCall(/contentbox/models/content/renderers/WidgetRenderer.cfc:32) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:802) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at models.content.renderers.widgetrenderer_cfc$cf.udfCall(/contentbox/models/content/renderers/WidgetRenderer.cfc:20) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:685) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:1930) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:900) at lucee.runtime.functions.dynamicEvaluation.Invoke.call(Invoke.java:49) at system.web.context.interceptorstate_cfc$cf.udfCall2(/coldbox/system/web/context/InterceptorState.cfc:515) at system.web.context.interceptorstate_cfc$cf.udfCall(/coldbox/system/web/context/InterceptorState.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:802) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at system.web.context.interceptorstate_cfc$cf.udfCall1(/coldbox/system/web/context/InterceptorState.cfc:360) at system.web.context.interceptorstate_cfc$cf.udfCall(/coldbox/system/web/context/InterceptorState.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:802) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at system.web.context.interceptorstate_cfc$cf.udfCall1(/coldbox/system/web/context/InterceptorState.cfc:148) at system.web.context.interceptorstate_cfc$cf.udfCall(/coldbox/system/web/context/InterceptorState.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:685) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:1930) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at system.web.services.interceptorservice_cfc$cf.udfCall1(/coldbox/system/web/services/InterceptorService.cfc:201) at system.web.services.interceptorservice_cfc$cf.udfCall(/coldbox/system/web/services/InterceptorService.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:685) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:1930) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:900) at lucee.runtime.functions.dynamicEvaluation.Invoke.call(Invoke.java:49) at system.ioc.provider_cfc$cf.udfCall(/coldbox/system/ioc/Provider.cfc:110) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.call(UDFImpl.java:217) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:684) at lucee.runtime.ComponentImpl.onMissingMethod(ComponentImpl.java:611) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:574) at lucee.runtime.ComponentImpl.call(ComponentImpl.java:1911) at lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:787) at lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1747) at models.content.basecontent_cfc$cf$8.udfCall8(/contentbox/models/content/BaseContent.cfc:1601) at models.content.basecontent_cfc$cf$8.udfCall(/contentbox/models/content/BaseContent.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.call(UDFImpl.java:217) at lucee.runtime.type.scope.UndefinedImpl.call(UndefinedImpl.java:785) at lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:787) at lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1747) at models.content.basecontent_cfc$cf$8.udfCall8(/contentbox/models/content/BaseContent.cfc:1574) at models.content.basecontent_cfc$cf$8.udfCall(/contentbox/models/content/BaseContent.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.call(UDFImpl.java:217) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:684) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.call(ComponentImpl.java:1911) at lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:787) at lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1747) at models.system.cbhelper_cfc$cf.udfCall7(/contentbox/models/system/CBHelper.cfc:925) at models.system.cbhelper_cfc$cf.udfCall(/contentbox/models/system/CBHelper.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.call(UDFImpl.java:217) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:684) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.call(ComponentImpl.java:1911) at lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:787) at lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1747) at modules.contentbox.themes._default.views._blogincludes_cfm$cf.call(/modules/contentbox/themes/default/views/_blogIncludes.cfm:11) at lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:1034) at lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:926) at lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:907) at system.web.rendererencapsulator_cfm$cf.call(/coldbox/system/web/RendererEncapsulator.cfm:54) at lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:1034) at lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:926) at lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:918) at lucee.runtime.tag.CFTag.doInclude(CFTag.java:319) at lucee.runtime.tag.CFTag.cfmlStartTag(CFTag.java:245) at lucee.runtime.tag.CFTag.doStartTag(CFTag.java:179) at system.web.renderer_cfc$cf.udfCall1(/coldbox/system/web/Renderer.cfc:469) at system.web.renderer_cfc$cf.udfCall(/coldbox/system/web/Renderer.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.call(UDFImpl.java:217) at lucee.runtime.type.scope.UndefinedImpl.call(UndefinedImpl.java:785) at lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:787) at lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1747) at system.web.renderer_cfc$cf.udfCall1(/coldbox/system/web/Renderer.cfc:329) at system.web.renderer_cfc$cf.udfCall(/coldbox/system/web/Renderer.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:685) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:1930) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at system.web.renderer_cfc$cf.udfCall1(/coldbox/system/web/Renderer.cfc:149) at system.web.renderer_cfc$cf.udfCall(/coldbox/system/web/Renderer.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:685) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:1930) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at models.system.cbhelper_cfc$cf.udfCalld(/contentbox/models/system/CBHelper.cfc:2015) at models.system.cbhelper_cfc$cf.udfCall(/contentbox/models/system/CBHelper.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.call(UDFImpl.java:217) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:684) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.call(ComponentImpl.java:1911) at lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:787) at lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1747) at modules.contentbox.themes._default.layouts.blog_cfm$cf.call(/modules/contentbox/themes/default/layouts/blog.cfm:9) at lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:1034) at lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:926) at lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:907) at system.web.rendererencapsulator_cfm$cf.call(/coldbox/system/web/RendererEncapsulator.cfm:54) at lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:1034) at lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:926) at lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:918) at lucee.runtime.tag.CFTag.doInclude(CFTag.java:319) at lucee.runtime.tag.CFTag.cfmlStartTag(CFTag.java:245) at lucee.runtime.tag.CFTag.doStartTag(CFTag.java:179) at system.web.renderer_cfc$cf.udfCall1(/coldbox/system/web/Renderer.cfc:469) at system.web.renderer_cfc$cf.udfCall(/coldbox/system/web/Renderer.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:802) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at system.web.renderer_cfc$cf.udfCall2(/coldbox/system/web/Renderer.cfc:697) at system.web.renderer_cfc$cf.udfCall(/coldbox/system/web/Renderer.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:685) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:1930) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at system.web.renderer_cfc$cf.udfCall1(/coldbox/system/web/Renderer.cfc:558) at system.web.renderer_cfc$cf.udfCall(/coldbox/system/web/Renderer.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:685) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:1930) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at system.frameworksupertype_cfc$cf.udfCall2(/coldbox/system/FrameworkSupertype.cfc:307) at system.frameworksupertype_cfc$cf.udfCall(/coldbox/system/FrameworkSupertype.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:802) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at modules.contentbox.modules.contentbox_ui495.handlers.content_cfc$cf.udfCall(/modules/contentbox/modules/contentbox-ui/handlers/content.cfc:264) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:802) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at modules.contentbox.modules.contentbox_ui495.handlers.blog_cfc$cf.udfCall1(/modules/contentbox/modules/contentbox-ui/handlers/blog.cfc:214) at modules.contentbox.modules.contentbox_ui495.handlers.blog_cfc$cf.udfCall(/modules/contentbox/modules/contentbox-ui/handlers/blog.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:685) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:1930) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:900) at lucee.runtime.functions.dynamicEvaluation.Invoke.call(Invoke.java:49) at system.web.controller_cfc$cf.udfCall3(/coldbox/system/web/Controller.cfc:1197) at system.web.controller_cfc$cf.udfCall(/coldbox/system/web/Controller.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:802) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at system.web.controller_cfc$cf.udfCall3(/coldbox/system/web/Controller.cfc:919) at system.web.controller_cfc$cf.udfCall(/coldbox/system/web/Controller.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:802) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at system.web.controller_cfc$cf.udfCall3(/coldbox/system/web/Controller.cfc:658) at system.web.controller_cfc$cf.udfCall(/coldbox/system/web/Controller.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:207) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:685) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:1930) at lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:866) at lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1766) at coldbox.system.bootstrap_cfc$cf.udfCall1(/coldbox/system/Bootstrap.cfc:292) at coldbox.system.bootstrap_cfc$cf.udfCall(/coldbox/system/Bootstrap.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.call(UDFImpl.java:217) at lucee.runtime.type.scope.UndefinedImpl.call(UndefinedImpl.java:785) at lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:787) at lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1747) at coldbox.system.bootstrap_cfc$cf.udfCall1(/coldbox/system/Bootstrap.cfc:509) at coldbox.system.bootstrap_cfc$cf.udfCall(/coldbox/system/Bootstrap.cfc) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.call(UDFImpl.java:217) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:684) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.call(ComponentImpl.java:1911) at lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:787) at lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1747) at application_cfc$cf.udfCall(/Application.cfc:170) at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:106) at lucee.runtime.type.UDFImpl._call(UDFImpl.java:344) at lucee.runtime.type.UDFImpl.call(UDFImpl.java:217) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:684) at lucee.runtime.ComponentImpl._call(ComponentImpl.java:572) at lucee.runtime.ComponentImpl.call(ComponentImpl.java:1911) at lucee.runtime.listener.ModernAppListener.call(ModernAppListener.java:437) at lucee.runtime.listener.ModernAppListener._onRequest(ModernAppListener.java:133) at lucee.runtime.listener.MixedAppListener.onRequest(MixedAppListener.java:44) at lucee.runtime.PageContextImpl.execute(PageContextImpl.java:2460) at lucee.runtime.PageContextImpl._execute(PageContextImpl.java:2450) at lucee.runtime.PageContextImpl.executeCFML(PageContextImpl.java:2421) at lucee.runtime.engine.Request.exe(Request.java:45) at lucee.runtime.engine.CFMLEngineImpl._service(CFMLEngineImpl.java:1179) at lucee.runtime.engine.CFMLEngineImpl.serviceCFML(CFMLEngineImpl.java:1125) at lucee.loader.engine.CFMLEngineWrapper.serviceCFML(CFMLEngineWrapper.java:97) at lucee.loader.servlet.CFMLServlet.service(CFMLServlet.java:51) at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:764) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.base/java.lang.Thread.run(Unknown Source)

 

 


Adam Tuttle

navigator.app.loadUrl is not the canonical way to load URLs in the system browser, and in fact will not work (navigator.app isn't present, so it'll throw an error) on iOS. Instead look into how they make use of window.open:

http://plugins.cordova.io/#/package/org.apache.cordova.inappbrowser

Adam Tuttle

Pause and Resume events are fired when you multitask. Pause when it goes into the background and resume when it comes back to the foreground.

Brad Wood

Thanks for the tips Adam! I have no previous PhoneGap/Cordova experience so I'm just coming at all this with a healthy dose of Googling and experimentation-- and of course, only an Android phone for testing.

The load URL bit came from a number of StackOverflow answers and I didn't notice that it's just an Android thing. I'll try updating to the method you described.

Aso, thanks for the clarification on resume and pause. I've seen phones with media buttons on them so i just assumed that's what they were for, I did notice the complete lack of actual explanation for these on the CFClient docs. It would be nice if the cfclient docs pointed to the underlying Phonegap library docs for those of us (like me) who are not already familiar with it.

Scott Busche

I'm guessing then adding an actual framework, like FW/1 and just adding client=true still wouldn't work, because of the dynamic views, that's to bad :/

Brad Wood

@Scott, I was thinking something more robust and modular like ColdBox-- I even thought I could maybe put a mobile module on ForgeBox to help add utilities. However, based on what I've seen, I don't think there's anyway to get a generic framework going. I'm really hoping the whole "no CF tags outside of cfclient" is just a bug in the compiler, but I'm not getting my hopes up.

Scott Busche

@Brad I went with FW/1 as since there's just one cfc it'd be easy to add the client=true attribute to it so it'd be picked up in the build. With ColdBox, there'd be a lot more files to apply it to, especially if just trying for a PoC.

Brad Wood

That brings up another thing to try-- is Application.cfc even used by cfclient? The Expense Tracker sample app from Adobe uses Application.cfm and tells you not to even include it when compiling. (It's just there to include a browser detection script)

Brad Wood

Thanks again for the pointers Adam. I have updated my app and amended the blog post above.

Adam Cameron

" CF Builder doesn't just hit my wife over HTTP to get the HTML from it"

Drinking the Stella again, Brad?

(ref: http://www.urbandictionary.com/define.php?term=wife+beater&defid=770054)

Site Updates

Entry Comments

Entries Search