When in Rome

By: on March 31, 2014

I’ve been trying to integrate js-sequence-diagrams into Trac. I’ve reached the point where I can choose between my sequence diagrams getting rendered, and the rest of the Javascript in Trac working. And it’s all because of an underscore…

There’s a popular library in the Javascript world: underscore. In python _ is used to internationalize a string. Trac have carried this notation into their Javascript. That description makes this sound harmless. It’s anything but: it’s taken me a very long time to work that out.

The lesson here is write idiomatic code. Because Javascript has no module system, a web page really is a place you have to co-operate in by using the local customs. For Trac, it’s not reasonable to claim the namespace all for yourself: it’s main claim is extensibility, and the easiest way to extend it is using Javascript in the browser.

Anyway, I guess I should point out the solution is require.js. It’s not all that complicated to use in this case: we leave all the trac stuff, including jquery, and jquery-ui outside. _ get’s redefined, but just in the scope of sequence-diagram.js. main.js looks like this:

require.config({
  baseUrl: requireBaseUrl,
  shim : {
    'underscore': {
      exports: '_'
    },
    'raphael': {
      exports: 'Raphael'
    },
    'sequence-diagram': {
      deps: [ 'underscore', 'raphael' ]
    }
  },
});

require(["draw-sequence-diagrams"])

draw-sequence-diagrams is my script, which is written as a module:

define(
  // I don't actually refer to this, I need it's side effects on jQuery
  ["sequence-diagram"],
  function(sd) {
    // jQuery comes from Trac - it's global
    jQuery(document).ready(function() {
      jQuery(".sequence-diagram").sequenceDiagram({theme: 'simple'})
    })
 })

I have to get trac to calculate the base URL, so I add it as a variable. Here’s the python for the Trac Extension:

import pkg_resources

from genshi.core import Markup

from trac.core import implements, Component
from trac.web.chrome import ITemplateProvider, add_script, add_script_data
from trac.wiki.api import IWikiMacroProvider
from trac.web.api import IRequestFilter
from trac.util.translation import N_

MACRO_DESCRIPTION = """
Include a sequence diagram in the Wiki. We use a javascript
extension to actually render the sequence diagram.
"""


SEQUENCE_DIAGRAM_TEMPLATE = """
<div class="sequence-diagram">{text}</div>
"""


class JsSequenceDiagrams(Component):

    implements(ITemplateProvider,IWikiMacroProvider,IRequestFilter)
       
    # ITemplateProvider methods

    def get_htdocs_dirs(self):
        return [ ( 'js-sequence-diagrams',
                   pkg_resources.resource_filename(
                    'jssequencediagrams',
                    'templates') ) ]

    def get_templates_dirs(self):
        return []

    # IWikiMacroProvider methods

    def get_macros(self):
        yield 'SequenceDiagram'

    def get_macro_description(self, name):
        return 'messages', N_(MACRO_DESCRIPTION)

    def expand_macro(self, formatter, name, text, args):
        return Markup(SEQUENCE_DIAGRAM_TEMPLATE.format(text=text))

    def pre_process_request(self, req, handler):
        return handler

    def post_process_request(self, req, template, data, content_type):
        get = self.env.config.get
        add_script_data(req, { 'requireBaseUrl': '%s/js-sequence-diagrams' % req.href.chrome() })
        add_script(req, 'js-sequence-diagrams/require.js', 'text/javascript')
        add_script(req, 'js-sequence-diagrams/main.js', 'text/javascript')
        return (template, data, content_type)

You can see that I load main.js, rather than letting require.js do it for me. That’s because otherwise, I wouldn’t be able to use trac’s add_script, which can’t add the extra attribute I’d need for that.

Also note I need to get trac to calculate require’s base URL, and I pass that in as a javascript variable.

You will need to look elsewhere for how to include resources in your Trac extension.

FacebookTwitterGoogle+

Comments are closed