technology from back to front

Archive for March, 2014

When in Rome

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.

by
david
on
31/03/14

Forces bolstered

After an intense drive to recruit over the last year we are proud to welcome a number of new staff. They bring a wealth of experience and talent.

Guy Strelitz fills the Programme Manager role. Joining the Lead Developers are Alex Thomas, Panos Katsaroumpas, Ash Hewson, Sam Carr, Robin Green and Shaun Tehari.

We are equally excited to see the very welcome return of one of the key members of the RabbitMQ development team: David Wragg, who first joined LShift in 2007.

by
sophie
on

Getting back into front-end web development

I’ve been working on a small SPA (Single Page Application) – just HTML, CSS and JavaScript statically served and doing its thing entirely in the browser. I learned a great deal throughout the project, but here are some of the things that strike me as most valuable.

Get a good workflow going

I used Grunt to setup a nice build system that is mostly a joy during development. It took a while to evolve my Gruntfile, but now when I edit a file, the results are immediately refreshed in the browser (I don’t even have to hit cmd-R). I can deploy to S3 test, staging and live sites with a single command that takes about 3 seconds. My SASS files are compiled down to minified CSS, my JS is minified with source maps etc.

The hardest part of using Grunt is figuring out how to configure it and its many contrib plugins. I could have done with a definitive reference or perhaps I could have used Yeoman to give me an out of the box solution. However I recognised that I was always going to have to figure out the guts of Grunt so I think I really was better off bespoking it from the start. I’m glad I did as now I have a tight setup that does precisely what I want and that I understand completely.

Now it seems there is a new kid on the scene, Gulp – nicely introduced in this tutorial blog post. I will definitely be looking closely at that for my next project, with the piping approach looking like the key step beyond Grunt, along with nicer syntax. I’d also look at Browserify, for a nicer way to piece together the JS bits.

Learn JavaScript properly

To the uninitiated, JavaScript is fairly surprising in many subtle ways, and though I can grok the prototype-based inheritance fairly easily, the scoping rules caught me out repeatedly. This was especially the case as I tried to create JQuery plugins with private methods and state. Eventually a simple old article by grand-daddy of JavaScript writing Douglas Crockford gave me the vital clues I was missing.

Really I should just read his book, and I would recommend that anyone else doesn’t just attempt to learn JavaScript as they go, but takes some time to pro-actively figure out the core concepts – it will pay off in short order.

jQuery is non-negotiable

And the award for most indispensable library goes to: jQuery. Seriously, it should be baked into the browsers or the ECMAScript standard. The nicest thing about it is I can pretty much just guess at the API and be right most of the time, though reading the docs usually reveals new conveniences that go further than I even imagined.

Browser quirks can be a living nightmare

JavaScript itself is fairly reliable, especially with judicious use of mature libraries like jQuery that paper over the cross-browser DOM cracks. CSS in complicated scenarios is where it all seems to go wrong however.

It’s amazing how broken/different some browsers are. Here are just a few highlights, though every day brought tens of new oddities and associated workarounds.

  • Mobile Safari on iOS 7 reports the viewport height inconsistently (depending on how you access it) leading to bad layout and horrible JavaScript workarounds.
  • Use of -webkit-overflow-scrolling:touch causes the hardware accelerated renderer to kick in, resulting in various flickers, flashes and flitches with content not rendering.
  • IE 10 on Windows 8 shows back/forward overlays at the left/right of the screen when your mouse moves near them, obscuring links in those locations.
  • Chrome running on Retina Macs suffers from strange graphical glitches when running CSS Animations, but is fine with CSS Transitions. However other browsers/platforms really need CSS Animations to get smooth, hardware accelerated movement. In my case it was necessary to implement both approaches and select using browser detection.
by
Sam Carr
on
06/03/14

Two Magnolias, one container

We are using Magnolia in a number of projects here at LShift. I have been feeling that Magnolia has a simple way to do most things, but often there are a number of other plausible alternatives that gradually lead you into wasting enormous amounts of time.

Here I want to present a simple way to get both author and public instances of Magnolia running in your dev environment in the same container. It may seem very obvious. If so, good. This was not the first way I tried, and it cost me a lot of time.

We will be aiming for:

  1. Easily deploying Magnolia onto a stage or production environment — one file, one or two configuration parameters only.
  2. Making it easy for a tester to launch local public and author instances of Magnolia that talk to each other correctly.
  3. Making it easy for a developer to debug Magnolia, having both instances running under the control of the IDE.

Preconditions

I will be assuming that you have a parent project with a child project that represents your webapp. I also will assume that you have copied the contents of src/main/webapp/WEB-INF/config from the magnolia-empty-webapp project into your own webapp project. The source for this is in the ce-bundle at git.magnolia-cms.com/gitweb/?p=ce-bundle.pub.git, but assuming you have magnolia-empty-webapp as a dependency (as recommended) you should be able to pick it up from your target directory.

I will be using Tomcat 7 as Tomcat is recommended by Magnolia and 7 is the latest stable version at the time of writing.

Deploying Magnolia to Stage or Production environments

For deployment to stage or production you don’t want both author and public deployed in the same container, or even on the same machine; so we only need to be able to configure a single running instance to be either author or public.

This is quite simple and well documented. In your webapp project, open your src/main/webapp/WEB-INF/web.xml (that you copied from the empty webapp project as described above) and look for the lines:

  <context-param>
    <param-name>magnolia.initialization.file</param-name>
    <param-value>
      WEB-INF/config/${servername}/${contextPath}/magnolia.properties,
      WEB-INF/config/${servername}/${webapp}/magnolia.properties,
      WEB-INF/config/${servername}/magnolia.properties,
      WEB-INF/config/${contextPath}/magnolia.properties,
      WEB-INF/config/${webapp}/magnolia.properties,
      WEB-INF/config/default/magnolia.properties,
      WEB-INF/config/magnolia.properties
    </param-value>
  </context-param>

You will need to add your own line at the top of the <param-value> section:

      WEB-INF/config/${contextAttribute/instanceName}/magnolia.properties,

Then when you deploy your WAR, you can simply set the instanceName environment variable to magnoliaPublic or magnoliaAuthor depending on what type of instance you want. As you can see from the fragment of web.xml above, this will make the settings in src/main/webapp/WEB-INF/config/magnoliaAuthor/magnolia.properties or src/main/webapp/WEB-INF/config/magnoliaAuthor/magnolia.properties active, respectively. Ultimately you will want to make more magnolia.properties files in more subdirectories (called, perhaps, stageAuthor, productionPublic and so on) with appropriate settings for those environments and you can simply make instanceName refer to the appropriate subdirectory.

Local Magnolia from the command line

Now, it would seem plausible that this method can be made to make your local testing environment work. Plausible, but wrong. This is the difficult way. You’ll start writing your context.xml files, then you’ll need a server.xml file, then before you know it you’ll be building your own Tomcat so that you can manage it all.

The “secret” is to use the fact that the web.xml already refers to the context path, in the form of the line:

      WEB-INF/config/${contextPath}/magnolia.properties,

(as well as in another line which we won’t concern ourselves with). This means that, instead using an environment variable, you can deploy the same WAR file to two different context paths and Magnolia will set itself up differently for each. And if you choose the paths /magnoliaAuthor and /magnoliaPublic you will automatically pick up the properties files provided by the empty webapp and all will be fine — Magnolia even sets up the author instance to point at http://localhost:8080/magnoliaPublic by default, so you won’t have to configure it yourself!

Well, actually, it’s not all fine. If you try this, you’ll find that one of your instances will refuse to start, complaining that its repository is already locked. Of course, they are trying to use the same repository. Fix this by adding a line similar to the following to magnoliaPublic/magnolia.properties:

magnolia.repositories.home=${magnolia.home}/repositories-public

The name of the subdirectory is not important. Note that, as it stands, this will change where the stage and production deployed Magnolias you configured above store their data. If that bothers you, now might be a good time to make your productionPublic/magnolia.properties and similar files.

So, how do we get that running painlessly so that your tester doesn’t keep asking you how to do it?

Add the Tomcat Maven plugin to your webapp’s pom.xml, and configure it to launch your WAR twice on two different context paths:

      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <webapps>
            <webapp>
              <groupId>com.my.group</groupId>
              <artifactId>my-webapp</artifactId>
              <version>1.0-SNAPSHOT</version>
              <type>war</type>
              <asWebapp>true</asWebapp>
              <contextPath>/magnoliaAuthor</contextPath>
            </webapp>
            <webapp>
              <groupId>com.my.group</groupId>
              <artifactId>my-webapp</artifactId>
              <version>1.0-SNAPSHOT</version>
              <type>war</type>
              <asWebapp>true</asWebapp>
              <contextPath>/magnoliaPublic</contextPath>
            </webapp>
          </webapps>
        </configuration>
      </plugin>

Replacing com.my.group and my-webapp with your own webapp’s group and artifact id.

Now you can run your Magnolia simply with:

mvn tomcat7:run-war

For reasons best known to the Tomcat plugin, boring old mvn tomcat7:run doesn’t work — deploying only one Magnolia in its default location. Sorry.

The instances are available, of course, at http://localhost:8080/magnoliaAuthor and http://localhost:8080/magnoliaPublic.

Local Magnolia from your IDE

Now you’re on the home straight. Here’s how I configure the Tomcat plugin in Eclipse:

Firstly, you need to get Eclipse to know about Tomcat 7. The foolproof way to do this is as follows: Window -> Preferences -> Server -> Runtime Environments -> Add… -> Apache Tomcat v7.0 -> Next. Now give it a location that is writable by you in the “Tomcat installation directory” box and click “Download and Install…”; using your pre-existing Tomcat might not work if it isn’t laid out in the way Eclipse expects. Now Finish and open the Servers view.

You can now add a new Tomcat 7 server and double-click on it. Tick “Publish module contexts to separate XML files”, set the start timeout to something large like 480 seconds, and in the modules tab add your webapp project twice; once with the path /magnoliaAuthor and once with the path /magnoliaPublic.

Now you can launch and debug your two instances of Magnolia from within your IDE!

by
Tim Band
on
03/03/14

Search

Categories

You are currently browsing the LShift Ltd. blog archives for March, 2014.

Feeds

Archives

2000-14 LShift Ltd, 1st Floor, Hoxton Point, 6 Rufus Street, London, N1 6PE, UK+44 (0)20 7729 7060   Contact us