10. Google Closure Library Extensions (incomplete)¶
Google Closure is a set of JavaScript tools, that Google uses to build many of their core products. It provides:
- A JavaScript Optimizer to build a distributable version of your application
- A comprehensive JavaScript library
- A templating system for JavaScript
- A JavaScript style checker and style fixer
- An enhanced stylesheet language that works with the optimizer to minifiy CSS.
Each one of these components is agnostic of the other. Closure is at the heart of building products with prestans.
Google Closure is unlike other JavaScript frameworks (e.g jQuery). An extremely central part of Closure tools is it’s compiler (which is not just a minifier), the Closure development philosophy is to use the abstractions and components made available by Closure library and allow the compiler to optimise it for production.
Note
It’s assumed that you are familiar with developing applications with Google Closure tools.
prestans provides a number of extensions to Closure Library, that ease and automate building rich JavaScript clients that consume your prestans API. Our current line up includes:
- REST Client, provides a pattern to create Xhr requests, manages the life cycle and parsers responses, also supports Attribute Fitlers.
- Types API, a client side replica of the prestans server types package assisting with parsing responses.
- Code generation tools to quickly produce client side stubs from your REST application models.
It’s expected that you will use the Google Closure dependency manager to load the prestans namespaces.
10.1. Installation¶
Our client library follows the same development philosophy as Google Closure library, although we make available downloadable versions of the client library it’s highly recommended that you reference our repository as an external source.
This allows you to keep up to date with our code base and benefit from the latest patches when you next compile.
Closure library does the same, and we ensure that we are leveraging off their latest developments.
10.1.1. Unit testing¶
/path/to/depswriter.py --root_with_prefix=". ../prestans" > deps.js
To run these unit tests you will need to start Google Chrome with --allow-file-access-from-files
parameter. Example on Mac OS X:
spock:docs devraj$ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --allow-file-access-from-files
10.2. Extending JavaScript namespaces¶
Models ensure the validity of data sent to and from the server. The application client should be as responsible validate data on the client side, ensuring that you never send an invalid request or you never accept an invalid response. Discussed later in this chapter are tools provided by prestans that auto generate Closure library compatible versions of your server side Models and Attribute Filters, needless to say our JSON client works seamlessly with these auto generated Models and Filters.
Auto generated code is accompanied with the curse of loosing local modifications (e.g adding a helper method or computed property) when you next run the auto generate process.
Consider the following scenario, prestans auto generates a Model class called User
, this uses the JavaScript namespace pdemo.data.model.User
, you now wish to write a function to say concatenate a user’s first and last name. The obvious approach is to use goog.inherits
to create a subclass of pdemo.data.model.User
. However for dynamic operations like parsing server responses maintaining the namespace is crucial.
Thanks to JavaScript’s dynamic nature and Closure’s excellent dependency management it’s quite easy to implement a pattern that closely resembles Objective-C Categories. The idea is to be able to maintain the custom code in a separate file and be able to dynamically merge it with the auto generated code during runtime.
To achieve this for our hypothetical User class, create a file called UserExtensions.js
, this will provide the namespace pdemo.data.model.UserExtension
and depend on pdemo.data.model.User
.
goog.provide('pdemo.data.model.UserExtension');
goog.require('pdemo.data.model.User');
# Closure will ensure that the namespace pdemo.data.model.UserExtension
# is available here, feel free to extend it
pdemo.data.model.User.prototype.getFullName = function() {
return this.getFirstName() + " " + this.getLastName();
};
Now where you want to create an instance of pdemo.data.model.User
, use the extension as the dependency pdemo.data.model.UserExtension
. This ensures that both the auto generated namespace and your extensions are available.
goog.provide('pdemo.ui.web.Renderer');
# This will make available the pdemo.data.model.User namespace with your extensions
goog.require('pdemo.data.model.UserExtension');
10.3. Types API¶
The Types API is a client side implementation of the prestans types API found on the server side. It assists in directly translating validation rules for Web based clients consuming REST services defined using prestans. Later in this chapter we demonstrate a set of tools that cut out the laborious job of creating client side stubs of your prestans models.
String
, wraps a stringInteger
, wraps a numberFloat
, wraps a numberBoolean
, wraps a booleanDateTime
, wraps a goog.date.DateTime and includes format configuration from the server side definition.Array
, extends goog.iter.Iterator enables you to usegoog.iter.forEach
, we wrap most of the useful methods provided by Closure iterables.Model
, wraps JavaScriptobject
Filter
is an configurable filter that you can pass with API calls, this translates back into attribute strings, discussed in Validating Requests.
10.3.1. Array¶
prestans.types.Array
extends goog.iter.Iterator
, allowing you to use goog.iter.forEach
to iterate.
isEmpty
isValid
append
insertAt
insertAfter
length
asArray
clone
Google Closur provides a number of useful methods
removeIf
remove
sort
clear
containsIf
contains
objectAtIndex
10.4. REST Client¶
prestans contains a ready made REST Client to allow you to easily make requests and unpack responses from a prestans enabled server API. Our client implementation is specific to be used with Google Closure and only speaks JSON.
The client has three important parts:
- Request Manager provided by
prestans.rest.json.Client
, this queues, manages, cancels requests and is responsible for firing callbacks on success and failure. Your application lodges all API call requests with an instance ofprestans.rest.json.Client
. It’s designed to be shared by your entire application. - Request provided by
prestans.rest.json.Request
is a formalised request that can be passed to a Request Manager. The Request constructor accepts a JSON payload with configuration information, this includs partial URL schemes, parameters, optional body and a format for the response. The Request Manager uses the responses format to parse the server response. - Response provided by
prestans.rest.json.Response
encapsulates a server response. It also contains a parsed copy of the server response expressed using prestans types.
The general idea is:
- To maintain a globally accessible Request Manager
- Formally define each Xhr operation as a Request object
- The Request Manager handles the life cycle of a Xhr call and call an endpoint in your application on success or failure
- Both these callbacks are provided an instance of
Response
containing the appropriate available information
10.4.1. Request Manager¶
First step is to create a request manager by instantiating prestans.rest.json.Client
, it takes the following parameters:
baseUrl
, to be consistent with the single point of origin constraint, we assume that all your API calls are prefixed with something like/api
. If you provide a base URL all your requests should provide URLs relative to the base. This also makes for eased maintenance in case you rearrange your application URLs.opt_numRetries
set to 0 by default, causing requests never to be retried. Xhr implementations are capable of retrying to reach the server in case of failure.
There’s a fair chance that your application might launch simultaneous Xhr requests, it’s also likely that you would want to cancel some requests on events e.g as the user clicks around names of artists to get a list of their albums, you want to cancel any previously unfinished calls if the user has clicked on another artist name.
Our request manager can work this, this is done by using a shared instance of the request manager across your application. The following code sample demonstrates how you might maintain a global Request Manager instance:
goog.provide('pdemo');
goog.require('prestans.rest.json.Client');
pdemo.GLOBALS = {
API_CLIENT: new prestans.rest.json.Client("/api", 0)
};
Then use the makeRequest
method on the Request Manager instance to dispatch API calls, it requires the following parameters:
request
is aprestans.rest.json.Request
object.callbackSuccessMethod
which is a reference to a function the Request Manager calls if the API call succeeds, the method will be passed a response object. Ensure you usegoog.bind
to bind your function to your namespace.callbackFailureMethod
optional reference to a function the Request Manager calls if the API call fails, this method will be passed a response object with failure information.opt_abortPreviousRequests
, asks the Request Manager to cancel all pending requests.
# Assume you have a request object
pdemo.GLOBALS.API_CLIENT.makeRequest(
request,
goog.bind(this.successCallback_, this),
goog.bind(this.failureCallback_, this),
false
);
Note
Request objects tell the manager if they are willing to be aborted, this is configurable per request lodged with the manager.
The second method the Request Manager provides is abortAllPendingRequests
, this accepts no parameters and is responsible for aborting any currently queued connections. The failure callback is not fired when requests are aborted.
10.4.1.1. Xhr Communication Events¶
The Request Manager raises the following events. These come in handy if your application requires global UI interactions e.g a Modal popup if network communication fails, or notification messages on success.
prestans.rest.json.Client.EventType.RESPONSE
, raised when a round trip succeeds, this would be raised even if your API raised an error code, e.g Bad Request or Service Unavailable.prestans.rest.json.Client.EventType.FAILURE
raised if a round trip fails.
Example of using goog.events.EventHandler
to listen to the Failure event:
goog.require('goog.events.EventHandler');
# and somewhere in one of your functions
this.eventHandler = new goog.events.EventHandler(this);
this.eventHandler_.listen(pdemo.GLOBALS.API_CLIENT, prestans.rest.json.Client.EventType.FAILURE, this.handleFailure_);
The event
object passed to the end points is of type prestans.rest.json.Client.Event
a subclass of goog.events.Event
. Call getResponse
method on the event to get the Response
object, this will give you access all the information about the request and it’s outcome.
10.4.2. Composing a Request¶
Requests prestans.rest.Request
prestans.rest.json.Request
identifier
unique string identifier for this request typecancelable
boolean value to determine if this request can be canceledhttpMethod
aprestans.net.HttpMethod
constantparameters
an array of key value pairs send as part of the URLrequestFilter
optional instance ofprestans.types.Filter
requestModel
optional instance ofprestans.types.Model
, this will be used to parse the response message bodyresponseFilter
optional instance ofprestans.types.Filter
, used to ignore fields in the responseresponseModel
Used to unpack the returned responsearrayElementTemplate
Used if response model is an arrayresponseModelElementTemplates
urlFormat
sprintf like string used internally with goog.string.formaturlArgs
a JavaScript array of parameters used withurlFormat
prestans.net.HttpMethod
encapsulate HTTP verbs as constants, currently supported verbs are:
prestans.net.HttpMethod.GET
prestans.net.HttpMethod.PUT
prestans.net.HttpMethod.POST
prestans.net.HttpMethod.DELETE
prestans.net.HttpMethod.PATCH
10.4.3. Reading a Response¶
requestIdentifier
The string identifier for the request type,statusCode
HTTP status code,responseModel
Class used to unpack response body,arrayElementTemplate
prestans.types.Model,responseModelElementTemplates
responseBody
JSON Object (Optional)