Archive for the ‘Rails’ Category

iphone scribble webapp

Tuesday, July 20th, 2010

As already mentioned through twitter i’m playing around with the iphone and the canvas element to build a scribble webapp running on the device (and android devices as well). It should be possible to save/upload the scribbled image afterwards. Here’s a first code snippet

<!DOCTYPE html>

<html>
  <head>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
    <script type="text/javascript" src="http://jasonkuhn.net/mobile/jqui/js/jquery.iphoneui.js"></script>

    <meta content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" name="viewport" />
    <meta charset="utf-8">
  </head>

  <body style="margin: 0; padding: 0; -webkit-text-size-adjust: none; padding-left: 10px; padding-top: 10px;">
    <button id="save" style="position: absolute; margin-left: 10px; margin-top: 10px;">save</button>

    <script type="text/javascript">
      $(document).ready(
        function() {
          $("body").append("<canvas id='canvas' style='border: 1px solid #000;'></canvas>");

          $("#canvas").attr("width", (window.innerWidth ? window.innerWidth : $(window).width()) - 20);
          $("#canvas").attr("height", (window.innerHeight ? window.innerHeight : $(window).height())- 20);

          $("#canvas").addTouch();

          var ctx = $("#canvas")[0].getContext("2d");

          ctx.lineCap = "round";
          ctx.lineWidth = 15;

          var start_x = null, start_y = null, drawing = false;

          $("#canvas").bind("mousedown",
            function(e) {
              drawing = true;

              start_x = e.pageX - $("#canvas").offset().left;
              start_y = e.pageY - $("#canvas").offset().top;
            }
          );

          $("#canvas").bind("mouseup",
            function(e) {
              drawing = false;
            }
          );

          $("#canvas").bind("mousemove",
            function(e) {
              if(!drawing)
                return;

              var x = e.pageX - $("#canvas").offset().left;
              var y = e.pageY - $("#canvas").offset().top;

              ctx.beginPath();
              ctx.moveTo(start_x, start_y);
              ctx.lineTo(x, y);
              ctx.stroke();

              e.preventDefault();

              start_x = x;
              start_y = y;
            }
          );

          $("#save").click(
            function() {
              var data = $("#canvas")[0].toDataURL("image/png").replace(/^[^,] ,/, "");

              $.ajax({
                 type: "post",
                 url: "/images/upload",
                 data: "image="   encodeURIComponent(data),
                 processData: false
              });
            }
          );
        }
      );
    </script>
  </body>
</html>

Step 1: Drawing

Drawing within the canvas is straight forward. We use mousedown, mouseup and mousemove to recognize the mouse events and draw lines between the coordinates.

Step 2: Upload

Uploading the the scribbled image is straight forward too, thanks canvas.toDataURL. toDataURL encodes the canvas using base64 what allows us to upload the encoded image using ajax or a form.

Of couse we need some server side processing. Using rails this could look like

class ImagesController < ApplicationController
  def upload
    open("#{RAILS_ROOT}/public/images/upload.png", "w") do |stream|
      stream.write decode64(params[:image])
     end 

    render(:update) { |page| page.alert("saved") }
  end
end

Step 3: Iphone

Thanks to this jquery plugin getting this to work on the iphone is as easy as including a javascript file. It translates the iphone’s javascript touch events (touchstart, touchmove, touchend) to mousedown, mouseup, mousemove, … if you addTouch() to the element.

That’s it. Maybe it’s worth to continue working on it and publishing it as a real project, because adding a scribble-webapp (within an iframe maybe?) could be a nice feature for many mobile webapps.

rails plugin testing

Friday, July 16th, 2010

I personally think there is not enough written about rails plugin testing, you have to deal with some problems, though, because you don’t have the rails environment on your side. I want to give 2 tips.

Tip 1:

When you extend ActionController::Base within your plugin, put your extensions into a module within a separate file. Then create another file where you extend ActionController::Base like so

require "my_fancy_controller_extension"

class ActionController::Base
  include MyFancyControllerExtension
end

The first advantage of doing it this way is: you can make your extensions protected

class ActionController::Base
  protected

  include MyFancyControllerExtension
end

and you can test your extensions without dealing with permissions: my_fancy_controller_extension_test.rb

class MyFancyControllerExtensionTest < Test::Unit::TestCase
  class DummyController
    include MyFancyControllerExtension
  end

  def test_whatever
    assert DummyController.new....

    # oh yeah, the fancy extensions are not protected within these tests
  end
end

This leads to

Tip 2:

Don’t try to load the whole rails environment within your plugin tests. Loading the whole environment is a lot of work, dependencies, paths, …. Avoid that, where you can. Instead, create dummy controllers, views, models, … to only build the interface neccessary for testing. Use duck typing! One example:

class TestController
  attr_accessor :request, :session, ... # only what your plugin code needs

  class TestRequest
    attr_accessor :host, ...
  end
end

def MyFancyControllerExtensionTest < Test::Unit::TestCase
  def setup
    @controller = TestController.new
  end

  ...
end

This helps a lot when you don’t know where to start your tests.

mobile-fu rjs

Thursday, July 15th, 2010

hm… hm… hm… hm

I’m a bit unsatisfied with mobile-fu for rails. Sure, it’s great in general, but I want to have different templates for mobile rjs and default rjs and i think it’s a bit clumsy to do it like that:

application.html.erb

def mobile?
  return session[:mobile_view]
end

# distinct between mobile and non-mobile js (mobile-fu)
# only supports :template paramter for mobile-enabled js

def render_mobilized_js(options = nil)
  # if we’re not mobile => render like there is no mobile

  unless mobile?
    return render options if options
    return render
  end

  # render mobilized rjs template

  ops = (options || {}).dup

  ops[:template] ||= “#{controller_name}/#{action_name}”
  ops[:template] += “.mobile_js.rjs”

  return render ops
end

but now I’m able to do this

respond_to do |format|
  format.html
  format.js { render_mobilized_js }
end

But in an ideal world i don’t want to call render at all. I want rails or mobile-fu to do it for me:

respond_to do |format|
  format.html
  format.js
  format.mobile_js
end

Not yet possible - and if it would be possible it would break dependencies. Therefore i keep using my method - unless anyone points me a step towards the elegant solution… anyone?