M@'s Musings

Pontificus Maximus

M@ McCray

I'm the creator of the webcomic ZooDotCom, and a web developer.

This is where I post random thoughts/images.

Enjoy!

Search my Posterous

 

Liquid.js, A Non-Evaling Template Engine in JavaScript

Of late, I’ve needed a robust JavaScript template engine that doesn’t use eval (and preferably not with). Also, I’ve always liked Liquid.

So, two plus two equals… JavaScript! Hah, you thought it was four, didn’t you? Well, that’s what you get for thinking.

Yeah, long story short, I ported Liquid to JavaScript. You can grab it from github (where else?) here: http://github.com/darthapo/liquid.js

I’m calling this version 0.1. It’s not battle tested yet, but I know it’ll work using Firefox 3+, Safari 3+, and Adobe Air 1.1.

So what are the differences between running the Ruby version and the JavaScript version? Well, as far as the templates themselves, nothing. It’s a full port, so all of Liquid’s default tags and filters are supported in Liquid.js. I even added a placeholder function that you can implement yourself (based on your own needs) to support the ‘include’ tag.

Liquid.readTemplateFile = function(path) {
  var elem = $(path);
  if(elem) {
    return elem.innerHTML;
  } else {
    return path +" can't be found."; 
    // Or throw and error, or whatever you want...
  }
}

var src = "{% include 'myOtherTemplate' with current_user %}";

var tmpl = Liquid.parse( src );

alert( tmpl.render({ current_user:'M@' }));

An easy way to include template in a page is to use script elements, like this:

<!-- Browsers ignore script blocks with an unrecognized type.  -->
<!-- Makes for pretty good inline template storage.  -->
<script type="text/liquid" id="myOtherTemplate">
  Hello, {{ current_user }}!
</script>

Currently, Liquid.js requires MooTools. Some of the things I’d like to polish up:

  • Remove MooTools requirement, run entirely independent of any other js library
  • Add Rhino for console-based testing
  • Test, test, test on Internet Exploder Explorer
Posted
 

TaskTHIS is Back!

Media_httptaskthiselu_eehmr
TaskTHIS is up and running again. Sorry for the downtime, but since I host it for you guys for free, I don't think you can complain too much. :-)

In the process, it's moved URLs as well. You can now find it at:

http://taskthis.elucidata-apps.com

The old URL (taskthis.darthapo.com) should redirect you there automatically.

I had to convert TaskTHIS from Rails 1.1 to Rails 2.1, which was not too fun. Especially since it had already been converted from pre-1.0 to 1.1.

Anyway, it's probably still a little rough around the edges, so if you find any bugs please add a post in the support forum.

Posted
 

Shuffling Servers

I'm in the process of shuffling servers around, so a lot of the apps/sites I've created will be down whilst I make the move.

Some things are already back up: ZooDotCom and Lil Monstas. Others, like Maelstrom and TaskTHIS are not.

In fact, TaskTHIS may take a little while longer to bring back up. I'm going to update it to Rails 2. That having been said, I did make a backup of the database before bringing it down. So when it returns all of your data will still be there.

Also, I've noticed that I post a lot fewer blog posts these days since I generally vent on my Twitter account, I wind up having not too much left to say. Funny that.

Posted
 

Sterling-code.com For Your HTML Slicing Needs!

Sterling Code can help you get from design to finished HTML quickly.

We specialize in creating semantic, SEO optimized XHTML and HTML for rich emails. Offering competitive rates and quick turn-around, check us out!

Posted
 

Animating NSViews In RubyCocoa

Yesterday we talked about how to make nifty selectable toolbars like this:

Selectable Toolbar

Now let’s look at the finishing touch for our Preferences window; Animating the panel changes. We’ll be flying through this at a pretty good clip, but don’t worry. I’ll provide the full source for your inspection.

First off, let’s add some new outlets to our window controller:

ib_outlets :generalPrefsView,
           :advancedPrefsView

Custom View

Now in Interface Builder, we’ll create the views for each preference pane by dragging Custom Views from the Library onto our Preferences.nib.

Note: Be sure to drop the Custom Views on the main nib window in IB, not on the Preferences NSWindow. Your project (in IB) should look something like this:

IB Project Window

Hook up the outlets to the new views, and edit your preference panels to your heart’s desire. From here, we go back to the code.

Tip: Be sure to set the auto-sizing on your preference panels (the NSView’s) so that it matches the NSWindow’s contentView.

Autosizing Panel

Next up are some helper methods for our window controller. I won’t spend too much time explaining these, but they’re pretty straight forward.

def viewForTag(tag)
  case tag
    when 0: [@generalPrefsView,  "General"]
    when 1: [@advancedPrefsView, "Advanced"]
  end
end

#viewForTag actually returns our NSView and a title string.

def newFrameForNewContentView(view)
  newFrameRect = window.frameRectForContentRect(view.frame)
  oldFrameRect = window.frame
  newSize = newFrameRect.size
  oldSize = oldFrameRect.size
  frame = window.frame
  frame.size = newSize
  frame.origin.y = frame.origin.y - (newSize.height - oldSize.height)
  frame
end

#newFrameForNewContentView calculates the new frame rectangle for the window based on the new view (preference pane).

Now we’re ready to fill out our selectPrefPanel action:

ib_action :selectPrefPanel do |sender|
  tag =  sender.tag
  view, title = self.viewForTag(tag)
  previousView, prevTitle = self.viewForTag(@currentViewTag)
  @currentViewTag = tag
  newFrame = self.newFrameForNewContentView(view)
  window.title = "#{title} Preferences"
  # Using an animation grouping because we may be changing the duration
  NSAnimationContext.beginGrouping
    # Call the animator instead of the view / window directly
    window.contentView.animator.replaceSubview_with(previousView, view)
    window.animator.setFrame_display newFrame, true
  NSAnimationContext.endGrouping
end

Right on! Now we setup the initial pane when the window loads:

def awakeFromNib
  window.setContentSize @generalPrefsView.frame.size 
  window.contentView.addSubview @generalPrefsView
  window.title = "General Preferences"
  @currentViewTag = 0
  # Will use CoreAnimation for the panel changes:
  window.contentView.wantsLayer = true
end

That pretty much does it. Now you have a professional looking preferences window. So enough of those dang blasted NSTabViews!

Here’s the completed PreferencesController.rb. Or, you can download the full Xcode project. (Requires Leopard, Xcode 3, and Interface Builder 3)

Happy coding!

Posted
 

Selectable Toolbar Icons in RubyCocoa

So you’d like to have some nifty selectable toolbar items to make your preferences window really polished? Or maybe you’d like to use the toolbar as a tab-set like Coda does. No problem, here’s how to do it.

Note: I’m using Leopard & Interface Builder 3. You can create selectable toolbars in Tiger, but the process is different and not within the scope of this article.

To start, in the window controller, add an ib_action:

ib_action :selectPrefPanel do |sender|
  # We'll do stuff here later...
end

Then in Interface Builder, create the toolbar and the toolbar items. For each toolbar item:

  • Turn off the autovalidates option
  • Set the action to target the selectPrefPanel: action on your window controller (probably the File’s Owner)

Before you save the Nib, be sure and set the toolbar’s delegate to the window controller.

Now back in the window controller code, implement a toolbarSelectableItemIdentifiers method in your controller:

def toolbarSelectableItemIdentifiers(toolbar)
  @toolbaridents ||= begin
    window.toolbar.toolbaritems.collect {|i| i.itemIdentifier }
  end
end

Lastly, when the window loads, select the first toolbar item:

def awakeFromNib
  window.toolbar.selectedItemIdentifier = window.toolbar.toolbarItems[0].itemIdentifier
end

Viola! Now you have selectable toolbar items.

Here’s the full source for the window controller.

It’s worth mentioning that this isn’t specific to RubyCocoa. You can do the same thing in Objective-C, Python, or Nu (example).

Next, I’ll show you how to create the views that will go within your preferences window, and how to animate them to really finish it off.

Update: Find the next article here.

Posted
 

Nuapp

Nuapp is a simple script (written in Nu, of course) that generates a skeleton Nu application. Check it out here: http://pastie.textmate.org/138976

You'll probably want to customize it. For instance, I doubt you'll want to use my "Requisite Beta Disclaimer".

Posted
 

Snippet: Compiling XIBs into NIBs in your Nukefile

Here's a function I use in my Nukefile to compile XIBs into NIBs:

(function compile-xibs-from-to (xibs to_path)(if (and (!= target "clobber") (!= target "clean"))(then(SH "mkdir -p #{to_path}")((filelist xibs) each:(do (xib)(set nib (xib stringByReplacingPathExtensionWith:"nib"))(SH "ibtool #{xib} --compile #{to_path}/#{( nib fileName )}")))(filelist "^#{to_path}/[^/]*.nib$"))(else nil)))(set @nib_files (compile-xibs-from-to "^resources/views/[^/]*.xib$" "build/nibs"))

It uses ibtool to compile the .xib into a .nib:

ibtool source.xib --compile target.nib
Posted
 

AJAX Is X-TREME!

Author’s Note: I actually wrote this back in 2006, but it kind of fell through the cracks. So I present it now, for your amusement/pleasure/whatever…

The term “Web 2.0” has become synonymous with AJAX. With the hype and buzz that’s surrounded “Web 2.0”, people seem to invent reasons for using it. Even when it makes no sense. Sometimes especially when it makes no sense. Just because it’s “cool!”

ZooDotCom Presents - AJAX is X-TREME!!!

Oh yes, AJAX is cool. It can really enhance the user experience of your application. And yes, you can do some interesting single page applications that don’t require a server. But hopefully you’re not quite as gung-ho about it as Charlie here (from the comic above).

Rails makes it easy to use AJAX. Really easy. Too easy? Perhaps. And occasionally even I’ve been sucked into the trap of throwing out a quick, AJAXified, solution rather than taking the time to build it right, to build it compatible with the “Web 1.0” (or non-JavaScript enabled) folks. But I shouldn’t have. And neither should you.

ZooDotCom Presents - Fine, then how do I it?!

That, Charlie, is a good question.

In my next post I’ll talk a bit about Comatose, and how I went about ensuring that it’s backward compatible with non-JavaScript enabled browsers.

Author’s Other Note: I will post the second part to this soon… Probably next week. Cheers.

Posted
 

Comatose 0.8.1

It’s been a long time coming, but it’s finally here: Comatose 0.8.1!

Over the next few days, I’ll be transitioning the project to Google Code and Google Groups please start using them for reporting bugs and such. Here are the important URLs:

I’ve also updated the docs to reflect the new 0.8+ way of doing things. They’ll wind up on the wiki so that they’ll be easily maintained by all.

From the changelog:

  • All includes and helpers are loaded at the end of the ComatoseController and ComatoseAdminController classes
  • Fixed the ComatoseController#show action to correctly send an HTTP status of 404 even if it finds, and renders, a page at ‘/404’
  • Fixed the migration to default a page’s full_path to ” instead of null
  • Formalized ComatoseDrops. Use Comatose.define_drop "drop_name", do ... end. Every method within that block should return a value for use with a Liquid and/or ERB template. Usage in a comatose page: {{ drop_name.def_name }}
  • Added support for a config.after_setup block that gets called after Comatose is setup (in the Dispatcher#to_prepare event)
  • Added HTML comment output for calls that result in a method_missing on the ProcessingContext
  • Updated page tree to remember collapsed/expanded nodes between page visits
  • Fixed some errors that were caused by null revisions (usually happened after initial installation)
  • Added my javascript test_runner lib for testing slugs generated by JavaScript. More JS tests to come.
  • Bugfix #8640 (rubyforge bug)

For more see the devblog

Posted
Theme by Cory Watilo.
More great Posterous themes at themes.posterous.com.