1. Technology
You can opt-out at any time. Please refer to our privacy policy for contact information.

Finishing the Project

By

See More About

In the last few articles, we got started with Sinatra web applications and implemented a simple AJAX web application. In this article, we'll start off by DRYing up our example application.

DRY

DRY stands for Don't Repeat Yourself, and in the previous example we have a huge repetition. It takes several lines of code to implement the Javascript for each button. This is not DRY at all, especially since each button does the same thing essentially, they're just called something different and make requests to different URLs, but other than that they're the same. So let's write a little loop that

The first thing you'll notice is the :javascript filter is gone, and the %script tag is in. This is the first time we run into a major limitation with Haml. While the syntax is nice, it's restrictive. We can't iterate over an array inside of a :javascript filter, and at the same time we can't arbitrarily indent inside of a %script tag, so while we do get this done, we lose all indentation on our javascript. It works, and it's DRY, but it introduced a new (much smaller) problem.

So first we define a list of buttons at the top of the file in a constant. We'll use these to both generate the Javascript and HTML.


BUTTONS = %w[start stop]

Now, our root view looks a bit different. Honestly I think it looks quite bad, but it's the best I could do in Haml. I have a feeling Erb would have been much easier in this case. However it is DRY, so we can be free to add as many buttons as we wish. All we have to do is add them to the array at the top.


@@ root
%script
	$(document).ready(function() {
	- BUTTONS.each do|button|
		$("##{button}").click(function() {
		$.ajax( {url:"/#{button}", success:function(result) {
		$("#message").html(result)
		}})});
	})
	
- BUTTONS.each do|button|
	%button{:id => button} #{button.capitalize}

Make It Do Something

The final piece of this puzzle is to make the buttons actually do something. And for that we'll reach back to the original point of this whole exercise and call some Win32 API calls. We're going to simulate some keystroke which start and stop Spotify. Everything added in this section is from the previous article TODO, the only changed in the actual Sinatra app are the start and stop actions.

The two actions have been replaced by play and next actions. These actions send the space bar keystroke for play/pause and Ctrl-right keystroke for next song. Because the application is DRY, the only things we need to change are the array of buttons and the actual actions that go with them.


require 'sinatra'
require 'sinatra/reloader'
require 'java'
require 'ffi'

module Win
  extend FFI::Library
 
  ffi_lib 'user32'
  ffi_convention :stdcall
 
  attach_function :message_box, :MessageBoxA, [:pointer, :string, :string, :uint], :int
  #attach_function :find_window, :FindWindow, [:string, :string], :pointer
 
  callback :enum_callback, [:pointer, :long], :bool
  attach_function :enum_desktop_windows,
	:EnumDesktopWindows,
	[ :pointer, :enum_callback, :long ],
	:bool
  attach_function :get_window_text,
	:GetWindowTextA,
	[ :pointer, :pointer, :int ],
	:int
	
  attach_function :send_message,
	:SendMessageA,
	[ :pointer, :uint, :uint, :long ],
	:int
 
  def self.enumerate_all_windows(&block)
	title = FFI::MemoryPointer.new :char, 512
	
	callback = Proc.new do|wnd,param|
  	title.clear
  	Win.get_window_text(wnd, title, title.size)  	
  	block.call(wnd,param,title.get_string(0)) if block
  	
  	true
	end
	
	Win.enum_desktop_windows(nil, callback, 0)
  end
 
  def self.find_windows(name)
	windows = []
	name = Regexp.compile(name) unless name.is_a?(Regexp)
	
	enumerate_all_windows do|wnd,param,title|
  	windows << [wnd,title] if title =~ name
	end
	
	return windows
  end
end

set :bind, '0.0.0.0'
BUTTONS = %w[play next]
spotify = Win.find_windows('Spotify').first

get "/" do
	haml :root
end

get "/play" do
	Win.send_message(spotify[0], 0x100, 0x20, 0)
	Win.send_message(spotify[0], 0x101, 0x20, 0)
end

get "/next" do
	Win.send_message(spotify[0], 0x100, 0xa2, 0)
	Win.send_message(spotify[0], 0x100, 0x27, 0)

	Win.send_message(spotify[0], 0x101, 0xa2, 0)
	Win.send_message(spotify[0], 0x101, 0x27, 0)
end

__END__
@@ layout
%html
	%head
		%title AJAX Example
		%script{:type => "text/javascript", :src => "http://code.jquery.com/jquery-2.1.0.min.js"}
	%body
		#message
		=yield

@@ root
%script
	$(document).ready(function() {
	- BUTTONS.each do|button|
		$("##{button}").click(function() {
		$.ajax( {url:"/#{button}", success:function(result) {
		$("#message").html(result)
		}})});
	})
	
- BUTTONS.each do|button|
	%button{:id => button} #{button.capitalize}

@@ start
It has started

@@ stop
It stopped
  1. About.com
  2. Technology
  3. Ruby
  4. JRuby
  5. Automation of Windows using FFI
  6. Finishing the Project

©2014 About.com. All rights reserved.