Showing posts with label Web. Show all posts
Showing posts with label Web. Show all posts

Monday, July 22, 2013

Local Reactivity in Meteor Web Applications

Every once in a while I stumble into something that changes the universe for me, allowing me rethink how I approach problems and develop solutions. Stumble because, while I can't necessarily plan on things like this happening, if I try to listen to people and keep my eyes open, I figure I'll eventually get lucky. When Meteor buzzed in a Cincinnati Javascript User Group meeting this Spring, I was happily surprised. And now just a few months later, it's become my new go-to web application framework.

Rather than try to introduce you to Meteor myself, I'll simply defer to the meteor web site www.meteor.com and let them do the talking. The site includes a high-level description of the framework, detailed documentation, example code and links to videos. The quality of the description and documentation alone merit your attention - and the capability of Meteor deserves your respect. This stuff is brilliant.

Of all the things Meteor brings to the table, Reactivity is the game changer for me. Updates to data sources cause dependent computations to be re-run, and the application shows updates immediately. The burden of carefully choreographing a dance between the server and the client to display pleasing changes in a timely way has been lifted. The dance is already beautiful and the performers are well-rehearsed. I can focus on building great user experience rather than on things like scratching my head over problems with data structures or working the weird bugs out of ajax callbacks.

Recently, when developing a web app I found myself wanting to use this strategy locally as well, building reactivity to be used only within a client. Though Meteor gives you session-level reactivity that propagates changes made to session variables locally, this wasn't quite what I wanted. Session variables are global to the client; I wanted variables that were at least scoped so I could keep them more closely associated with the functions and objects that use them. I've been conditioned to believe that global variables smell bad and I'm not ready to give up that sense yet.

Instead, I opted to adapt a notion that was mentioned in the Meteor documentation. I associated a variable and a dependency in an object and called it an injective. The idea is that I can inject client values into the reactive flow. In Coffeescript,

    Deps.injective = (init, options) ->
        _value: init ? 0
        _dep: new Deps.Dependency
        _force: !!(options && options.force) ? false
        set: (value) ->
            if (@_value != value) || @_force
                @_value = value
                @changed()
            @
        get: ->
            @depend()
            @_value
        depend: ->
            @_dep.depend()
            @
        changed: ->
            @_dep.changed()
            @
        force: (f) ->
            @_force = !!f
            @

The internal _value and _dep are managed through the get and set functions. Calling get returns the value while making the function asking for it dependent on the injective. Updating a value using set re-computes all functions that depend upon it.

A quick example perhaps, again in Coffeescript.

    App.innerWidth = Deps.injective window.innerWidth
    $(window).resize -> App.innerWidth.set window.innerWidth

This code creates an injective that tracks the inner width of the browser window. When the window resizes, the injective's value is set to the new inner width. While this might not sound like much, anything that has been computed based on the getting the value of innerwidth will be automatically recomputed when it changes. In the context of this example, that means that you don't have to know about the 23 layout decisions you made based on this value and how they'll need to propagate, and then manage it all yourself. Instead, you just grab the corner of the window and resize it, and watch the magic happen.

The depend and changed functions provide access to the dependency mechanism and are used by get and set. When depend is called, the injective adds the calling function to its list of dependents. An update to the injective using set will re-run these dependent functions. The changed function is what triggers the dependents to be re-run.

Finally, force is a way to force changes to objects and arrays values to be propagated without having to write any special comparitors. When the value being set is a scalar, == works: if the value is different it is copied and changed is called. If the value is an object (or an array) and the change is made within the object itself, == won't report the object has changed; a more complex comparison must be written to do the test. But that really isn't needed if you know about the complexity already. The force function (or constructor argument) provides a simple shortcut: if _force has been set to true then the set function trusts that the incoming value is different; set just goes ahead and updates the value and propagates the change. If something more complex is needed, values in the object can be adjusted and the injective's changed function can be called directly.

I've submitted injective (a Javascript version) to the meteor github repository. Maybe they'll integrate it, maybe not; I wasn't fixing a bug, just adding an enhancement so it doesn't have any sort of priority. However, it's a clean way to inject values into the reactivity stream that's useful for me. If it looks like it may be useful for you too, then enjoy!

Monday, February 21, 2011

Laissez les bons temps rouler!

Starting on the day before Mardi Gras, I will be joining EdgeCase working in their Cincinnati office. To me, a much bigger party than they'll be having in the French Quarter!

This is a phenomenal opportunity, working alongside the genius of Jim Weirich and Scott Barron every day, and collocated with the Gaslight Software crew. (Let me tell you, this is a really smart room.)

Many thanks to Joe, Ken, Adam and Leon for taking the time to meet with me and make sure I'd fit there, and to everyone who's supporting me in this transition. To be sure, I'll miss the folks I've been working with, but I'm ready to get cracking on new stuff!

Wednesday, October 15, 2008

Enhancements to Railroad

A picture is worth a thousand words, and is certainly easier to talk about than reading a bunch of code. At least for me.

On a recent Rails project two dozen models and sixty associations were needed to drive the application. A medium sized effort. However, it's tough to talk users through the complexity when the need arises, even when you're just dealing with a small chunk. After drawing circles and arrows on whiteboards too many times, I decided to mechanize.

I'd used Railroad (a great rubygem written by Javier Smaldone) a few times before to document smaller projects. It loads models and controllers from a rails project and renders everything in 'dot' format that can be processed by graphviz, an open source graph rendering framework. I railroaded the app and quickly had diagrams in hand. Happiness all around.

But there were a few things I noticed as I talked around the pictures. I needed colors. And labels. And fewer circles and arrows. I decided to take a dive into the railroad code and add some features.

What I eventually ended up with was a considerable set of changes.

Changes to Allow Subgraphing

Often I didn't want to see the whole graph at once. What I wanted was the ability to include only the models I listed, or exclude a set of models from the whole. After implementing this, I decided I also wanted the ability to focus on a set of nodes, including them and any other directly-connected nodes.

  • I added support to only include model classes in a diagram by name specifying -I class1[,classN] on the command line.
  • I added support to exclude model classes from a diagram by name using -E class1[,classN].
  • I added the capability to focus on a set of model classes. By specifying -F class1[,classN] on the command line, only these nodes, the nodes they connect to, and the associations between them are displayed. However, this is also subject to any additional model class exclusions (-E) or inclusions (-I) that are specified.

  • By using -F, -E and -I in combination, a logical subset of the model can be displayed fairly easily.

    Model Node Content Display Changes

    I found that I sometimes needed more or less information in a displayed node. Brief mode was already present (for just displaying the class name in a node) but I wanted a little more content control.

  • I changed the display of magic fields in model nodes to be off by default. Before, you'd hide the magic fields by specifying --hide-magic on the command line. I added a --show-magic option to turn them on instead.
  • I changed the display of association fields in the node to be off by default, shown only by specifying --show-assoc-fields on the command line, and label them with the name of the associated model.
  • I added a -B option to display all nodes as brief except focused nodes (those specified with -F).

  • Using -B with -F turned out to be a great way to present information. I could see what was being focused upon in context, without extraneous detail.

    Changes to Association Display

    I found I needed to be able to show different associations differently. Conventional Rails associations were fine, left black and unlabeled. But polymorphic associations, many-to-many through relationships, and unconventionally named associations needed different labels and colors. By default, I decided to always display these and provide methods to hide them.

  • Unconventional associations and their labels are blue; navy blue if only one side of the relationship is unconventional. The labels are hidden by specifying --hide-uaslab on the command line.
  • Polymorphic associations and labels are red. The labels are hidden with --hide-paslab.
  • Through associations and labels are dark green. The labels are hidden with --hide-taslab.
  • For convenience, all association labels may be hidden with --hide-aslab.

  • Multiple Diagrams at Once

    Once the rest was done and I'd used it all for a while, I decided that automatically generating a focused diagram for each model would save me a lot of time.

  • I added support to create a focused diagram for each model class in its own dot file using -O FILE. The output file name for each model is created as FILE.dot, and FILE may include directory separators.

  • A simple rake task completed the automation:
    @railroad_command = Config::CONFIG["target_vendor"] == 'pc' ?
    'railroad.bat' : 'railroad'
    task :graphs do
    FileUtils.mkdir_p 'graphs'
    `#{@railroad_command} -M -b -o graphs/_overview.dot`
    `#{@railroad_command} -M -B -O graphs/`
    FileUtils.cd 'graphs' do
    FileList['*.dot'].each do |f|
    `dot -Tpng #{f} -o #{f.gsub(/dot$/,"png")}`
    end
    end
    end
    Finally, I refactored the completed code, DRYing out the iterative changes I'd made.

    What I now have is a nice, simple way to produce diagrams of the database and associations between tables for discussion and documentation. I submitted the patch to Javier and hopefully it will be integrated into the railroad trunk fairly soon. You can grab railroad-0.5.0 and the patch and play with it if you want by downloading it from rubyforge.

    Tuesday, September 23, 2008

    Installing Rails Plugins from Local Sources

    I'm usually reluctant to go change "big things", almost always prefering to work around any issues I encounter. On the rare occaision though, I find I really must make changes or I'd forget what I did and struggle with it again later.

    The script/plugin install command in Rails is a wonderful thing. It fetches code from repositories (like subversion or github) and installs it into your Rails application. Plugins generally extend Rails' capabilities, much in the way that Ruby is extended through Rubygems. However, the plugin installer assumes a repository, or at least the web. If there is a local copy of the plugin in your environment that you'd like to use as the source, you're out of luck. You have to do the install by hand, and remember to set up some environment specification. There's just no provision for local installs.

    Necessity being the mother of invention, when I found myself needing to install a plugin from local sources, I tried to find a workaround. When I could find nothing satisfying, I did some editing. A very slight amount of editing. In ruby/lib/ruby/gems/1.8/gems/rails-2.1.0/lib/commands/plugin.rb

    class Plugin
      def file_url?
    @uri =~ /^\//
    end
      def install(method=nil, options = {})
    method ||= rails_env.best_install_method?
    if :http == method
    method = :export if svn_url?
    method = :clone if git_url?
    method = :file if file_url?
    end

    uninstall if installed? and options[:force]

    unless installed?
    send("install_using_#{method}", options)
    run_install_hook
    else
    puts "already installed: #{name} (#{uri}). pass --force to reinstall"
    end
    end
      private
        def install_using_file(options = {})
    root = rails_env.root
    mkdir_p "#{root}/vendor/plugins"
    Dir.chdir "#{root}/vendor/plugins" do
    cp_r @uri, @name
    end
    end
    end
    That is, if the specified plugin being installed starts with a slash, assume it's an absolute path to the directory that contains the plugin and fetch it by copying. Then the install can go along it's merry way.

    Now I can just fire off a
       ruby script/plugin install /Downloads/rails/plugins/foo
    and it will install the foo plugin in the specified directory into my Rails project, just as if it were out on the net.

    This certainly may not be perfect, and if I were polishing it I'd add a check for a "file:" protocol and include relative paths, but this was just enough for my needs.

    Friday, August 15, 2008

    Rails, Prototype, Ajax and Forms Walk into a Bar...

    Rails is my friend. Prototype is my friend's foreign friend. Ajax is my child-prodigy friend. Forms is my long-time blue-collar friend. Submit buttons are pieces of currency, among many other Internet coins and bills. Recently my friends and I got together at the local web bar to have a beer.

    Rails, Prototype and Ajax had been hanging out together lately, and while it seems they knew Forms fairly well, he was kind of old. The other three were younger and getting rich, and so they'd started paying for everyone's drinks. Forms had been buying me beer for a long time, but hadn't had to pull out Submit Buttons very often in their presence since we met the other three. The other day, I asked Forms for a few Submit Buttons to get a beer, but Rails, Prototype and Ajax said they'd take care of it. Forms smiled and said "Fine. Thanks."

    I took the Submit Buttons up to the web's bartender, and to my surprise he said they were no good! They were skilled forgeries! They looked like the real thing, but the serial numbers were all the same!

    I went back to the table and told my friends. It turns out Rails had suspected they might not be good enough to pass before a scrupulous eye for a few years. Apparently Prototype had been giving them to Rails to help Ajax for a while. Forms pulled out a real Submit Button so I could hold it and the forgery up to the light and compare them.

    The deal is that if you use Rails to generate multiple submit buttons on a remote form (ie. for Ajax), regardless of which button is clicked, only the value of the first button is set in the parameters coming into the controller. The issue was documented by Ticket #5031 on Rails-Trac over two years ago. While there was a partial fix put into prototype in 2007, it didn't go far enough to fix the problem.

    I played with the prototype code a bit and roughed in a solution, but had an older Firefox on the machine I was using that was incompatible with the latest Firebug. My tests passed, but I was sure they weren't exhaustive. Since I couldn't see what was getting generated under the covers, I didn't trust it enough to use my fix and went back to the web to see what others had done.

    I found the solution that I decided to use on Harry's Blog, basically to tuck the value of the submit button that was pressed on an Ajax form into a hidden field. This is done by assigning the onclick of each submit button to some field-setting javascript code. Since this was essentially what I was doing under the covers in the prototype, I was happy.


    I sat down and changed the Submit Buttons Rails had given me out of the view of the web bartender. I then gave them to our server who brought me back a nice tall beer. Good times ensued.

    I haven't done any wholesale changing on either of my friends Rails' or Prototype's Submit Buttons yet and I hope to avoid it, preferring getting together with my friends in the more comfortable setting of my neighborhood web bar instead of submitting my changes to official scrutiny. For now I'll keep fixing the Submit Buttons locally and just let the officials get to it in their own time.

    Time for another beer, eh guys?

    Thursday, May 08, 2008

    Opening a URL in a Viewer from Ruby

    While web apps are all the rage, what with the browser being ubiquitous and everything, there are still some features that don't scale - usually stuff that requires a lot of high-bandwidth data getting manipulated and displayed. Sorry, but interactive data-heavy apps just don't yet run well enough on the web to make the jump. For other things, yes; but not for these.

    Unfortunately, applications based on these sorts of features are the ones I've spent a lot of time writing. They want to be web-enabled, not web-based. It's one thing to use the web as a communications medium, but another to use it as an interaction medium.

    So, I got Lyle Johnson's book (FXRuby: Create Lean and Mean GUIs with Ruby) I've been messing with FXRuby lately. Quite a nice little package. I'm moving some big-application stuff into it and should have more to say about it in the upcoming weeks and months. One of the parts I just moved in deals with opening html documents and URLs. A long time ago I decided to write all of my help documents in html and use the browser for displaying them. When the user clicks a help button in an app (or something like that) I need to bring up the requested page in a browser.

    Opening a URL in a browser seems like it should be almost trivial, but from a Ruby app there is a little art to it... Different vendors have different mechanisms to do it, and you have to make it work right so the users of your apps don't get flustered. So I hide the vendor-specific logic under a general method:
    def open url
    send "open_#{Config::CONFIG['target_vendor']}".to_sym, url
    end
    This dispatcher will call the open for the specific vendor. I make the separation here, so I can handle each vendor cleanly no matter what sort of crazy stuff they may be doing.

    Some vendors make it easy. Take Apple, for instance:
    def open_apple url
    system "open #{url}"
    end
    Apple's made opening a file in your viewer of choice a basic function of the OS. By hiding the details, it's less work and higher productivity for me.

    In Windows it takes more effort. Because the viewer of choice is buried in the registry, I have to dig it out:
    require 'win32/registry'
    def windows_browser
    Win32::Registry::HKEY_CLASSES_ROOT.
    open('htmlfile\shell\open\command') { |reg|
    reg_type, reg_value = reg.read('')
    return reg_value
    }
    end
    Now I can do the open:
    def open_pc url
    system "#{windows_browser} #{url}"
    end
    On other systems (say Linux, for instance) there's no direct solution. Because the viewer-of-choice is buried in desktop preferences that may be different for different window managers, there's no definitive way to know what it is and how to pull it out. So I opt for flexibility - I depend on an outsider to inject the name of the URL viewer into the mechanism prior to opening the url.
    attr_accessor :url_viewer
    def method_missing(method,*args)
    if method.to_s =~ /^open_/
    if @url_viewer
    system "#{@url_viewer} #{*args}"
    else
    raise DocumentException,
    "no URL Viewer was designated to open the URL."
    end
    end
    end
    This isn't enough, however. The Kernel's system method will block until the application opening the URL returns. I don't want the application to stop and wait! In order for the app to stay in control, the open has to run in it's own thread.
    def open_document url
    Thread.new {
    send "open_#{Config::CONFIG['target_vendor']}".to_sym, url
    }
    end
    But there's a problem. I'd like to catch the raised exceptions from the dispatched opens if something unexpected happens - but since the exceptions come from a new thread that I'm not waiting for, they'll just go into the aether. I need to do this using another mechanism. So instead of raising, I'll hypothecate an exception handling mechanism that the thread can use to notify the app that there was a problem during the open.
    attr_accessor :exception_handler
    def open_document url
    Thread.new {
    begin
    send "open_#{Config::CONFIG['target_vendor']}".to_sym, url
    rescue Exception => exception
    @exception_handler.handle exception
    end
    }
    end
    and we'll let the caller pre-designate the exception handler. Though this isn't quite as nice as rescuing in the caller, I'm not as concerned since the limited set of things that can fail when opening a url really come down to configuration issues or the absence of the URL's target.

    While this will open a URL in a viewer from a Ruby app, there's a little more work needed. I want to ensure the URL is properly-formed enough not to choke the viewer. I'll do this by normalizing before I do the open.
    def open_document url
    Thread.new {
    begin
    send "open_#{Config::CONFIG['target_vendor']}".to_sym,
    normalize(url)
    rescue Exception => exception
    @exception_handler.handle exception
    end
    }
    end
    The normalizing is just a bit funky, but trivial in concept - just return a String containing the normalized URL. My top-level logic is: if it's a file resource, then normalize it as a file; otherwise, validate it as a URI. Since Ruby already comes with a URI class, I just use it.
    @@using_pc_filesystem =
    Config::CONFIG['target_vendor'] == "pc"
    def normalize(url)
    (file_url? url) ? nomalize_file(url) : URI.parse(url).to_s
    end
    def file_url?
    (url =~ /^file:/) or
    (url =~ /^\//) or
    ((url =~ /^[A-Za-z]:/) and @@using_pc_filesystem) or
    !(url =~ /:/)
    end
    It's a URL file resource if it starts with file:, a slash or a drive designator (on a pc) or it doesn't have a colon in it.

    Normalizing a file amounts to giving back the normalized file name with file:// prepended to it.
    def normalize_file file_url
    path = normalize_file_path(
    (file_url =~ /file:\/\//) ? $' : file_url)
    "file://#{path}"
    end
    def normalize_file_path file_url
    if absolute_file_path? file_url
    file_url
    elsif @relative_base != nil
    "#{relative_base}#{file_url}"
    else
    raise UrlException, "no relative file base was configured"
    end
    end
    A file path is absolute if it starts with a drive designator and it's on a PC, or a slash (with no drive designator) if it isn't.
    def absolute_file_path? file_url
    @@using_pc_filesystem ? (file_url =~ /^[A-Za-z]:\//) :
    (!(file_url =~ /^[A-Za-z]:/) and (file_url =~ /^\//))
    end
    Finaly, a relative base is prepended to relative file paths. It fits between a drive designator and the relative path or a PC, or otherwise just sits at the front of the path. When I assign it, I make sure it looks like it'll work.
    def relative_base=(relative_base)
    if valid_relative_base? relative_base
    @relative_base = relative_base
    else
    raise UrlException, "Invalid relative base '#{relative_base}'
    end
    end
    def valid_relative_base? relative_base
    ((@@using_pc_filesystem and (relative_base =~ /^[A-Za-z]:\//)) or
    (!@@using_pc_filesystem and (relative_base =~ /^\//))) and
    (relative_base =~ /\/$/)
    end
    This wraps everything up nicely. Nice enough that I wrapped it up into a gem I can pull into any of my apps. I added it to my cori project (Chunks Of Ruby Infrastructure) on rubyforge in the eymiha_url rubygem.

    Having done this once, I can now get on with the meat of writing my interactive-but-data-heavy Ruby applications, waiting for enough bandwidth on the Internet to someday move them to the web.

    Wednesday, September 20, 2006

    Growing Pains and Browsers

    Why can't the browser writers make html and the DOM behave according to a standard? By now you'd expect standardization, but no, they apparently still don't get it. Just this morning I've butted my head against more three differences between IE and Firefox!

    1 - Table Cells. IE formats table cells painfully. After an input text field, a break pushes a large vertical space while Firefox doesn't. To try to solve this, if you instead put the broken text in the following row, in IE the row height isn't based on size of the text in the cells while it is in Firefox.

    2 - Page caching, Ajax and Radio buttons. If you update a text field on a page using Ajax in Firefox and reload it, and if the page contains radio buttons, their state is switched - you can get it right by doing a shift-reload. IE doesn't mangle the radio button state.

    3 - Input text fields and focus events. In Firefox, entering a return key will cause a focus change, while in IE it doesn't.

    Some of this can be solved through extra coding, while some can't. Perhaps I ought to be less critical, but it just seems like things should be a lot cleaner and just work consistently, independent of the browser choice. It just seems to me that it shouldn't be that hard to get right.

    It begs the question of whether such a behavior standard even exists. Maybe it's time for the web development industry to form a browser certification group to create and push such a standard, and evaluate different browsers. Even if it had no real clout, it'd be nice to have a list of inconsistencies for each browser, and possibly a recognized set of workarounds to get the rest of the way.

    I hate whining, but I can't be the only one feeling this pain.

    Tuesday, September 19, 2006

    Establishing Session Context

    I'm a big fan of context sensitivity. I'm also a big fan of Rails. Recently the two topics crossed paths in the guise of contextual overlap in the session of a large Rails application I've been building, and I decided to get rid of the headache. I've built some code to establish disjoint contexts in the session, transparent to normal Rails development.

    Feel free to take a look and comment...

    Tuesday, July 25, 2006

    Tipping Points and Big Adventures

    In Malcolm Gladwell's year-2000 neo-classic, "The Tipping Point: How Little Things Can Make a Big Difference", the phenomenon of epidemics is explored. He explains how small events catalyze sweeping changes and discusses them in many different contexts.

    Many of us have been anticipating the Ruby and Rails tipping point to be close at hand. It is clear to me that this is happening now. The growth of use of the language and framework, the coverage in the press, new books appearing on the market, sell-out conference attendances - all are indicative that the knee in the curve has been reached.

    But different epidemics have different quantitative characteristics. There are many dimensions in which the epidemic manifold may lie, and the shape of the curving surface depends on many factors - what's important is unique to each epidemic. Gladwell helps his readers understand what tipping points are and why they occur, but doesn't give us ways to recognize what the small events were that enabled them except in retrospect.

    It is much easier to analyze a historical epidemic than measure an occurring one. Take the Asian bird flu and i-Pod as examples. Will bird flu be the explosive pandemic that the health officials have predicted? Has the knee-phase in the i-Pod curve been completely passed? How can such things be measured when it's not clear exactly which measurements are appropriate? I suppose that in absence of prescience, all we have is gut feels, educated guessing, and luck.

    The closet futurist in me says to the intrepid software developer in me that the Ruby and Rails epidemic is hitting. The wily entrepreneur in me is overhearing the conversation and wondering about timing, asking the mysterious gypsy in me to do a little gazing into the crystal ball. As I stand at the edge on the hill and look up to see the peak of the summit, I wonder which route will get me to the top and let me enjoy the best sights along the way. It is joyous to live in such fun, turbulent times.

    Thursday, June 08, 2006

    Bad Magic

    One of my companies uses an ISP a few time zones away. I created all of the pages for our site, while the business guys set up the e-commerce portion of the site, all done about three or four years ago. It was a good value at the time. But this weekend the ISP had a massive crash that supposedly corrupted several of their servers. Of course, it hit our site and took it offline.

    Besides being down for a few days now, our e-commerce data was wiped. You'd think that the ISP would have a failover and backup and would be working hard to restore everything. Not so. I spent a few hours uploading the pages and putting things back into place, but the e-commerce area was not something I had worked with... the ISP didn't provide access to it. Hell, it took me a day to get them to even put a placeholder up so I could start repairing things.

    So now the business guys are scambling, trying to recover the rest of the site. I'm away for the weekend to go see my son graduate from high school, so I can't help. They're on their own. The fact that the business guys see this all as magic scares the crap out of me. They just hadn't ever really considered what was going on behind the curtain. Nobody's fault... the magic just went bad.

    It just galls me that in an age of technology, disaster recovery has been so ill-conceived. Lesson learned: don't let those that are mystified by technology make important technology choices. I'd laugh, but it's all just too sad.

    Sunday, February 05, 2006

    Firefox and IE, and Rails Pain

    I'm doing some Ajax in Rails and I found some more weirdness in the behavioral differences between Firefox and IE. I've abstracted a few snippets of code that should illustrate the issue...

    I have a form that contains a table holding a cell with a select that fires some ajax on a change, and the input field that is the target of the ajax change in another cell. I know that I could do this differently, and clean the code up a bit too, but there's no reason this shouldn't work in my little chunk of web. For the sake of this example, I've got a Foo and a Bar. Both have a cost field. When editing a Foo, you can select a Bar by name it's cost is put into the Foo's cost in the edit.

    in foo/_form.rhtml I have:

    <table>
    ...
    <tr><td>Bar<td>
    <%=
    @bars = [["Choose Bar"]]+
    Bar.find(:all).map {b [b.name, b.id]}
    select(:foo, :bar_id, @bars, {},
    {:onchange => "new Ajax.Updater('foo_cost',
    '/foo/bar_changed/'+
    this[this.selectedIndex].value,
    {asynchronous:true,
    evalScripts:true});"})
    %>
    ...
    <tr><td>Cost<td>
    <div id=foo_cost><%=
    render :partial => 'cost' %></div>
    ...
    </table>

    in foo/fooController.rb I have:

    def bar_changed
    @foo =
    begin
    Foo.find(params[:id])
    rescue
    Foo.new
    end
    @foo.cost = Bar.find(params[:id]).cost
    render(:partial => 'cost')
    end

    and in views/foo/_cost.rhtml I have:

    <%= text_field 'foo', 'cost',
    'value' => @foo.cost.to_money %>

    Ok now, I have to admit that I got this working over lunch at the day job using <gasp> IE 6. But when I brought it home to dig into in Firefox, instead of the Ajax updating the chunk of stuff in the <div id=foo_cost>, the input field itself was extended - because it too had an id of foo_cost. Firefox can apparently replace inside anything that has an id, where in this case (at least) IE just does a div replacement. This appeared as a field within a field (within a field, etc.) every time I selected a new Bar.

    The quick fix, of course, is to rename the id of the div. No biggie. But it did catch me unawares, and made me scratch my head a little. In general, I prefer more choices than less, and Firefox gives me that little extra. But it also means I have to be careful I don't get id space overlaps. Your mileage may vary.

    Monday, January 09, 2006

    Web Apps and Native Apps

    A web app is not a native app. I know this in my head, but in my heart I want them to be the same. Rails is ever so nice. The deeper I go, the cleaner it seems - the thin client attitude just feels so right, especially when Ruby and Rails make it so easy. This is the way I should be doing all my app development.

    And yet, a web app is not a native app. The controls aren't the same. The browser isn't as dynamic (even with Ajax). The bandwith is limited. I don't get the clean and perfect app I envision... But wait - is this attitude just the older hacker dog coming out in me instead of the younger software pup? Consound it, Tom, I'm not too old to change! Perhaps I should get with the program, do the dew, and ride the wave - compromise my meticulous look-and-feel sensibilities for the ease and convenience of the quick new paradigm. Yeah.

    So, a web app is not a native app. Big deal. Today productivity really does reign. I don't want to spend the time I used to putting together apps - I have more need than ever to get more done more quickly. I'll do what I need to to make things work, and make them look more native and pretty when it's important or quick or easy - but I'll make things work first. I'm not caving in, I'm just becoming more pragmatic. I still know I can do it when I need to - but it's time for me to happily embrace another change in my thinking.

    A web app is not a native app. Carpe webum!

    Thursday, December 01, 2005

    Where did the Time Go?

    Time is the devourer of all things. - Ovid

    It's amazing. This week in an email to a friend I made a quick catalog of all the things going on in my life. After I finished the note, I read it again and saw I was buring the candle at both ends, in the middle, and learned the curtains have also caught fire.

    As a present to myself this Christmas, I've decided that it's about time to write myself a time management application. Something small and sweet. It'll actually have a lot in common with a project planning and management application and calendaring system, with some time tracking, note capturing, and alarms and notification mechanisms thrown in. I'm thinking web-based using Rails so I can get at it from everywhere. And hopefully AJAX out the wazoo.

    It'd be nice to live with a little bit more focus. Of course, I can only hope that'll happen; I've always tended cast structure to the wind in my personal life.