Integrating Dojo by adding command line options

There is one last key aspect of modules, which has not been touched yet: the opportunity to add new command line options to modules.

This recipe utilizes the Dojo toolkit to show this feature. Dojo is one of the big JavaScript toolkits, which features tons of widgets, a simple interface, and a very easy start for object oriented developers. If you are using the standard distribution of Dojo together with a lot of widgets, there are many HTTP requests for all the widgets, as every widget is put into its own JavaScript file in a default Dojo installation. Many requests result in very slow application loading. Dojo comes with its own JavaScript optimizer called ShrinkSafe, which handles lots of things, like compressing the JavaScript code needed by your custom application into one single file as well as creating i18n files and compressing CSS code.

In order to use the ShrinkSafe capabilities, you need a developer build of Dojo. A configuration file is created, where you specify exactly which widgets are needed for this custom build. Only these referenced widgets are compressed into the single JavaScript file.

Such a precompilation step implies that there must be some way to download and compile the JavaScript before or during the Play application is running. You can develop without problems with a zero or partly optimized version of Dojo, because missing files are loaded at runtime. However, you should have a simple method to trigger the Dojo build process. A simple method like entering play dojo:compile on the command line.

The source code of the example is available at examples/chapter5/dojo-integration.

Getting ready

Create a new module called Dojo, and create any example application in which you include this module via your depdencies.yml file. Also put a Dojo version in your application.conf like this:

dojo.version=1.6.0

Just put the most up-to-date Dojo version as value in the key.

How to do it...

Your module is actually almost bare. The only file which needs to be touched is the dojo/commands.py file:

import urllib
import time
import tarfile
import os
import sys
import getopt
import shutil
import play.commands.modulesrepo

MODULE = 'dojo'

COMMANDS = ['dojo:download', 'dojo:compile', 'dojo:copy', 'dojo:clean']

dojoVersion = "1.6.0"
dojoProfile = "play"

def execute(**kargs):
    command = kargs.get("command")
    app = kargs.get("app")
    args = kargs.get("args")
    env = kargs.get("env")

    global dojoVersion
    global dojoProfile

    dojoVersion = app.readConf("dojo.version")

    try:
        optlist, args = getopt.getopt(args, '', ['version=', 'profile='])
        for o, a in optlist:
            if o in ('--version'):
                dojoVersion = a
            if o in ('--profile'):
                dojoProfile = a
    except getopt.GetoptError, err:
        print "~ %s" % str(err)
        print "~ "
        sys.exit(-1)

    if command == "dojo:download":
        dojoDownload()
    if command == "dojo:compile":
        dojoCompile()
    if command == "dojo:copy":
        dojoCopy()

The next three methods are helper methods to construct the standard naming scheme of Dojo directories and filenames including the specified version.

def getDirectory():
   global dojoVersion
   return "dojo-release-" + dojoVersion + "-src"

def getFile():
   return getDirectory() + ".zip"

def getUrl():
    global dojoVersion
    return "http://download.dojotoolkit.org/release-" + dojoVersion + "/" + getFile()

All the following defined methods start with Dojo and represent the code executed for each of the command line options. For example, dojoCompile() maps to the command line option of play dojo:compile:

def dojoCompile():
    dir = "dojo/%s/util/buildscripts" % getDirectory()
    os.chdir(dir)
    os.system("./build.sh profileFile=../../../../conf/dojo-profile-%s.js action=release" % dojoProfile)

def dojoClean():
    dir = "dojo/%s/util/buildscripts" % getDirectory()
    os.chdir(dir)
    os.chmod("build.sh", 0755)
    os.system("./build.sh action=clean" % dojoProfile)

def dojoCopy():
    src = "dojo/%s/release/dojo/" % getDirectory()
    dst = "public/javascripts/dojo"
    print "Removing current dojo compiled code at %s" % dst
    shutil.rmtree(dst)
    print "Copying dojo %s over to public/ directory" % dojoVersion
    shutil.copytree(src, dst)

def dojoDownload():
    file = getFile()

    if not os.path.exists("dojo"):
        os.mkdir("dojo")

    if not os.path.exists("dojo/" + file):
        Downloader().retrieve(getUrl(), "dojo/" + file)
    else:
        print "Archive already downloaded. Please delete to force new download or specifiy another version"

    if not os.path.exists("dojo/" + getDirectory()):
        print "Unpacking " + file + " into dojo/"
        modulesrepo.Unzip().extract("dojo/" + file, "dojo/")
    else:
        print "Archive already unpacked. Please delete to force new extraction"

After this is put into your commands.py, you can check whether it works by going into your application and running play dojo:download.

This will download and unpack the Dojo version you specified in the application configuration file. Now create a custom Dojo configuration in conf/dojo-profile-play.js:

dependencies = {
  layers:  [
    {
      name: "testdojo.js",
      dependencies: [
        "dijit.Dialog",
        "dojox.wire.Wire",
        "dojox.wire.XmlWire"
      ]
    }
  ],
  prefixes: [
    [ "dijit", "../dijit" ],
    [ "dojox", "../dojox" ],
  ]
};

Now you can run play dojo:compile and wait a minute or two. After this you have a customized version of Dojo, which still needs to be copied into the public/ directory of your application in order to be visible. Just run play dojo:copy to copy the files.

The last step is to update the code to load your customized JavaScript. So edit your HTML files appropriately and insert the following snippet:

<script src="@{'/public/javascripts/dojo/dojo/dojo.js'}" type="text/javascript" charset="utf-8"></script>

<script src="@{'/public/javascripts/dojo/dojo/testdojo.js'}" type="text/javascript" charset="utf-8"></script>

<script type="text/javascript">
  dojo.require("dijit.Dialog")
  dojo.require("dojox.wire.Wire")
</script>

How it works...

Before explaining how this all works, you should make sure that an optimized build is actually used. You can connect to the controller, whose template includes the Dojo specific snippet that we saw some time back in this chapter. If you open your browser diagnostics tools, like Firebug for Mozilla Firefox, or the built in network diagnosis in Google Chrome, you should see the loading of only two JavaScript files, one being dojo.js itself, and the other testdojo.js, or however you named it in the profile configuration. If you require some Dojo module, which was not added in the build profile, you will see another HTTP request asking for it. This is ok for development, but not for production.

Now it is time to explain what actually happens in the Python code. The commands.py file does not have too much logic itself. If there is no dojo.version parameter specified in the application configuration, it will use 1.6.0, which was the current version at the time of writing. Furthermore, a default profile named Play is defined, which is used for the profile configuration file in the conf/ directory. You can overwrite the version as well as the profile to be used via the --version or the --profile command line parameters if you need to.

The execute() method parses the optional command line parameters and executes one of the four possible commands.

The getDirectory(), getFile() and getUrl() methods are simple helpers to create the correct named file, directory or download URL of the specified Dojo version.

The dojoCompile() method gets executed when entering play dojo:compile and switches the working directory to the util/buildscripts directory of the specified Dojo version. It then starts a compilation using either the specified or default profile file in the conf/ directory.

The dojoClean() method gets executed when entering play dojo:clean and triggers a clean inside of the dojo/ directory, but will not wipe out any files copied to the public/ folder. This command is not really necessary, as the code called in dojoCompile() already does this as well.

The dojoCopy() gets executed when entering play dojo:copy and method copies the compiled and optimized version which is inside the releases directory of the Dojo source to the public/dojo directory. Before copying the new Dojo build into the public directory, the old data is deleted. So if you stop the execution of this task, you might not have a complete Dojo installation in your public directory.

The dojoDownload() method gets executed when entering play dojo:download and downloads the specified Dojo version and extracts it into the dojo/ directory in the current application. It uses the ZIP file, because the unzip class included in Play is operating system independent. As ZIP files do not store permissions, the build.sh shell script has to be set as executable again. In order to save bandwidth it only downloads a version if it is not yet in the dojo/ directory.

There's more...

If you know Python a little bit, you have unlimited possibilities in your plugins, such as generating classes or configuration files automatically or validating data like internationalization data.

More about Dojo

The Dojo JavaScript toolkit is worth a look or two. Go to http://dojotoolkit.org/ or better yet to a site with tons of examples to view and copy, which is http://dojocampus.org/.

Create operating system independent modules

When watching closely, you will see that the module created in this recipe only works on non Unix based operating systems such as MacOS or Linux, as it executes the build.sh script directly. In order to be independent of the operating system you should always check the platform you are on and act appropriate. For example, getting the value of os.name returns posix for both Linux and Mac OS and could be used to differentiate operating systems.

More ideas for command support

Whenever you need to create configuration files from existing data in your application, this is the way to go. An example might be the Solr module in the next chapter. If you could create the needed XML configuration by typing play solr:xml, that would prevent some configuration mistakes. You could iterate over all model classes and check whether they have search fields configured and output these in the correct format.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.135.175