Putting Sencha Architect 2 into Rails 3.1's asset pipeline

Suraj N. Kurapati

  1. Problem
    1. Solution
      1. app/assets/javascripts/gui.rake

      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:

      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
      

      Updates