Putting Sencha Architect 2 into Rails 3.1's asset pipeline
Problem
After months of building ExtJS applications by hand, I decided to try a simpler, Visual Basic styled approach by using Sencha Architect 2, wherein I would design the Graphical User Interface (GUI) by drag-and-drop and then add interactivity through appropriately-placed JavaScript snippets.
In this article, I’ll show you how I put my ExtJS application (generated by Sencha Architect 2) into my Rails 3.1 application’s asset pipeline. This procedure should work equally well for handwritten ExtJS applications too!
Solution
First, I created a gui/
directory inside my Rails application’s root
to house all of my Sencha Architect 2 project’s files like this:
- gui/
- app/
- controller/
- model/
- store/
- view/
- Application.js
- Viewport.js
- app.html
- app.js
- metadata/
- Application
- controller/
- model/
- resource/
- Library
- store/
- view/
- My_Sencha_Architect_2_Project.xds
- app/
Second, I wrote a Rake script (see below) to compile the gui/
directory
into a single app/assets/javascripts/gui.js
asset and ran it like this:
cd app/assets/javascripts/
rake -f gui.rake # builds the asset as necessary
rake -f gui.rake clean default # forcefully rebuilds the asset
Third, I placed the compiled app/assets/javascripts/gui.js
asset into
my Rails application’s asset pipeline by adding the following line to my
app/assets/javascripts/application.js
file:
//= require gui
That’s all folks!
app/assets/javascripts/gui.rake
This Rake script operates by assembling a dependency graph from requires
fields (which specify the names of other ExtJS classes that need to be
loaded beforehand in order to avoid dependency errors) that are present in
most ExtJS classes generated for the project by Sencha Architect 2.
It then flattens the dependency graph into a linear sequence, using the Topological Sort algorithm, in such a way that simply loading each ExtJS class in the linear sequence, sequentially, will not trigger any dependency errors.
require 'rake/clean'
require 'tsort'
require 'yaml'
task default: 'gui.js'
CLEAN.include 'gui.js'
directory project = '../../../gui'
file 'gui' => project do |t|
ln_s t.prerequisites.first, t.name
end
file 'gui.js' => FileList['gui', 'gui/app/**/*.js'] do |t|
requires_by_file = RequiresHash.new
t.prerequisites.each do |file|
if file.end_with? '.js' and File.read(file) =~ /\brequires:\s+(.+?)\s+\}/m
requires_by_file[file] = Array(YAML.load($1)).map do |require|
"gui/app/#{require.split('.', 2).last.tr('.', '/')}.js"
end
end
end
File.open(t.name, 'w') do |file|
file.puts(
'//= require_self',
requires_by_file.tsort.map {|r| "//= require ./#{r}" },
'//= require_tree ./gui/app',
'//= require_tree ./gui',
# add a blank line to separate asset requires from inline logic
# that directly corresponds to the require_self directive above
nil,
# prevent GUI logic from enabling ExtJS' dynamic asset loader since
# all necessary GUI assets are already present in this gui.js asset
'Ext.Loader.setConfig = function(){};'
)
end
end
class RequiresHash < Hash
include TSort
alias tsort_each_node each_key
def tsort_each_child(node, &block)
fetch(node, []).each(&block)
end
end