My Love/Hate Relationship with WordPress

Written by Perry Woodin on . Posted in Misc Ramblings, Software

WordPress is really amazing. I can standup a WordPress site on Amazon’s EC2 in under 15 minutes. And we’ve got a developer here at Troy Web who is adept at writing plugins. So, I love WordPress because it can facilitate launching a basic website in under a day.

What I hate about WordPress is it is quite often the wrong tool for the job. That’s not really the fault of WordPress. People become enamored by its ease of use and try to shoehorn functionality that is probably better served by custom application development. At a certain point they feel locked into the platform. That’s never a good thing.

But what really prompted this post/vent is the ubiquity of WordPress plugins. I recently helped a friend move from WordPress.com to a self hosted install. We had to replace some widgets and it took me hours to find a suitable plugin. Not because the plugin didn’t exist but rather because there were hundreds of plugins that claimed to offer the same functionality. My new rule for plugins is only install if the number of downloads exceeds 250,000.

So, if you want a WordPress site, I’m happy to help, but lets talk about your site’s goals before you commit to the platform.

Distracto-boy meets layout=plain

Written by jbriccetti on . Posted in App Dev, Architecture, Misc Ramblings

I’m a huge fan of UI frameworks – particularly Twitter Bootstrap and jQuery UI – and I love making things look great – these frameworks really help me do that. Basically, I’m a wannabe designer. I admit it.

A simple bootstrap-infused login form

a bootstrap styled list view

Some days, i just spend way too much time on getting those buttons, just right, or the right table or grid layout – it can be a time-sucker and I sometimes find myself behind on a deadline because I spent too much time pushing around the UI and not enough time building the “application” code. Enter: layout=plain.

In the CF Wheels framework, a Rails-like approach to CFML development, the base controller class (/controllers/Controller.cfc) has an init method where most folks handle all the pre-processing needed for all requests. We scaffold our CF Wheels apps from our  TroyWeb1 base-build – which includes a “filter” in the base controller to handle  initialization and cleansing of request parameters as well as setting up defaults for the built-in params used by wheels for layout and format.

<cfscript>
	firewall();
	cleanseAndStrip(params);
	param name="params.format" default="html";
	param name="params.layout" default="";
	switch(params.layout) {
		case "plain":
			usesLayout("/plain");
			break;
		default:
			usesLayout("/bootstrap");
	} //end switch
	if(params.format EQ "xml" || params.format EQ "json"){
		get("cfsetting").setEnablecfoutputonly(true);
		get("cfsetting").setShowdebugoutput(false);
		set(showDebugInformation=false);
	}
</cfscript>

Like most app dev frameworks, CF Wheels leverages a front controller design pattern. That means all requests get routed through the same initial channel and that means certain things can easily be managed in that initial routing – like format and layout, which in CF Wheels are parameterized so the request can dictate what format needs to be returned. This is really great in the world of JSON feeds or other syndication streams. Thus, add ?format=json to the url and the plumbing to route your call to a json generating view is all built in. [checkout provides()] Layouts are managed this way too when combined with the usesLayout() controller method.

If you too are mesmerized by the shiny, candy-like buttons, join Distracto-boy in the fight. Create /views/plain.cfm with one line of code:

<cfoutput>#includeContent()#</cfoutput>

and simply change the default for params.layout to “plain”

	param name="params.layout" default="plain";

and there ya go. Badabing! all those distractions are gone and you can focus on creating the app without all that glitter.

layout=plain rendering of the same form

layout=plain on the same list view

No daddy

Written by jbriccetti on . Posted in Gotchas, Misc Ramblings

I was in the fray when a few clients called me today to say “help, the website is down” – it seemed odd a few sites were down, others weren’t. of course trying to get o the websites that were experiencing problems, well i just got nuthin’ – no error message back from the webserver or anything. seemed odd.

I saw the webserver instances were up so i added host headers for the IP quickly, just to see if i could get traffic routed there – and it worked – so i knew immediately it was a dns issue – and that’s when i discovered go daddy was down for the count.

 

$createNestedParamStruct is a cfwheels badass

Written by jbriccetti on . Posted in Misc Ramblings

One of the nice things about the cfwheels framework is the ability to build forms that map directly to a model object using form helpers. On the “receiving” end (in your controller), you have access to a key in the params scope that is a nested structure of data that’ll line right up with your model. This stuff makes scaffolding CRUD forms a serious layup.

#select(label="org", objectName="contact", property="org_id",options=orgs, valueField="org_id", textField="org_name",includeBlank="Select an Org")#

Recently, I have been working on a standard set of scaffolding templates to take the CRUD generation to a whole new level. Along with a few methods in the base controller (and some helper methods for the view) I’ve worked in generic code to include pagination, column header sorting and filtering in all the generated templates. While I was working on the filter controls, I realized I’d seriously like to have a nested param structure with all the filter fields contained within. But the form helpers are specifically for mapping fields to a model object, and I’m not doing that. So I looked further into how wheels constructs those nested params for the form helpers and low-and-behold, i found a simple little function that when used with the right naming convention, tears it up on the form-field parsing front. And, it’s called on every request automagically, so you aint gotta do nuthin to make it work. Schvinnnng!

Looking inside /wheels/dispatch/request.cfm i found the $createNestedParamStruct() method. After discovering how it expects and parses field names, i then reverted back to my filter form and started naming fields like this:

#selectTag(label="type",name="filters[label_id]", options=labels,textfield="label_description",valueField="label_id")#
#selectTag(label="org",name="filters[org_id]", options=orgs,textfield="org_name",valueField="org_id")#
#selectTag(label="contact",name="filters[contact_id]", options=contacts,textfield="full_name",valueField="contact_id")#

Notice I’m not using the objectName or property arguments to the selectTag function (like we did in the select(0 function for the model object mapping) – selectTag doesn’t tie the form field to a model “object” But, by using the [] notation on the field name, wheels automatically creates a sub-structure in the params scope when the post occurs… so by the time I can even get my hands on these any of these params in the controller, it’s already neatly encapsulated in a sub-structure named “params.filters”

dump of params.filters

How to restore from svndump (or svn export)

Written by Perry Woodin on . Posted in Misc Ramblings

If you have ever had to move svn from one server to another, you were probably given a svndump file that contains all revision history. If you are like me, you favor a GUI over command-line. Unfortunately, most GUIs don’t support a load or restore.

So for ease of future reference, here is how to restore from a svndump file (sometimes called an svn export).

First step. Fire up terminal.

Note: You can find all of this info in the book Version Control with Subversion at http://svnbook.red-bean.com/

Get the svndump

You’ll probably get a svndump file from your svn host. If you already have the file you can skip this section.

If you need to create a svndump file you can do so with svnadmin dump. If you type the following into terminal, you’ll create the file repo.svndump. This file will contain a dump of your entire repository history.

$ svnadmin dump /path/to/repo > repo.svndump

Example:
$svnadmin dump /www/repos/helloworld > helloworld.svndump

Create a new empty repository

In order to restore the repository that was just dumped, you need to create a new empty repository. Here you’ve got a couple of options. Most svn GUI clients allow you to create a new repository. You can either go that route, or create the new repository via the command-line like so.

$ svnadmin create /path/to/new/repository

Example:
$ svnadmin create /www/helloworld

Load (restore) the svndump

Once you’ve got a new empty repository, you can load the svndump file. Here’s where most svn GUI clients let you down. In most cases, your only option is command-line.

$ svnadmin load /path/to/new/repository < /path/to/svndump

Example:
$ svnadmin load /www/helloworld < /temp/helloworld.svndump

Let that run until you see the command prompt again, and your new repository will be fully loaded.

Sync with a remote repository

If you need to restore to a new remote repository, you will need to use svnsync. I have only had success with this if the remote repository is new (empty). Here is the command-line sync.

$ svnsync init --username YOUR_REMOTE_REPO_USERNAME REMOTE_URL SOURCE
$ svnsync sync --username YOUR_REMOTE_REPO_USERNAME REMOTE_URL

Example:
$ svnsync init --username troyweb http://mysvnhost.com/helloworld file:///www/helloworld
$ svnsync sync --username troyweb http://mysvnhost.com/helloworld

SVN obstructed? How to remove all .svn folders

Written by Perry Woodin on . Posted in Misc Ramblings

I recently committed a rather large code base to SVN. To my knowledge, the code base had not been in SVN before. Turns out I was wrong. Upon attempting to commit the working copy, I kept seeing directories flagged as “obstructed.” I tried an SVN clean, but that didn’t help.

The problem was a bunch of legacy .svn folders in various directories. My fix was to fire up terminal and delete all of the .svn folders in one fell swoop. Here’s how to do it.

  • Start Terminal.
  • Change directory to your code base. This is really important. Otherwise you could unintentionally delete .svn folders you would rather keep.
    $ cd /Library/WebServer/targetdirectory
  • I like to list, just to make sure I’m in the right place.
    $ ls
  • Find and delete the .svn files
    $ find ./ -name ".svn" | xargs rm -Rf

Done. Now you should be able to commit without issue.

Preventing Browsers from Caching Resources with Spring 3…Sort of…

Written by Sean Ryan on . Posted in Misc Ramblings

Developing Web apps can be very frustrating sometimes since there are so many different browsers and environments that you need to consider just to get simple stuff working at times.  Not too long ago I had to tell a client to hold down the control key and hit F5 to force her browser to fetch a new file from the server instead of from her browser’s cache.  She wasn’t seeing the UI changes I made, but she was seeing the content. It looked horrible. That’s terribly embarrassing to say to a client and entirely preventable.

With Spring, you can use a WebContentInterceptor to modify properties of the request to help govern how a browser will handle resources that don’t change very often. Here’s a snippet from a config file that I’ve used:

<mvc:interceptors>
 <bean id="webContentInterceptor" class="org.springframework.web.servlet.mvc.WebContentInterceptor">
 <property name="cacheSeconds" value="0"/>
 <property name="useExpiresHeader" value="true"/>
 <property name="useCacheControlHeader" value="true"/>
 <property name="useCacheControlNoStore" value="true"/>
 </bean>
</mvc:interceptors>

Fairly straight forward. I’m saying to browser, don’t cache stuff.  In an actual production system, you’ll want to be a little more specific about what you want cached and not cached. In development though, it’s nice to never cache.

The problem with this is the browser can still do whatever it wants. So how do we control the browser? We don’t. Instead, we spoof the location of the resource and trick the browser into thinking it’s a brand new file – obviously not cached. This is the recommended technique and Spring handles it quite nicely. Check it out.

We use a resource mapping.

<mvc:resources location="/static,classpath:/META-INF/" mapping="/static-2.1.0/**"/>

This configuration says, for all requests that come in that have the URL pattern /staticapp-version followed by anything, look for the resource in folder named static in the web root or in a jar on the classpath within its META-INF directory.

Now, a web browser that requests a resource called /static-2.1.0/css/app.css will serve the app.css file from the same old location but since the browser doesn’t know that, it must download the app.css file regardless of whether or not a previous version was cached.

Cool.

We can improve upon this by making the version dynamic so you don’t need to modify your Spring config file each time you deploy.

<mvc:resources location="/static,classpath:/META-INF/" mapping="/static-${application.version}/**"/>

You can add a variable to a properties file and read that file into your Spring config file. Then you’ll be able to use the variable in a SpEL expression.

<util:properties id='appProperties' location='classpath:default.properties'/>
<context:property-placeholder properties-ref="appProperties"/>

Your default.properties file might look like this.

application.version=2.1.0

Just update this file on each deploy and all your static resources will be forced to be loaded from browsers.

You have much more control over what resources should be cached and the expiration of those caches than I’ve talked about here. For more information, I encourage you to explore the Spring documentation which talks about this very technique along with several other cache control strategies.

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-interceptors

 

Switching Annotation Preprocessors for JPA Meta Model Generation in Eclipse

Written by Sean Ryan on . Posted in Misc Ramblings

In case you haven’t noticed, Eclipse doesn’t have the most stable annotation preprocessor. I use Hibernate as my persistance provider but I’ve experienced problems with Hibernate’s meta model generation jar – at least when used in Eclipse. In need of a solution, a colleague turned me onto OpenJPA for the meta model generation and still use Hibernate as the actual persistance provider. Open JPA is a bit too strict as a provider still but works like a charm for metamodel generation.

To set the annotation processor in Eclipse, follow the steps below. I’ll set it to OpenJPA, but you can use any one you want.

  1. Right click the project in the project explorer and go to Properties…
  2. In the left side navigation area, select Annotation Processing under Java Compiler.
  3. You’ll want to check Enable Project Specific Settings, then Enable annotation processing
  4. Select the generated source directory – I build with Maven, so I’ve selected the standard Maven location.
  5. You can see from the the screenshot that I have a single processor option openjpa.metamodel=true. You’ll need to set this if you’re planning to use OpenJPA for your metamodel generation. Hibernate does not require this. See the documentation of your processor if using something other than OpenJPA or Hibernate.

    Annotation Processor Screenshot - Spring Source Toolkit

    Annotation Processor Screenshot – Spring Source Toolkit

  6. Next, expand Annotation Processing in the left side navigation and select Factory Path.
  7. Here is where you’ll point to the jar file containing your metamodel generator. You can see that I unchecked the eclipse version and later tried the Hibernate version and now I’m using the OpenJPA version.

    Annotation Processing Screenshot - Factory Path

    Annotation Processing Screenshot – Factory Path

  8. Click OK and you should be good to go.

Also, you’ll want to make sure you update your pom.xml to use the same provider.

 

 

Relocating localStorage in iOS 5.1 for PhoneGap Applications

Written by Sean Ryan on . Posted in Misc Ramblings

See bug details here: https://issues.apache.org/jira/browse/CB-330

The general problem is that as of iOS update 5.1 (3/2012), Apple no longer considers data stored in localStorage or the SQL Lite DB to be persistent data. This means that any data stored using these HTML5 features is considered volatile across updates and process termination. To say the least, this is a problem for any developers replying on this data being available across app updates, backups, or crashes. It’s bad enough that we only get 5MB of storage (2.5MB if you’re storing 16bit text characters), now we can’t even persist the data when the app is run natively? That’s yucky.

Product groups like Sencha, Ext4.js, and  PohoneGap are working on work arounds for this new Apple specification but as of this entry, they aren’t all there yet. Until a fix exits for Sencha, I’ve taken the approach of changing the default location of localStorage and the SQL DB that resides within the app container on the device. This solution solves the problem entirely and has the added benefit of tons more storage capacity. The down side is that it requires modification of the native code-base and therefore requires apps for the iOS system to be built in xcode. Consequently, mobile app dev for iOS using this approach now requires a Mac. Lucky me, I have me some Macs.

The Fix

Copy and paste the following code into AppDelegate.m‘s method didFinishLaunchingWithOptions. The placement likely doesn’t matter but I add it as the first block of code.

You can read through the code to see that it won’t affect you older iOS devices and that it just relates where the localStorage mechanism point to on the device.

/* Fix problem with ios 5.0.1+ and Webkit databases described at the following urls:
     *   https://issues.apache.org/jira/browse/CB-347
     *   https://issues.apache.org/jira/browse/CB-330
     * My strategy is to move any existing database from default paths
     * to Documents/ and then changing app preferences accordingly
     */

    NSString* library = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)objectAtIndex:0];
    NSString* documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

    NSString *localStorageSubdir = (IsAtLeastiOSVersion(@"5.1")) ? @"Caches" : @"WebKit/LocalStorage";
    NSString *localStoragePath = [library stringByAppendingPathComponent:localStorageSubdir];
    NSString *localStorageDb = [localStoragePath stringByAppendingPathComponent:@"file__0.localstorage"];

    NSString *WebSQLSubdir = (IsAtLeastiOSVersion(@"5.1")) ? @"Caches" : @"WebKit/Databases";
    NSString *WebSQLPath = [library stringByAppendingPathComponent:WebSQLSubdir];
    NSString *WebSQLIndex = [WebSQLPath stringByAppendingPathComponent:@"Databases.db"];
    NSString *WebSQLDb = [WebSQLPath stringByAppendingPathComponent:@"file__0"];

    NSString *ourLocalStoragePath = [documents stringByAppendingPathComponent:@"LocalStorage"];;
    //NSString *ourLocalStorageDb = [documents stringByAppendingPathComponent:@"file__0.localstorage"];
    NSString *ourLocalStorageDb = [ourLocalStoragePath stringByAppendingPathComponent:@"file__0.localstorage"];

    NSString *ourWebSQLPath = [documents stringByAppendingPathComponent:@"Databases"];
    NSString *ourWebSQLIndex = [ourWebSQLPath stringByAppendingPathComponent:@"Databases.db"];
    NSString *ourWebSQLDb = [ourWebSQLPath stringByAppendingPathComponent:@"file__0"];

    NSFileManager* fileManager = [NSFileManager defaultManager];

    BOOL copy;
    NSError *err = nil; 
    copy = [fileManager fileExistsAtPath:localStorageDb] && ![fileManager fileExistsAtPath:ourLocalStorageDb];
    if (copy) {
        [fileManager createDirectoryAtPath:ourLocalStoragePath withIntermediateDirectories:YES attributes:nil error:&err];
        [fileManager copyItemAtPath:localStorageDb toPath:ourLocalStorageDb error:&err];
        if (err == nil)
            [fileManager removeItemAtPath:localStorageDb error:&err];
    }

    err = nil;
    copy = [fileManager fileExistsAtPath:WebSQLPath] && ![fileManager fileExistsAtPath:ourWebSQLPath];
    if (copy) {
        [fileManager createDirectoryAtPath:ourWebSQLPath withIntermediateDirectories:YES attributes:nil error:&err];
        [fileManager copyItemAtPath:WebSQLIndex toPath:ourWebSQLIndex error:&err];
        [fileManager copyItemAtPath:WebSQLDb toPath:ourWebSQLDb error:&err];
        if (err == nil)
            [fileManager removeItemAtPath:WebSQLPath error:&err];
    }

    NSUserDefaults* appPreferences = [NSUserDefaults standardUserDefaults];
    NSBundle* mainBundle = [NSBundle mainBundle];

    NSString *bundlePath = [[mainBundle bundlePath] stringByDeletingLastPathComponent];
    NSString *bundleIdentifier = [[mainBundle infoDictionary] objectForKey:@"CFBundleIdentifier"];
    NSString* libraryPreferences = @"Library/Preferences";

    NSString* appPlistPath = [[bundlePath stringByAppendingPathComponent:libraryPreferences]    stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.plist", bundleIdentifier]];
    NSMutableDictionary* appPlistDict = [NSMutableDictionary dictionaryWithContentsOfFile:appPlistPath];

    BOOL dirty = NO;

    NSString *value;
    NSString *key = @"WebKitLocalStorageDatabasePathPreferenceKey";
    value = [appPlistDict objectForKey: key];
    if (![value isEqual:ourLocalStoragePath]) {
        [appPlistDict setValue:ourLocalStoragePath forKey:key];
        dirty = YES;
    }

    key = @"WebDatabaseDirectory";
    value = [appPlistDict objectForKey: key];
    if (![value isEqual:ourWebSQLPath]) {
        [appPlistDict setValue:ourWebSQLPath forKey:key];
        dirty = YES;
    }

    if (dirty) 
    {
        BOOL ok = [appPlistDict writeToFile:appPlistPath atomically:YES];
        NSLog(@"Fix applied for database locations?: %@", ok? @"YES":@"NO");
        [appPreferences synchronize];
    }
    /* END Fix problem with ios 5.0.1+ and Webkit databases */

Next, copy and paste the following code in AppDelegate.h. Place it as the last line of code above @end

/* 
 * Returns YES if it is at least version specified as NSString(X)
 */
#define IsAtLeastiOSVersion(X) ([[[UIDevice currentDevice] systemVersion] compare:X options:NSNumericSearch] != NSOrderedAscending)