Tuesday, June 7, 2011

Styling flash notices in Rails

So I've made some pretty good progress with Chorenivore the last couple of days. Last night I dug into CSS to clean up the view, mostly making sure that my <:th>'s and my <td>'s were lined up. It's kind of hard not to go nuts obsessing over padding and font size and palette choices. Save that for later, dummy! Work on your functionality first.

So sure, I have some questions.

Here's some code from my Tasks controller in Chorenivore:

respond_to do |format|
  if @task.update_attributes(params[:task])
    format.html { redirect_to(tasks_path, :notice => 'Task was successfully updated.') }
    format.xml { head :ok }
  else
    format.html { render :action => "edit" }
    format.xml { render :xml => @task.errors, :status => :unprocessable_entity }
  end
end


The problem is whenever a Task is updated (or created, or destroyed, etc) the :notice hangs around until the page is reloaded. I'd like to be able to make it disappear after a couple of seconds and style it using CSS. Back when I was working through Agile Web Development with Rails, I created a way to make this notice fade away after a few seconds using Ajax. The problem is, I haven't been able to duplicate it in Chorenivore.

A few questions about this:

First, is this :notice the same between methods? Is the :notice for creating a Task the same :notice for editing a Task? I'm sure there's a way to make this uniform.

Second, how do I do this? Is Ajax the best way? Should I use Javascript? I don't think reloading the entire page is the best way to handle this, and to my understanding Ajax doesn't reload the entire page.

I've seen a lot of references to flash[notice], is this the same thing as :notice, from the code I posted? Also, what does "flash" refer to in this instance? Does it mean Rails is FLASHING a notice, or that it's a notice Rails stores in the flash because it doesn't need to save it after it displays it? Could it be both? Or something else entirely?

Any recommended reading for understanding and working with Ajax/Javascript in Rails? I've read through the API and it's good for reference, but tutorials are my main jam. I'll look around for some on my own as well, but I figured some of you guys would know of some special favorites.

It's also becoming increasingly clear the time is right for me to go back to Agile Web Development with Rails, since I'm definitely thinking about things tutorials are having me do in a different way. Now it's more "What can I do with this?" and less "Am I getting the syntax exactly right so Rails won't complain and barf up a string of errors?"

One last thing: I've been really into Github, and while I feel I'm not using it as efficiently as I could, I really like being able to document and store my code as it progresses. I've been uploading new versions of Chorenivore every time I solve a problem or make an update. I don't know if that's how most Rails programmers use it, but it definitely adds to my feeling of accomplishment once I run that git push command.

9 comments:

  1. The :notice option is just a way for placing the message in the flash, and is the same as issuing 'flash[:notice] = "blahblahblah"'. The flash holds onto things that you need in the next request in the session but don't need persisted across the entire session (so perfect for notices, error messages, etc.). Basically, you set the flash, it's available next request, then it gets deleted.

    I'd use JavaScript to fade the notice after a certain period of time. I haven't done much JS coding, but the following jQuery worked fine for me for fading out the flash section after 3 seconds:

    setTimeout(function() {
    $('#flash').fadeOut('slow');}, 3000
    );

    As for Github, try to commit code in chunks that correspond to adding a feature or fixing something, or reformatting, or upgrading. Keep the changes grouped logically so that if you need to remove something it can be backed out easily. Also, it will make reading through the commit history much nicer, and make it easier to grasp the intent of the code within each commit.

    ReplyDelete
  2. Ajax is only for when you need to make a request to the server to get or find out something.

    All you need is a little bit of javascript to cure what ails you.
    This should do nicely if you use Prototype:

    document.observe('dom:loaded'), function(){

    Effect.fade( $('flash_notice'), {
    duration: 1.5,
    afterFinish: function(){
    $('flash_notice').remove()
    }
    })

    })

    Throw that in application.js and it should always run against flash notices. If you want it to work with flash(:error) or flash(:message), you'll have to change your flash message builder in your layout to use a common class or ID to target with the above code.

    ReplyDelete
  3. @Matt Ok, that answers my question about the flash. I figured it was a reference to storing something temporarily then discarding it and not just flashing a message.

    Also, that's basically how I'm using Github. Trying not to get hung up on documenting every little change and just stick to big edits. Using it as source control is a better way of thinking about it than just showing what I've been working on.

    ReplyDelete
  4. @David I tried the method you showed me, and it's not working. Do I need to make a call elsewhere in the application, or should it just work if that code is loaded into application.js?

    It seems to me I'd need to hook it to the controller somewhere, so it knows to call the .js file. Do I need a format.js in my respond_to methods?

    Also, I downloaded the Prototype gem and installed it. I haven't used it before, but I think I prefer the syntax to using jquery. Is there anything else I should be doing? Thanks again!

    ReplyDelete
  5. Looking at Chorenivore on Github, you already have prototype loaded. It lives in /public/javascripts, and is loaded in your layout with javascript_include_tag :defaults.

    You should just be able to paste the code I posted into /public/application.js and it will be loaded on every page load, so flash messages will always disappear.

    You could load a bit of javascript only on the pages you'll want it on just by including it in a script tag, or using content_for to add another javascript_include_tag to your layout.

    I see I let a stray paren sneak into my example. Try just changing the top line to this:

    document.observe('dom:loaded', function(){

    ReplyDelete
  6. Most people these days like jQuery better. The plugins certainly are more plentiful and the chaining syntax makes complex operations shorter.

    However, Rails has traditionally shipped with Prototype by default, and lots of existing tutorials you are working with use it.

    With Rails3, jQuery is now the default. Pretty confusing, eh? Pick your poison.

    ReplyDelete
  7. @David hmm this still isn't working. Any ideas what could be holding it up? I tried restarted the server and still no dice.

    Also, thanks for showing how to include javascript on individual pages. That's another question I had that I forgot to ask about.

    I think it might be a bit early in the game for me to be looking seriously into Jquery v. Prototype, but it's a good idea to learn how these tools work, too.

    ReplyDelete
  8. Checked out Chorenivore and added it myself to make sure everything was working alright. I shortened it (Don't really need to remove when fade does that anyway). This is what I used:


    document.observe('dom:loaded', function() {
    Effect.Fade( $('flash_notice'), { duration: 4 } )
    })

    I also submitted it as a patch to your project. Social coding!

    ReplyDelete
  9. Finally got it to work! I ended up using the example Matt showed, partly because I couldn't get the method David showed to work and partly because everything you've said backs up what I've read on my own: I should be using jquery anyway. A quick gem install later and we're back in business!

    ReplyDelete