Bootstrap buttons UX improvement

I wrote a while back about a shortcoming of jQueryUI buttonsets Twitter bootstrap has become a much more prominent UI toolset than jQueryUI in recent years but its buttonset corollary has the same problem. The solution is equally as easy, and maybe even a little better given the icon set bootstrap already includes that can be leveraged

http://jsbin.com/xabatatuki/edit?html,css,output

Flattr this!

Grails and Spine.js decoupled development setup

Using Grails as the API server for a spine app and Spine.js as a front end framework has been going well. My Spine app has up till now lived in the web-apps directory of my Grails app. After living with this arrangement for a while I started to realize how separating the two even further would have many all-around advantages. Decoupling code makes new projects easier to manage by isolating versioning, testing, deployment/release for example.

In production environments this sort of setup isn’t all that uncommon, you let Apache or something like it serve static image, js or css files and use mod_jk or some proxy setup to pass the rest on to Java app server like Tomcat.

My setup is a little different in that I want to serve html and js and have to make sure all relative links still work for ajax API requests, and locally there is a cross domain issue if you try to simply work from files in the browser and make calls to Grails running on Tomcat

Long story short; to get my decoupled dev setup here is what I did:

  1. Install Apache
  2. Get it running
  3. Set up virtualhost that will proxy appropriate requests to Grails (need mod_proxy_http)
  4. Add alias definitions for Spine apps

Steps one and two are relatively standard stuff. On Ubuntu it is trivially easy. (sudo apt-get install apache2)

Step 3 was new to me. Searching turned up a good starting point on that, but simply proxy isn’t what I really needed. That brings us to step 4. I needed some interceptors (Alias’) for my spine apps, so that relative links still played nice. For example, I wanted ‘http://localhost/grailsApp/spineApp/’ to direct to my Spine app, while links within the spine app like ‘../api/book/5’ were handled by Grails. an old forum post contained the nugget I was looking for.

Basicially this is what I needed inside of my virtualhost block:

ProxyPass /grailsApp/spineApp !
Alias /grailsApp/spineApp /path/to/spineApp/on/fileSystem

ProxyRequests Off
ProxyPreserveHost On

ProxyPass /grailsApp http://127.0.0.1:8080/grailsApp
ProxyPassReverse /grailsAPp http://127.0.0.1:8080/grailsApp

Grails config will have to change to handle redirects and such in grails controllers, that setting is normally in grails-app/conf/Config.groovy

environments {
development {
grails.serverURL = "http://localhost/grailsApp"
...

Flattr this!

Customized Grails Controller for REST

Update: new code https://gist.github.com/4152409

Grails can do RESTfull easily enough, but I wanted a restful API from grails without throwing out grails scaffolding, So I decided to customize Grails’ controller template.

grails install-templates

then in [app-name]/src/templates/scaffolding/Controller.groovy

import org.springframework.dao.DataIntegrityViolationException
import grails.converters.XML
import grails.converters.JSON

class ${className}Controller {

	static allowedMethods = [list:'GET',
		show:'GET',
		edit:['GET', 'POST'],
		save:'POST',
		update:['POST','PUT'],
		delete:['POST','DELETE']
	]

	def index() {
		redirect(action: "list", params: params)
	}

	def list() {
		params.max = Math.min(params.max ? params.int('max') : 50, 200)
		def list = ${className}.list(params)
		def listObject = [${propertyName}List: list, ${propertyName}Total: ${className}.count()]
		withFormat {
			html listObject
			json { render list as JSON }
			xml { render listObject as XML }
		}
	}

	def create() {
		[${propertyName}: new ${className}(params)]
	}

	def save() {
		def ${propertyName} = new ${className}(params)
		if (!${propertyName}.save(flush: true)) {
			withFormat {
				html {render(view: "create", model: [${propertyName}: ${propertyName}])}
				json {
					response.status = 403
					render ${propertyName}.errors as JSON
				}
				xml {
					response.status =403
					render ${propertyName}.errors as XML
				}
			}
			return
		}
		flash.message = message(code: 'default.created.message', args: [message(code: '${domainClass.propertyName}.label', default: '${className}'), ${propertyName}.id])
		withFormat {
			html {
				redirect(action: "show", id: ${propertyName}.id)
			}
			json {
				response.status = 201
				render ${propertyName} as JSON
			}
			xml {
				response.status = 201
				render ${propertyName}.id
			}
		}
	}

	def show() {
		def ${propertyName} = ${className}.get(params.id)
		if (!${propertyName}) {
			withFormat {
				html {
					flash.message = message(code: 'default.not.found.message', args: [message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id])
					redirect(action: "list")
				}
				json { response.sendError(404) }
				xml { response.sendError(404) }
			}
			return
		}
		def object = [${propertyName}: ${propertyName}]
		withFormat {
			html {object}
			json { render object as JSON }
			xml { render object as XML }
		}
	}

	def edit() {
		def ${propertyName} = ${className}.get(params.id)
		if (!${propertyName}) {
			flash.message = message(code: 'default.not.found.message', args: [message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id])
			redirect(action: "list")
			return
		}
		[${propertyName}: ${propertyName}]
	}

	def update() {
		def ${propertyName} = ${className}.get(params.id)
		if (!${propertyName}) {
			withFormat {
				html {
					flash.message = message(code: 'default.not.found.message', args: [message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id])
					redirect(action:"list")
				}
				json { response.sendError(404) }
				xml { response.sendError(404) }
			}
			return
		}

		if (params.version) {
			def version = params.version.toLong()
			if (${propertyName}.version > version) {
				${propertyName}.errors.rejectValue("version", "default.optimistic.locking.failure",
						  [message(code: '${domainClass.propertyName}.label', default: '${className}')] as Object[],
						  "Another user has updated this ${className} while you were editing")
				withFormat {
					html {render(view: "edit", model: [${propertyName}: ${propertyName}])}
					json { response.sendError(409) }
					xml { response.sendError(409) }
				}
				return
			}
		}

		${propertyName}.properties = params

		if (!${propertyName}.save(flush: true)) {
			withFormat {
				html {render(view: "edit", model: [${propertyName}: ${propertyName}])}
				json {
					response.status = 403
					render ${propertyName}.errors as JSON
				}
				xml {
					response.status = 403
					render ${propertyName}.errors as XML
				}
			}
			return
		}
		withFormat {
			html {
				flash.message = message(code: 'default.updated.message', args: [message(code: '${domainClass.propertyName}.label', default: '${className}'), ${propertyName}.id])
				redirect(action: "show", id: ${propertyName}.id)
			}
			json {
				response.status = 204
				render ${propertyName} as JSON
			}
			xml {
				response.status = 204
				render ''
			}
		}
	}

	def delete() {
		def ${propertyName} = ${className}.get(params.id)
		if (!${propertyName}) {
			withFormat {
				html {
					flash.message = message(code: 'default.not.found.message', args: [message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id])
					redirect(action: "list")
				}
				json { response.sendError(404) }
				xml { response.sendError(404) }
			}
			return
		}
		try {
			${propertyName}.delete(flush: true)
			withFormat {
				html {
					flash.message = message(code: 'default.deleted.message', args: [message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id])
					redirect(action: "list")
				}
				json {
					response.status = 204
					render ''
				}
				xml {
					response.status = 204
					render ''
				}
			}
		}
		catch (DataIntegrityViolationException e) {
			withFormat {
				html {
					flash.message = message(code: 'default.not.deleted.message', args: [message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id])
					redirect(action: "show", id: params.id)
				}
				json { response.sendError(500) }
				xml { response.sendError(500) }
			}
		}
	}

}

then modify your UrlMappings.groovy file to look something like this:

class UrlMappings {

	static mappings = {
		"/$controller/$action?/$id?"{
			constraints {
			// apply constraints here
			}
		}
		name api0: "/api/$controller/$id"(parseRequest:true){
			action = [GET: "show", PUT: "update", DELETE: "delete"]
			constraints {
				id(matches:/\d+/)
			}
		}

		name api1: "/api/$controller"(parseRequest:true){
			action = [GET: "list", POST: "save"]
		}
	}
}

I found the JSON Restful plugin a while after I already did most of this, so that is another way to go. Other than it having some issues in grails 2 right now it looks like a really good option, but one thing I like about my approach is that if you want to customize your API in fine detail you can do so. You can always generate individual controllers and modify them of course. Another option is registering custom marshallers. The simple example for customizing your JSON output without changing any controller code would be doing something like this in your Bootstrap.groovy

import grails.converters.JSON
class BootStrap {
  def init = {servletContext ->;
    JSON.registerObjectMarshaller(Person) {
      def returnArray = [:]
      returnArray['name'] = it.name
      returnArray['addrs'] = it.addresses
      return returnArray
    }
    ...
  }

...

Flattr this!

A Service is not a Product

A recent article details How Google Is ‘Closed’, Just Like Apple

I have to disagree. They may both have aspects that are closed, but they are definitely not ‘just like’ each other. Apple sells a product that is closed, Google sells a service that is closed. The difference is crucial, and should be obvious to anyone who has taken business 101. Some companies make money on a product some on a service. A closed service is nothing new, and doesn’t offend me (though it may entrap me). A closed product is a new concept. It is offensive because it restricts me in new ways that I am not accustomed to evidenced by new laws that have to be created to enforce the restrictions. I would add that if these restrictions actually were justifiable we wouldn’t need new laws to enforce them and thus wouldn’t feel offended.

Apple has plenty of closed services as well by the way, so they may qualify as doubly closed, or maybe closed squared?

Flattr this!

my sunspider test results for Sept.

Run on Ubuntu 10.04 64bit on core2quad@2.66Ghtz

Browser Version Sunspider result
Chromium 7.0.530.0 336.0ms +/- 9.1%
Midori 0.2.8 366.4ms +/- 2.1%
Epiphany 2.30.2 454.4ms +/- 3.8%
Opera 10.62 397.8ms +/- 2.8%
Firefox 3.6.11pre 1805.0ms +/- 3.0%
Swiftfox 3.6.10 955.0ms +/- 1.3%
Firefox 4b7pre 462.6ms +/- 7.9%

Chromium is still leading the pack, but it’s lead is not as dramatic as it used to be. Firefox 4 is shaping up to be a pretty stellar browser overall. Still waiting for the UI on Linux to get some love though. Swiftfox, which is essentially Firefox optimized for specific chip architectures, is interesting. It is almost twice as fast in this benchmark compared to standard Firefox . Maybe Firefox should do this themselves if they want to eek out some extra performance, but I suppose there are reasons not to as well.

Looking back it is pretty impressive how much faster browser javascript has gotten over the last couple years.

Flattr this!

My Android Experience Thus Far

I have had my Samsung Moment since around Thanksgiving 2009. It came with Adroid 1.5 initially and was my first smartphone. I played with it quite a bit at first, but after the first few months the novelty mostly wore off. I fell into a pattern of using it for its browser, email, twitter (mostly reading tweets not tweeting myself.) and occasionally for taking photos. Battery life was pretty dismal which actually discouraged me from using it because I would rather not have a dead phone by the afternoon.

Sometime in the spring it got updated to 2.1 which was sort of a let down to be honest. Most things were just as they were. There were a few more options as far as apps, and a nice new unlock screen, but that about covers it. standard 2.1 stuff was left out by Sprint. I guess they had their reasons.

About a month ago I decided I wanted to try to do some deeper customization. These phones are like little computers. If I could install Ubuntu on my laptop why couldn’t I do something similar on this phone.  Android is basically a Linux distro for mobile phones.

Anyway long story short – rooted it and installed custom ROM with the help of sdx-developers. I actually had some reasons for work that prompted this move, but those aren’t so relevant here. The phone is awesome again. A couple new features and capabilities come with root access that only geeks will appreciate, and all the bloatware that I never used and couldn’t get rid of was gone, but the most useful aspect is the dramatically improved battery life. Now a full day of heavy use is no problem. Two full days is pushing it a bit, but I do it from time to time. The wireless tethering is slick and actually useful when traveling. (did I mention I am not paying Sprint for this feature? … don’t tell….)

It comes with some consequences. The occasional random reboot or flickery screen, but so far they haven’t really interrupted anything I needed to do. reboot is faster than it used to be too. Overall the stability isn’t really a hit, because the phone wasn’t glitch free before anyway.

What is cool is that these now ‘old’ phones (Sprint tweeted they were done providing updates for the Moment 6-7 months after it was released!) can continue to be very useful because of an active developer/hacker community. I do encounter these longings to have the latest greatest devices, but I am also beginning to see much more challenge/fun/value in being able to keep lower end hardware useful through better software.

The more I have a smartphone the less I want to be without one. It is interesting how my computer usage habits are changing because of it. Maybe a topic for another post.

Flattr this!

week of year in jqueryUI datepicker

I needed to display week of year in a colum on the jqueryUI datepicker. Luckily it has the feature built in, unluckily it has a quirk relative to the way we typically display calendars in the US with Sunday as the first day colum. At first I thought it was a bug, but technically it is not, so I guess it is better classified as a gotcha. Anyway, to get the week of year to display as we would expect in the US you need two pieces.

First where you instantiate the datepicker:

$(".uiDatePicker").datepicker({minDate: -1, maxDate: '+10D', defaultDate: +1, hideIfNoPrevNext: true, showWeek: true, calculateWeek: myWeekCalc });

The important argument there is the ‘calculateWeek: myWeekCalc’ .

Next you need to define the custom week of year calculator, in this case ‘myWeekCalc’:

function myWeekCalc(date) {
  var checkDate = new Date(date.getTime());
  // Find Thursday of this week starting on Sunday
  checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay()));
  var time = checkDate.getTime();
  checkDate.setMonth(0); // Compare with Jan 1
  checkDate.setDate(1);
  return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
}

I borrowed this from the jqueyUI source and tweaked it to calculate the week based on the Thursday in the row instead of the first day colum in the row. To me this makes sense given the way the ISO for week of year is determined.

It seems to be working perfectly for me. Let me know if you use this and run into issues 🙂

Flattr this!

Customizing The Ubuntu Terminal

The Obvious:

With a terminal open select Edit -> Profile Preferences.

In here you can set background color, transparency, font, font-size, text colors and more.

A couple not-quite obvious things I like to do.

Uncheck the ‘show menubar by default in new terminals’ – it just isn’t very useful and a right click in the terminal gives you some of those options anyway and if what you need is not there you can easily bring back the menubar from there.

Increase the number of scrollback lines – personally I at least double it to 1024 lines as Grails errors are long and ugly and that is the framework I spend a lot of my time working with. Your mileage may vary.

Advanced:

The .bashrc file in your home directory is where extra configuration options lie.

Colors:

Ubuntu disables the prompt being a different color. but simply uncomment the proper line in .bashrc and you’ll undo that. Look in the vicinity of line 36-40. The comment says the goal is to avoid distraction… just doesn’t make sense to me. A colored prompt helps me distinguish certain pieces of text from others, so for me, it is essentially the opposite of a distraction helping me focus on the pieces I need to read from the pieces I don’t.

Aliases:

Many Linux command line gurus expect “ll” or certain other common aliases to work. Uncomment a couple more lines in Ubuntu’s .bashrc file and they will. Look in the vicinity of line 80-83.

you’ll also see just a couple lines down:

# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
So if you’d like to add extra aliases create a .bash_aliases file and fill it in. Sweet right?!

Flattr this!

Starting and Stopping Development Server on Laptop

I am not sure how much battery life suffers on my laptop when it is unnecessarily running Apache, PostgreSQL and MySQL, but the databases consistently show up in the list when I use PowerTop, so it is probably not insignificant. Anyway, since I knew it couldn’t be to hard to shut them down and start them up again when I needed to, I wrote a couple little bash scripts to do the job.

LampStart.sh

 
  #sudo /etc/init.d/mysql start
  sudo service mysql start
  sudo /etc/init.d/postgresql-8.4 start
  sudo /etc/init.d/apache2 start

LampStop.sh

 
  #sudo /etc/init.d/mysql stop
  sudo service mysql stop
  sudo /etc/init.d/postgresql-8.4 stop
  sudo /etc/init.d/apache2 stop

To reuse these you might have to modify them for your particular server setup, but that would be relatively easy. The other thing I did is to make them executable. Then I put them on my desktop and I treat them like simple switches. click on, click off. easy!

All this made me think again about the merits of using something like XAMPP for development environments. Not really sure what I would lose or gain by doing that, but one thing I do remember from my Windows days when I used it that it had a control panel with lots of handy on/off/restart switches.

Flattr this!

Linux Mint 7 Gloria

I am very excited about the pending release of Linux Mint 7 “Gloria”. I plan to do a fresh install with it on my main work desktop when the release drops. Mint is a well polished and subtly but well improved version of Ubuntu. It’s releases tend to lag a month or so behind Ubuntu, but it seems like they use that time well. In my experience Mint has been more stable, better looking, and more complete in its features.

I was thinking this morning as I was looking at reviews of the Ubuntu Netbook Remix, “I wonder if Mint will have a netbook remix version.” That would probably be pretty cool, even though I don’t personally have a netbook to benefit from something like that. What might be better is if some of the specialized netbook distros like eeebuntu would learn a thing or two from Mint or maybe use Mint as their base distro.

The next thought was that system76, a company that sells ubuntu laptops, should use Mint instead. There are probably good reasons for them to stick with a mainline distro though, for example they also make servers and Linux Mint is really meant as a desktop OS. So maybe there should be a company that makes just a small hanful of machines that are designed for Linux Mint. I know I would be very interested in a laptop that was designed and built to the same types of quality standards as the Mint distro itself. I am thinking something comparable to Apple’s laptops. In my mind such a product would be a real competitor in the market as well. Maybe somebody with time and ambition will read this and be inspired…

Flattr this!