Decisions
Ruby on Rails vs. Google App Engine
My first decision was what to use as my server platform. I've had some experience with Java Servlets, ASP.NET, PHP, and Ruby on Rails. Ruby on Rails is definitely the coolest of these. However, given that I am a Google fan-boy, I at least considered the Google App Engine which also looks pretty cool. However, laziness won out and I stuck to Rails because I know it and I am already tackling a bunch of new things on this project. Plus I want to know Rails better. At some point I'll have to come up with a project to try out with Google, and see if I want to change my mind.
JQuery vs. Prototype
As I said above, I want to accomplish the interaction between the client and the server via AJAX. Well, actually I want to pass JSON messages, not XML, but I think they still call it AJAX. Anyway, while I could do this myself, it seems to make sense to take advantage of existing libraries. While there are a ton of JavaScript libraries, the two that seem to be the most popular are jQuery and Prototype. Prototype is distributed with Rails and so is the natural choice. But supposedly jQuery is much more lightweight and efficient, so I am going to go with jQuery.
I realize that neither of the choices above seem to be very well researched. Well, this is just a toy project - if I spend forever researching all the options then I'll never get anything done. Just by doing something, hopefully I'll be better informed in the future.
What I Did
First I created a rails project:
rails drawing
and then I imported the HTML and JavaScript that I wrote into the public directory of the project.
Of course I needed a database to store the maps in. I decided to use SQLite for now because it is so easy to set up. I'll probably move to mysql or something if/when I deploy it for real. I then used the rails scaffold command to generate the stub rails code.
ruby script/generate scaffold Map name:string content:text
In theory, I don't really need the generated map controller and views. However, being able to point a browser at <host>/map/ and see all of the uploaded maps provides a very easy way to test if maps are being saved. I then created my AJAX controller with a save_map and load_map action. Since these are intended to be used in an AJAX fashion, I didn't create any views for them.
RubyMy intent was for the drawing program to pass the map as a JSON object to the AJAX call. The controller could just save that to the database and read it back for a load. As far as Ruby is concerned it is just a string. Here is the ruby code that makes that work.
class MyAjaxController < ApplicationController def save_map name = params[:name] mapData = params[:map].to_json map = Map.find(:first, :conditions=>{:name=>name}) map.content = mapData if map map = Map.new(:name=>name, :content=>mapData) unless map map.save! render :text=>"ok" end def load_map name = params[:name] map = Map.find(:first, :conditions=>{:name=>name}) render :text=>map.content end endAs you can see, the save method just reads the name and map value from the posted parameters. Since ruby tries to parse these as objects, I call
to_json
to get the map contents back into a string. Then I just save it to the database (either as a new row, or updating an existing row). load_map
just returns the map content that was saved with the given name. Neither of these methods have much in the way of error checking, so they are definitely not "production" ready, but they work great as a proof of concept.HTML
Here are the controls I added to the page to allow saving and loading.
<input type="text" id="name"/> <input type="submit" id="save" value="Save" /> <input type="submit" id="load" value="Load" />
JavaScript
The JavaScript for making the AJAX call looks like this:
1 $("#save").click(function() { 2 var data = draw.save(); 3 var name = $("#name").get(0).value; 4 $.post('/my_ajax/save_map', {name:name, map:data}, function() { 5 alert("Map saved!"); 6 }); 7 }); 8 9 $("#load").click(function() { 10 var name = $("#name").get(0).value; 11 $.getJSON('/my_ajax/load_map', {name:name}, function(data, textStatus) { 12 control.reset(); 13 draw.load(data); 14 }); 15 });Everything that starts with a
$
is a jQuery function. #save
, #load
, and #name
refer to the HTML controls I've put on the page. The draw
variable is the DrawingRecord instance, and control
is a ControlLayer instance from the original drawing program. I added a save and a load method to the DrawingRecord to save and restore the map. Both Ajax calls $.post(...); $.getJSON(...);
take a URL, an object to pass to the server, and a function which is called when thecall returns (i.e. the A in AJAX).As for what the
draw.save()
method returns, originally I just tried using the DrawingRecord.lines object. Unfortunately this caused problems - apparently jQuery tried to package up all the methods as well as the fields of the object. So instead I made the save
and load
methods in DrawingRecord package up all of the points into a single array which can be easily passed back and forth.1 DrawingRecord.prototype.save = function(){ 2 var lines = [] 3 for (var i in this.lines) { 4 var line = this.lines[i]; 5 lines.push(line.p1.x, line.p1.y, line.p2.x, line.p2.y); 6 } 7 return {lines:lines} 8 } 9 DrawingRecord.prototype.load = function(data) { 10 this.reset(); 11 this.dontDraw = true; 12 var temp = []; 13 for (var index in data.lines) { 14 temp.push(parseInt(data.lines[index])); 15 if (temp.length == 4) { 16 this.addLine(new Point(temp[0], temp[1]), new Point(temp[2], temp[3])); 17 temp = []; 18 } 19 } 20 this.dontDraw = false; 21 this.draw(); 22 }And with that, it basically all works. Unfortunately, I can't have a live demo of this to put in this blog as I don't have a live server that I want to have committed to serving this project forever.
No comments:
Post a Comment