Jump To …

pageinit.coffee

Copyright 2012, 2013 The Apache Software Foundation

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http:#www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

t5/core/pageinit

Module that defines functions used for page initialization. The initialize function is passed an array of initializers; each initializer is itself an array. The first value in the initializer defines the name of the module to invoke. The module name may also indicate the function exported by the module, as a suffix following a colon: e.g., "my/module:myfunc". Any additional values in the initializer are passed to the function. The context of the function (this) is null.

define ["underscore", "./console", "./dom", "./events"],
  (_, console, dom, events) ->
    pathPrefix = null

Borrowed from Prototype:

    isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'
    isIE = !!window.attachEvent && !isOpera

    rebuildURL = (path) ->
      return path if path.match /^https?:/

See Tapestry.rebuildURL() for an error about the path not starting with a leading '/' We'll assume that doesn't happen.

      if !pathPrefix
        l = window.location
        pathPrefix = "#{l.protocol}//#{l.host}"

      return pathPrefix + path

    rebuildURLOnIE =
      if isIE then rebuildURL else _.identity

    addStylesheets = (newStylesheets) ->
      return unless newStylesheets

Figure out which stylesheets are loaded; adjust for IE, and especially, IE 9 which can report a stylesheet's URL as null (not empty string).

      loaded = _.chain(document.styleSheets)
      .pluck("href")
      .without("")
      .without(null)
      .map(rebuildURLOnIE)

      insertionPoint = _.find(document.styleSheets, (ss) ->
        parent = ss.ownerNode || ss.owningElement
        parent.rel is "stylesheet t-ajax-insertion-point")

Most browsers support document.head, but older IE doesn't:

      head = document.head or document.getElementsByTagName("head")[0]

      _.chain(newStylesheets)
      .map((ss) -> { href: rebuildURL(ss.href), media: ss.media })
      .reject((ss) -> loaded.contains(ss.href).value())
      .each((ss) ->
        element = document.createElement "link"
        element.setAttribute "type", "text/css"
        element.setAttribute "rel", "stylesheet"
        element.setAttribute "href", ss.href
        if ss.media
          element.setAttribute "media", ss.media

        if insertionPoint
          head.insertBefore element, insertionPoint.ownerNode
        else
          head.appendChild element

        console.debug "Added stylesheet #{ss.href}"
      )

      return

    invokeInitializer = (tracker, qualifiedName, initArguments) ->
      [moduleName, functionName] = qualifiedName.split ':'

      require [moduleName], (moduleLib) ->

Some modules export nothing but do some full-page initialization, such as adding event handlers to the body.

        if not functionName and
          initArguments.length is 0 and
          not _.isFunction moduleLib
            console.debug "Loaded module #{moduleName}"
            tracker()
            return

        fn = if functionName? then moduleLib[functionName] else moduleLib

        unless fn?
          throw new Error "Could not locate function `#{qualifiedName}'."

        if console.debugEnabled
          argsString = (JSON.stringify arg for arg in initArguments).join(", ")
          console.debug "Invoking #{qualifiedName}(#{argsString})"

        fn.apply null, initArguments

        tracker()

Loads all specified libraries in order (this includes the core stack, other stacks, and any free-standing libraries). It then executes the initializations. Once all initializations have completed (which is usually an asynchronous operation, as initializations may require the loading of further modules), then the data-page-initialized attribute of the <body> element is set to 'true'.

This is the main export of the module; other functions are attached as properties.

    loadLibrariesAndInitialize = (libraries, inits) ->
      console.debug "Loading #{libraries?.length or 0} libraries"
      exports.loadLibraries libraries,
        -> exports.initialize inits,
          ->

At this point, all libraries have been loaded, and all inits should have executed. Unless some of the inits triggered Ajax updates (such as a core/ProgressiveDisplay component), then the page should be ready to go. We set a flag, mostly used by test suites, to ensure that all is ready. Later Ajax requests will cause the data-ajax-active attribute to switch between "true" and "false", so some test logic may need to be driven by that instead.

            dom.body.attr "data-page-initialized", "true"

    exports = _.extend loadLibrariesAndInitialize,

Passed a list of initializers, executes each initializer in order. Due to asynchronous loading of modules, the exact order in which initializer functions are invoked is not predictable.

      initialize: (inits = [], callback) ->
        console.debug "Executing #{inits.length} inits"
        callbackCountdown = inits.length + 1

tracker gets invoked once after each require/callback, plus once extra (to handle the case where there are no inits). When the count hits zero, it invokes the callback (if there is one).

        tracker = ->
          callbackCountdown--

          if callbackCountdown is 0
            console.debug "All inits executed"
            callback() if callback

First value in each init is the qualified module name; anything after that are arguments to be passed to the identified function. A string is the name of a module to load, or function to invoke, that takes no parameters.

        for init in inits
          if _.isString init
            invokeInitializer tracker, init, []
          else
            [qualifiedName, initArguments...] = init
            invokeInitializer tracker, qualifiedName, initArguments

        tracker()

Pre-loads a number of libraries in order. When the last library is loaded, invokes the callback (with no parameters).

      loadLibraries: (libraries, callback) ->
        reducer = (callback, library) -> ->
          console.debug "Loading library #{library}"
          require [library], callback

        finalCallback = _.reduceRight libraries, reducer, callback

        finalCallback.call null

      evalJavaScript: (js) ->
        console.debug "Evaluating: #{js}"
        eval js

Triggers the focus event on the field, if the field exist. Focus occurs delayed 1/8th of a second, which helps ensure that other initializions on the page are in place.

  • fieldId - element id of field to focus on
      focus: (fieldId) ->
        field = dom fieldId

        if field
          _.delay (-> field.focus()), 125

Passed the response from an Ajax request, when the request is successful. This is used for any request that attaches partial-page-render data to the main JSON object response. If no such data is attached, the callback is simply invoked immediately. Otherwise, Tapestry processes the partial-page-render data. This may involve loading some number of JavaScript libraries and CSS style sheets, and a number of direct updates to the DOM. After DOM updates, the callback is invoked, passed the response (with any Tapestry-specific data removed). After the callback is invoked, page initializations occur. This method returns null.

  • response - the Ajax response object
  • callback - invoked after scripts are loaded, but before page initializations occur (may be null)
      handlePartialPageRenderResponse: (response, callback) ->

Capture the partial page response portion of the overall response, and then remove it so it doesn't interfere elsewhere.

        responseJSON = response.json or {}
        partial = responseJSON._tapestry
        delete responseJSON._tapestry

Extreme case: the data has a redirectURL which forces an immediate redirect to the URL. No other initialization or callback invocation occurs.

        if partial?.redirectURL
          window.location.href = partial.redirectURL
          return

        addStylesheets partial?.stylesheets

Make sure all libraries are loaded

        exports.loadLibraries partial?.libraries, ->

After libraries are loaded, update each zone:

          _(partial?.content).each ([id, content]) ->
            console.debug "Updating content for zone #{id}"

            zone = dom.wrap id

            if zone
              zone.trigger events.zone.update, { content }

Invoke the callback, if present. The callback may do its own content updates.

          callback and callback(response)

Lastly, perform initializations from the partial page render response.

          exports.initialize partial?.inits

        return