Unobtrusive, yet explicit »
Created at: 02.03.2010 02:15, source: the { buckblogs :here } - Home, tagged: Projects Tips & Tricks javascript rails ujs
A few weeks ago I started a new side project (a string-figure catalog, not yet ready for an audience, sadly), and I figured it would be a good opportunity to dabble in the new goodies in Rails 3. It’s been a fun experience, for the most part, but I’ll save my “wins and fails” for a separate post.
For now, I want to focus on one particular frustration: Unobtrusive Javascript (UJS). In any project of even moderate complexity, I’ve found that Javascript plays a role, and in Rails 2 the primary way to play that game was by inlining your Javascript. (This is where you put Javascript directly into your tags, for instance in “onchange” or “onclick” handlers.)
Apparently this is a Bad Thing, although the only arguments I’ve found against inline Javascript sound suspiciously like “purity for purity’s sake”. At any rate, Rails 3 is embracing UJS, and you’ll find that helper methods like “link_to_function” don’t even exist in Rails 3.
This raises the question: what do you do instead? Well, you have to use UJS. Only, UJS in Rails isn’t super mature yet; there’s a lot of manual labor involved simply trying to work around the absence of “link_to_function”.
So, I set to work. Initially, I tried to copy what rails.js was doing (for Ajax operations, etc.): I installed a handler, and examined the triggering element to see what operations match:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
document.observe("dom:loaded", function() { $(document.body).observe("click", function(event) { var element = event.findElement("a[data-toggle]"); if(element) { var action = element.readAttribute("data-toggle"); element.hide(); element.next().show(); event.stop(); } else { var element = event.element(); if(!element.readAttribute("data-tab")) element = element.up("a[data-tab]") if(element) { selectTab(element.readAttribute("data-tab")); } } }); }); |
I quickly realized that this does not scale, for two reasons. The first is that you quickly wind up with a massive branch statement inside each of your observer functions, with finicky conditions that (hopefully) map to actual elements in your views. The second is that the relationship between your markup and your Javascript is tenuous at best; even coming back to my code just a few days later, I found it was challenging to discover what code was executed when a link was clicked.
This is, I believe, one of the greatest strengths of inline Javascript: the relationship between markup and code is immediately obvious, and it requires very little hunting to follow the path of execution from the inception of an event.
So I went looking at what other options exist. Low Pro, an extension to Prototype for aiding with UJS, looked promising; I liked how each behavior was registered separately, which seemed like it would give a stronger relationship between markup and execution. However, Low Pro uses CSS selectors to identify which markup gets associated with which callbacks, and while this sounds like it ought to be a great idea, it falls down for one really big reason: CSS selectors depend on styling attributes (classes and ids), and trying to tie functionality to those means you are still left staring at markup and wondering where all the events go. Sometimes a class is stylistic, and sometimes it is logical, and there is not generally any clear way to determine which is which.
Now, you could resort to naming conventions: if a class name is prefixed with “behavior-” (or similar), then it refers to a behavior that is defined in Javascript. That’s closer to what I was looking for, so I played with that.
But what I soon discovered was that you wind up with a bunch of CSS classes that are not used for styling at all, because they specifically refer to dynamic behaviors. What I really wanted was an altogether different attribute for specifying behaviors, like what “onchange” and “onclick” gave me before. Only I had to beware upsetting the Manifold Avatars of UJS Purity by embedding actual Javascript.
What I finally ended up doing (I’ll say I “stumbled on it”, rather than “invented it”, since I’m positive it’s been done before) was defining a “data-behaviors” attribute on every element that needed one:
<%= link_to "Add an alias", "#", "data-behaviors" => "add-alias" %> |
Then, in my Javascript driver, I registered callbacks for those named behaviors:
1 2 3 |
Behaviors.add("click", "add-alias", function(element) { // ... }); |
The result is UJS that clearly reveals the relationship between the markup and the code; you can easily search for all elements in the views that behave like “add-alias”, for instance, and given a behavior name (like “add-alias”), you can quickly find the code that gets executed for it. Elements can have multiple behaviors, too: just give a space-delimited list of behavior names in the “data-behaviors” attribute.
It’s not perfect, though: the current implementation doesn’t deal well with elements that want to behave like X on “click”, but Y on “change”. That’s not a scenario I’ve needed to deal with yet, though, so I’m sure when (if?) it comes up, a solution can be found. In the meantime, I’m quite pleased with this. It “clicks”, whereas other UJS solutions just felt obscure and heavy-weight.
Below is the code for behaviors.js. Please feel free to fork the gist on Github and hack away; I’m sure it can be improved upon in lots of ways.
Enjoy!
Update: Josh Peek suggested some tips that resulted in a drastic simplification of behaviors.js. It’s simple enough now that there’s almost no point in providing it as a separate library!
more »
Vim Follow-up »
Created at: 17.11.2008 21:09, source: the { buckblogs :here } - Home, tagged: Tips & Tricks vim
So, it’s been over a month and a half since I switched back to Vim, and I figured I’d post a bit about how things are going.
I love it. Though the future is notoriously difficult to foretell, I think it’s safe to say that I won’t be switching editors again anytime soon. Vim is where it’s at, for me.
Here’s the combination of plugins and such that I’ve found work best for me.
MacVim FTW
Any Vim will do, really, but if you’re on a Mac, you really ought to consider MacVim. Like any self-respecting Vim port, it does the command-line really well, but it also has a great OS X GUI interface.
Settings
I’ve got my <Leader> character (:h mapleader) mapped to the comma (since it’s easier to reach than the backspace character).
let mapleader = "," |
I’ve installed ack and have configured Vim to use ack for grep.
1 2 |
set grepprg=ack set grepformat=%f:%l:%m |
I’m a believer in spaces instead of tabs (let’s just agree not to go there), and I prefer a tab size of 2. I also like editors to try and guess the indentation level.
1 2 3 4 5 6 |
set tabstop=2 set smarttab set shiftwidth=2 set autoindent set expandtab set backspace=start,indent |
But, some file types really do require explicit tabs, and not spaces:
1 2 |
autocmd FileType make set noexpandtab autocmd FileType python set noexpandtab |
(Update: apparently, python can do tabs or spaces. All my python vimming for the last 8 years has been read-only, so it never actually came up.)
I like my lines numbered, syntax highlighting on, and highlighted searches:
1 2 3 |
set number set hlsearch syntax on |
I’ve got quite a few other things tweaked in my .vimrc, but those are the major biggest ones.
Plugins
FuzzyFinder
The FuzzyFinder and FuzzyFinder TextMate plugins have become essential for me. For now, installation of the TextMate-like behavior is kind of painful, but I plan to get something up on the Vim scripts index in the nearish future.
My settings for FuzzyFinder TextMate:
1 2 3 4 5 |
let g:fuzzy_ignore = "*.log" let g:fuzzy_matching_limit = 70 map <leader>t :FuzzyFinderTextMate<CR> map <leader>b :FuzzyFinderBuffer<CR> |
NERD_tree
Thanks to all who recommended the NERD tree plugin by Marty Grenfell. It really is fantastic, definitely the best-of-breed of VIM project/directory explorers. I especially like that it is easily toggled away. I usually keep it hidden, and toggle it open only when I need to browse to something. (Thanks to the fuzzyfinder stuff, my need for a project browser is pretty small, but when I need one, NERD_tree works great.)
1 |
map <leader>d :execute 'NERDTreeToggle ' . getcwd()<CR> |
rails.vim
The rails.vim plugin is pretty extensive, and I’m currently only scratching the surface. I don’t use snippets at all (never did in TextMate, either), but the Rake integration is pretty handy, and I’ve used the migration generator pretty often lately.
I need to spend some more time reading the docs for this one, and practicing some of the commands, since I’m sure it could turn into a real time-saver for programmers (like myself) who spend a good part of their day in Rails code.
scratch.vim
I’ve really come to love scratch.vim. Sometimes I just need to jot down some numbers, or paste the result of some query, or even take notes on a phone call. Writing any of that on a scrap of paper is a sure way to lose the info. The scratch plugin lets me take notes right where I am all day: in Vim. It doesn’t come with a way to toggle the scratch buffer (which is odd), so I wrote a quick one:
1 2 3 4 5 6 7 8 9 |
function! ToggleScratch()
if expand('%') == g:ScratchBufferName
quit
else
Sscratch
endif
endfunction
map <leader>s :call ToggleScratch()<CR>
|
tComment
Maybe it’s just my own coding style, but I find myself commenting and uncommenting large blocks of code on a daily basis. The tComment plugin from Tom Link is perfect for this. You can toggle comments by using the Vim motion operators, or just do a quick visual block followed by “gc”. Good stuff!
Others
There are a few other plugins I’m experimenting with (surround.vim, vcscommand.vim), but which I use infrequently enough that they haven’t become muscle memory yet.
Workflow
Because I prefer the GUI version of MacVim, I typically have only one or two terminal windows open. I then type “mvim” to open vim from the root of whichever project(s) I’m working on, and then use ”,t” (my FuzzyFinder TextMate trigger) to search for and open files. I also use ”,b” (my FuzzyFinderBuffer trigger) to search my buffer list if I know the file is already open.
Switching between buffers with the carat character ”^” is a huge time saver. I used to use :ls (to list open buffers) and :buffer (to jump to a buffer), but the FuzzyFinderBuffer has really taken the place of both of those.
Ironically, split windows (which I missed most of all when I switched to TextMate) have taken me the longest to fit back into my workflow. TextMate trained them out of me. :) That said, I’m still trying to fit them back in, and when I remember to use them, I love them.
You?
So, that’s me. How does Vim fit in your own workflow? What settings do you prefer, and why? And what customizations are you using in your own setup?
more »
