define ["underscore", "./console", "./dom", "./events"],
(_, console, dom, events) ->
pathPrefix = null
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.
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 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) ->
try
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}"
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
finally
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 be incremented (from 0) and decremented (when the requests complete).
dom.body.attr "data-page-initialized", "true"
for mask in dom.body.find ".pageloading-mask"
mask.remove()
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.
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.
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
if window.location.href is partial.redirectURL
window.location.reload true
else
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