F2 apps are synonymous with modules, widgets and portlets. Think charts, portfolios, trade tickets, and screeners. F2 apps only need to be programmed once, no matter where they will be used. To start, F2 Apps are either:
-
-
-Display App
-
-
-A display app presents information to users in the form of a visible widget (using HTML, CSS, and JavaScript).
-
-
-Data App
-
-
-A data app is a content feed available in industry-standard formats including JSON, JSONP, RSS or app developer-designed XML.
-
-
-
-
For the purposes of the documentation on this page, we'll focus on developing display apps. Browse to The Basics: Framework for more background information about F2 apps.
To help you get started building an F2 app, browse through the resources below. To jump start your F2 app development, download the F2 app template (which now includes a basic container) or follow the instructions below.
In developing a more advanced container, the HTML document's body element would contain additional markup and allow for specific positioning or placement of apps. Additionally, more advanced containers could introduce features and functionality to their apps in the form of authentication APIs, streaming data feeds, federated search, etc. All containers must follow the F2 design guidelines.
-
-
-
Basic App
-
Create your basic F2 app manifest and save it as /path/to/your/manifest.js using this code below. Note the path to this file should be specified in the manifestUrl property within the _appConfigs array in your basic container (shown above).
Now with a basic container and a basic app, you can load your F2 container and expect to see:
-
-
In getting to this point, you've only scratched the surface of F2 containers and apps. Continue reading and understanding the F2 spec to build exactly the financial solutions that our customers want.
-
-
-
Sample Apps and Container
-
Good news! In the project repo on GitHub, you will find a basic container along with a number of sample apps which demonstrate functionality far beyond the basic app above. Once you clone or download the project repository, open the sample container by pointing your browser at:
-
http://localhost/F2/examples/container/
-
-
-
Configuration
-
It is assumed you will be developing F2 apps locally and have a localhost setup. The URLs mentioned in this specification also assume you have configured your F2 apps to run at http://localhost/F2/. The examples provided as part of the project repository demonstrate apps written in different languages (PHP, JavaScript, C#). While it is not a requirement you have a web server configured on your computer, it will certainly allow you to more deeply explore the sample apps.
Design considerations are an important first step when creating a new app. Content can range from news to research to multimedia, and content should be presented using Progressive Enhancement, Mobile First and Responsive Design methodologies. That is to say multimedia content, for example, should be shown plugin-free (using HTML5 video or audio elements) for capable browsers and fallback to Flash-based players for browsers that do not yet support HTML5 related technologies. (VideoJS is good example of open-source JavaScript and CSS "that makes it easier to work with and build on HTML5 video, today.")
-
If App Developers embed URLs back to their own websites or to third party sites, URLs must be opened in a new window as to not interrupt the experience of someone using the container. If authentication is required on an App Developer's site, this can be accomplished with pass-through authentication using encrypted URLs as discussed in Single Sign On.
-
-
Choices
-
In order to ensure that apps built using F2 are successful, they must be accessible. As such, F2 made choices for which open-source libraries and frameworks would be leveraged to reduce the level of effort across F2 adopters.
Ultimately, the responsibility of app design falls on either the Container or App Developer. In many cases, Container Developers will provide App Developers will visual designs, style guides or other assets required to ensure apps have the form and function for a given container. Container Developers may also provide CSS for App Developers to adhere to—which should be easy since F2 enforces a consistent HTML structure across all containers and apps.
-
-
-
-
-
Developing F2 Apps
-
Let's take a close look at how to build an F2 app. We'll explain how to get an F2 AppID, what the AppManifest is all about, what output format your app needs to support, how the contents of the AppContent.html property work, and the two hooks for adding form and function to your app: scripts and styles.
To develop an F2 app, you need a unique identifier called an AppID. This AppID will be unique to your app across the entire open financial framework ecosystem. The format of the AppID looks like this: com_companyName_appName, where the companyName "namespace" is your company name and appName is the name of your app.
-
As an example, your AppID could look like this:
-
com_acmecorp_watchlist
-
If you built more than one app while working at Acme Corporation, you could create more AppIDs. All of these are valid:
-
-
com_acmecorp_watchlist2
-
com_acmecorp_watchlist_big_and_tall
-
com_acmecorp_static_charts
-
com_acmecorp_interactive_charts
-
-
To guarantee uniqueness, we have provided an AppID generation service that allows you to customize your AppID.
Once you have your AppID, start by setting up your project. You will need at least one file: the App Manifest. Create a new file called manifest.js. Also, chances are you'll want custom styling and functionality, so go ahead and create appclass.js (for your app logic) and app.css for your CSS. Your project folder should look like this:
The App Manifest can be generated by the server-side code of your choice or be written-by-hand in your favorite text editor. In the GitHub repository, there are apps written in JavaScript, PHP, and C# to serve as examples to get you started.
-
When it's complete (using the examples further below), the App Manifest looks like this:
Let's break the App Manifest object down and look at each property (in reverse order to keep it fun).
-
-
-
Apps
-
The apps property is an array of AppContent objects. Each AppContent object contains three properties:
-
-
html
-
data
-
status
-
-
-
html
-
The html property contains the view of your app represented in (optionally encoded) HTML. While you can modify the way your app appears or functions within the container, the html property is what the container will show when it registers your app and displays its contents for the first time.
The data property exists to support the placement of arbitrary data needing to be passed along with the app. This field is optional.
-
Example:
-
"data": {
- foo: "bar",
- value: 12345
-}
-
-
-
status
-
The status property allows app developers to communicate a server-side arbitrary status code to itself or to the container. This field is optional.
-
Example:
-
"status": "good"
-
-
-
-
Styles
-
The styles property is an array of URLs. The styles array refers to any CSS files needed by the app so it will be displayed properly on the container. The externally-referenced CSS files should be fully-qualified, including a protocol.
Note URLs referenced in the Scripts and Styles arrays are loaded synchronously by F2.js, so be sure to order your scripts properly.
-
-
-
Scripts
-
The scripts property is an array of URLs. The scripts array refers to any JavaScript files needed by the app so that it will function correctly on the container. The externally-referenced JS files should be fully-qualified.
Note URLs referenced in the Scripts and Styles arrays are loaded synchronously by F2.js, so be sure to order your scripts properly.
-
-
-
Inline Scripts
-
The inlineScripts property is an array of strings. The inlineScripts array can include any JavaScript code needed by the app that cannot be included in your App Class. The contents of the inlineScripts array will be evaluated as JavaScript (using eval()) when all scripts have finished loading.
-While the use of inlineScripts is supported by F2's App Manifest, it is not recommended for use. There are many reasons for this, the main one is to avoid cluttering the global namespace. Developers should make every attempt to put their JavaScript code inside their App Class.
-
Note You may have noticed the presence of the function name F2_jsonpCallback_com_companyname_appname on the first line of the example above. That function name is the callback and is explained in App Manifest Response.
-
-
-
App Manifest Response
-
OK, so you know about F2 apps and you're ready to write your own App Manifest. To go from zero to something, download the F2 app starter zip. Once you have your AppManifest defined (or at least stubbed out), there's one important detail you need to know now—the App Manifest response format.
-
As part of F2, containers register apps—typically hosted on different domains—using JSONP. This means F2 App Manifest files must provide a JSONP callback function. (If you don't know what JSONP is or how it works, we recommend reading what Remy Sharp has to say about it.)
-
For security reasons, the App Manifest JSONP callback function must be a specific, reliable, and testable format. F2 has defined that using a combination of a string and your unique F2 AppID. The JSONP callback function name looks like this:
-
F2_jsonpCallback_<AppID>
-
When applied, the final (bare bones) App Manifest file looks like this example (where com_companyname_appname is your AppID):
Note the JSONP callback function name will not be passed from the container using a traditional querystring parameter (HTTP GET), so you must configure this correctly for your app to appear on a container. This means you have to hard-code it in your appmanifest.js.
-
Required Don't forget you need an AppID before you can run your app on a container. Get your AppID now »
-
-
-
App HTML
-
Every F2 app has HTML. The only catch is that the HTML isn't provided by the app itself but rather passed to the container via the app's AppManifest. But that's not a problem because F2 has provided examples to show you the way. Here are the steps for getting your app HTML into your AppContent.html property:
-
-
Develop the web page or module or widget or component or portlet that will be your app.
-
Take all the contents of it—that is, the HTML—and encode it. (This step is optional.)
-
Put the (optionally encoded) result in the html property of your AppContent object within your App Manifest file's App object.
Note You are not required to encode the app HTML, so follow steps 2 and 3 above omitting the encoding step.
-
-
Automatic Consistency
-
F2 uses and recommends Twitter Bootstrap for Container and App Developers to benefit from a consistent HTML and CSS structure regardless of who developed the F2 component. This way, Container Developers can write CSS they know will style F2 apps without engaging with the app developer to ensure compatability.
Once your app is on the container, chances are you'll want it to actually do something. As an app developer, it is entirely up to you to write your own stylesheets and javascript code to add form and function to your app. F2's standardized App Manifest provides hooks for your CSS and scripts to get onto the container—just use the scripts and styles arrays detailed above in the App Manifest.
-
-
-
Styles
-
Including your own CSS in the styles array of the App Manifest opens the door to the potential of unexpected display issues. Therefore, as an app developer, you are required to properly namespace your CSS selectors and declarations. For the details on writing correctly namespaced code, read the namespacing docs.
-
It is recommended you include your app styles in a file named app.css.
-
-
-
Scripts
-
-
App Class
-
While it isn't required, it's expected all F2 apps will ship with javascript. This code should be included in an appclass.js file as shown in Setting Up Your Project. The F2.Apps property is a namespace for app developers to place the javascript class that is used to initialize their app. The javascript classes should be namepaced with the F2.App.AppID. It is recommended that the code be placed in a closure to help keep the global namespace clean.
To make it even easier to build F2 apps and for faster app loading by the container, the F2.js SDK provides automatic JavaScript method execution at appropriate times during F2.registerApps() (and the internal _loadApps() method). If the class has an init() function, it will be called automatically during execution of F2's registerApps() method.
-
We recommend—and have samples below for—two different patterns for writing your appclass.js code: prototypal inheritence or the module pattern.
-
-
-
Arguments
-
When F2's registerApps() method is called by the container, F2 passes three arguments to your App Class: appConfig, appContent and root. The SDK documentation details the contents of each arg and these should be familiar because appConfig contains your apps' meta, appContent contains your html, data and status properties, and root is the outermost DOM element in which your app exists on the container. The root argument provides your App Class code your apps' parent element for faster DOM traversal.
We won't even begin to talk about or describe this fantastic design pattern simply because Douglas Crockford has already writtenall about it.
-
An example of an App Class using prototypal inheritance inside a closure is below. Note the inclusion of the App_Class.prototype.init() function—which will be called automatically during app load—and the trailing parentheses, (), which are responsible for automatic function execution. Thanks to the closure, the App_Class is returned and assigned to F2.Apps["com_companyname_appname"].
As an alternative to the prototypal inheritance pattern above, appclass.js code could be written following the module pattern shown in the example below. Note the inclusion of an init() function—which will be called automatically during app load—and the exclusion of the closure and trailing parentheses present in the example using prototypal inheritance above.
Of course, you don't have to use either one of these patterns in your appclass.js file. What you do have to use is a function. That is to say the value assigned to F2.Apps["com_companyname_appname"] by your App Class code must be a function. Within F2's registerApps() method, the new operator is used which produces an object (check out the code).
-
-
-Important!
-
-
-In the absence of a function in your appclass.js, F2 will be unable to load your app on a container.
-
F2 is a web integration framework which means apps are inherently insecure—at least those non-secure apps. Following this spec, App Developers must avoid CSS collisions and JavaScript namespace issues to provide users with the best possible experience.
As discussed in Developing F2 Apps: F2 AppID, to develop an F2 app, you need a unique identifier called an AppID. This AppID will be unique to your app across the entire open financial framework ecosystem. The format of the AppID looks like this: com_companyName_appName, where the companyName "namespace" is your company name and appName is the name of your app.
-
When Container Developers register apps, F2.js draws each app as defined by the ContainerConfig. Before the app is added to the container DOM, F2 automatically wraps an outer HTML element—with the AppID used as a class—around the rendered app.
-
This example shows app HTML after it has been drawn on the container. Note the com_companyName_appName classname.
To avoid styling conflicts or other display issues related to app-provided style sheets, App Developers must namespace their CSS selectors. Fortunately, this is quite easy.
-
Every selector in app-provided style sheets must look like this:
Adhering to one of the OpenAjax Alliance goals, F2 also promotes the concept of an uncluttered global javascript namespace. For Container and App Developers alike, this means following this spec closely and ensuring javascript code is contained inside closures or is extended as a new namespace on F2.
-
To ensure javascript bundled with F2 apps executes in a javascript closure, follow the guidelines for the appclass.js file and one of the two patterns described (prototypal inheritance or module).
-
The F2.js SDK was designed with extensibility in mind and therefore custom logic can be added on the F2 namespace.
-
Example:
-
F2.extend('YourPluginName', (function(){
- return {
- doSomething: function(){
- F2.log("Something has been done.");
- }
- };
-})());
Apps are capable of sharing "context" with the container and other nearby apps. All apps have context which means the app "knows" who is using it and the content it contains. It is aware of an individual's data entitlements and user information that the container is requested to share (name, email, company, etc).
-
This means if a user wants to create a ticker-focused container so they can keep a close eye on shares of Proctor & Gamble, the container can send "symbol context" to any listening apps that are smart enough to refresh when ticker symbol PG is entered in the container's search box.
-
While apps can have context themselves, the responsibility for managing context switching or context passing falls on the container. The container assumes the role of a traffic cop—managing which data goes where. By using JavaScript events, the container can listen for events sent by apps and likewise apps can listen for events sent by the container. To provide a layer of security, this means apps cannot communicate directly with other apps on their own; apps must communicate via an F2 container to other apps since the container controls the F2.Events API.
In this example, the container broadcasts, or emits, a javascript event defined in F2.Events.Constants. The F2.Events.emit() method accepts two arguments: the event name and an optional data object.
To listen to the F2.Constants.Events.CONTAINER_SYMBOL_CHANGE event inside your F2 app, you can use this code to trigger an alert dialog with the symbol:
-
F2.Events.on(
- F2.Constants.Events.CONTAINER_SYMBOL_CHANGE,
- function(data){
- F2.log("The symbol was changed to " + data.symbol);
- }
-);
-
The F2.Events.on() method accepts the event name and listener function as arguments. Read the SDK for more information.
-
Note For a full list of support event types, browse to the SDK for F2.Constants.Events.
-
-
-
Container-to-App Context (Server)
-
Often times containers will want to send context to apps during app registration. This is possible through the AppConfig.context property. This property can contain any javascript object—a string, a number, an array or an object.
When F2.registerApps() is called, the appConfig is serialized and posted to the app's manifest URL. The serialized object converts to stringified JSON:
The appConfig object is sent to the server using the params querystring name as shown in the example below. This is the complete app manifest request sent by F2.registerApps() with the appConfig URL-encoded, of course:
This demonstrates complete flexibility of passing arbitrary context values from the container to any F2 app.
-
Important To receive context from a container during app initialization, F2 App Developers are required to build object deserialization for the params value into their app code.
-
-
-
App-to-Container Context
-
In this example, your app emits an event indicating a user is looking at a different stock ticker within your app. Using F2.Events.emit() in your code, your app broadcasts the new symbol. As with container-to-app context passing, the F2.Events.emit() method accepts two arguments: the event name and an optional data object.
The container would need to listen to your apps' broadcasted F2.Constants.Events.APP_SYMBOL_CHANGE event using code like this:
-
F2.Events.on(
- F2.Constants.Events.APP_SYMBOL_CHANGE,
- function(data){
- F2.log("The symbol was changed to " + data.symbol);
- }
-);
-
Note For a full list of support event types, browse to the SDK for F2.Constants.Events.
-
-
-
App-to-App Context
-
Apps can also pass context between apps. If there are two or more apps on a container with similar context and the ability to receive messages (yes, through event listeners, context receiving is opt-in), apps can communicate with each other. To communicate with another app, each app will have to know the event name along with the type of data being passed. Let's take a look.
-
Within "App 1", context is sent using F2.Events.emit():
Within "App 2", context is received using F2.Events.on():
-
F2.Events.on(
- "buy_stock",
- function(data){
- if (data.isAvailableToPurchase){
- F2.log("Trade ticket order for " + data.symbol + " at $" + data.price);
- } else {
- F2.log("This stock is not available for purchase.")
- }
- }
-);
-
-
-
More Complex Context
-
The examples above demonstrate simple Context objects. In the event more complex data and/or data types are needed, F2 Context can support any JavaScript object—a string, a number, a function, an array or an object.
-
This is an example Context object demonstrating arbitrary JavaScript objects:
The F2 app listening for the buy_stock event would fire the callback function.
-
F2.Events.on(
- "buy_stock",
- function(data){
- F2.log("Trade ticket order for " + data.symbol + " at $" + data.price);
- //..populate the trade ticket...
- //fire the callback
- if (typeofdata.callback === 'function'){
- data.callback();
- }
- }
-);
-
-
-
Types of Context
-
Context is a term used to describe the state of an F2 container and its apps. At the same time, Context is also the information passed from Container-to-App or from App-to-App or from App-to-Container. In the examples shown above, two types of context were shown: symbol and trade ticket context. It is important to realize F2.js allows client-side messaging between third parties using a collection of arbitrary name-value pairs. This provides the utmost flexibility and affords Container Developers the option to define context within their container.
-
-
Universal F2 Instrument ID
-
Said another way, while { symbol:"AAPL", name: "Apple, Inc" } can be used to communicate symbol context, developers could also use { symbol: "123456789" } to identify Apple, Inc. The latter is more likely given not all apps would programmatically understand AAPL but—given symbol lookup services—would understand 123456789 as the universal F2 identifier for Apple, Inc. It is clear Container and App Developers alike would prefer to communicate with a guaranteed-to-never-change universal ID for all instrument types across all asset classes.
-
F2 will be providing lookup web services in future releases that provide universal F2 identifiers for container and app providers. These lookup services will not just be limited to symbols. Further details will be forthcoming as the F2 specification evolves.
-
-
-
-
-
-
Secure Apps
-
F2 fully supports secure apps. A secure app is one that exists inside an iframe on a container and is hosted on a different domain. The F2.js SDK provides developers with seamless handling of Context, UI and the other F2 APIs whether or not an app is secure. This means app developers do not have to code apps any differently if an app is secure.
-
An app is defined as "secure" in the AppConfig. Creating the AppConfig is something that is done when apps are registered on the Developer Center.
-
Noting the isSecure property, the AppConfig looks like this:
To see examples of secure apps, fork F2 on GitHub and point your browser at:
-
http://localhost/F2/examples/container/
-
The example container runs sample apps—defined in sampleApps.js—and that's where you'll find the isSecure flag defined in some of the AppConfig objects.
-
-
-
-
Container Integration
-
Good news! The container is responsible for loading its apps, and as long as you've followed F2's standard for App Manifests and have a working—and tested—app, you're pretty much done.
When you cloned the F2 GitHub repo you also got an example F2 container for your app development and testing. Open the project repository and navigate to ~/F2/examples/container to find them or to jump-start your testing, point your browser at:
-
http://localhost/F2/examples/container/
-
If you open ~/F2/examples/container/js/sampleApps.js in your text editor, you'll find a list of sample F2 apps broken down by programming language. Simply modify this file to your liking and add your app anywhere in the appropriate array (JavaScript, PHP or C#). The configuration is comprised of F2.AppConfig properties, and the following are the minimum required properties.
There are some utility methods provided within F2.js in the UI namespace. These helpers are for controlling layout, showing (or hiding) loading spinners, modals, managing views within your app, and more.
-
-
Basics
-
While there are numerous utility methods in F2.UI, we will focus on a couple important ones here such as updateHeight() and showMask(). The F2.UI methods are passed as an instance to each F2 apps' App Class on the appConfig argument. The instance of F2.UI gets added to the appConfig object at runtime, and is available in appclass.js as appConfig.ui.
As the layout inside your app changes, your app should update or refresh its height on the container. This is particularly important for those secure apps inside iframes. To handle this, F2.js provides a updateHeight() method you should call anytime a DOM element is added or removed from within an app.
-
Assuming the example above is used, the this.ui property holds the instance of appConfig.ui.
Apps can show loading spinners—or "masks"—when they are being loaded by a container or afterwards when making data requests. Container Developers configure the UI.Mask as discussed in the SDK F2.ContainerConfig.UI.Mask docs, therefore it is simple for app developers to call showMask().
-
To show a loading spinner when making an ajax request within an app:
The showMask() method takes two arguments: a DOM element where to show the mask and a boolean indicating whether or not to show a spinning graphic.
-
If you do not want to show a spinning graphic, simply pass false to the showMask() method. A mask without a spinner is useful in the case when you want to "lock" the view from user interaction.
//appclass.js snippet
-...
- this.ui.setTitle("Chart for MSFT");
-...
-
-
-
-
F2.UI.Modals
-
F2.js provides two methods in F2.UI for modal dialogs. F2 uses and recommends Twitter Bootstrap for many reasons, and taking advantage of Bootstrap's modals was an easy choice.
-
For full details on F2.UI.Modals and the two types of modals (alert and confirm), read the SDK docs.
-
Usage is simple:
-
//appclass.js snippet
-...
- this.ui.Modals.alert("A message to display in a modal.");
-...
-
You can optionally provide a callback to be fired when the user closes the modal.
-
//appclass.js snippet
-...
- this.ui.Modals.alert("A message to display in a modal.", function(){
- F2.log("Modal closed!");
- });
-...
-
Additionally, there is a confirm modal.
-
//appclass.js snippet
-...
- this.ui.Modals.confirm(
- "A message to display in a confirmation modal.",
- function(){
- F2.log("OK clicked");
- },
- function(){
- F2.log("Cancel clicked");
- }
- );
-...
-
-
-
F2.UI.Views
-
Adding and managing views within an F2 app is considered an advanced topic. If your app only needs a single view, you don't have to worry about reading this part of the F2 spec.
-
F2 apps can have one or more views. Every app will have at least one "home" view, while others will include views for settings, help or about. Inside the F2.js SDK, we've included support for views and the list can be extended by the container provider.
-
Note If the container doesn't support all the views you need inside your app, you will need to coordinate those additions with the container provider.
-
-
Setting Up Views
-
Once you've determined the views you'd like to include in your app, the view should be specified by applying the F2.Constants.Css.APPVIEW classname to the containing DOM Element. A data- attribute should be added to the element as well which defines what view type is represented. Twitter Bootstrap's hide class should be applied to views that are hidden on startup.
-
To setup a single view in your app, use this HTML on your app's outermost element noting the use of the f2-app-view classname and the data-f2-view attribute.
Note When appConfig.ui.Views.change() is called, the hide classname is automatically added or removed by F2.js depending on the visibility of the view. Read more in the SDK docs.
-
In F2 app HTML, you can use a combination of CSS classnames and data- attributes to provide UI elements making it easy for users to navigate between Views.
-
For example, an app has two views: "home" and "about". On the "home" View, a button allows the user to navigate to the "about" view. In the presence of the classname f2-app-view-trigger and the data-f2-view data attribute, F2.js automatically adds a javascript event to the button.
-
<div class="f2-app-view" data-f2-view="home">
- <!--use 'data-f2-view' to switch to the "about" View-->
- <button class="btn f2-app-view-trigger" data-f2-view="about">Show About View</button>
-</div>
-
To get back to the "home" View from the "about" View:
You shouldn't be surprised to know F2.js contains event triggers for handling app View changes. To listen for View changes inside F2 app javascript code:
-
appConfig.ui.Views.change(function(view){
- F2.log("View changed to ", view);
-});
User or content entitlements are the responsibility of the App Developer. Many apps will need to be decoupled from the content that they need. This could include apps like research aggregation, news filtering, streaming market data, etc. Similarly to how companies build their own websites today with their own authentication and access (or content) entitlements, F2 apps are no different.
-
Further details around app entitlements will be forthcoming as the F2 specification evolves.
-
-
-
-
Single Sign-On
-
Single sign-on (SSO) is a shared responsibility between the Container and App Developer. In some cases, containers will want all of its apps to be authenticated seamlessly for users;that will be negotiated between Container and App Developers. For the purposes of this documentation, it is assumed Container Developers will build and host their container access authentication.
-
Once a user is authenticated on the container, how is the user then authenticated with all of the apps? Encrypted URLs.*
-
Note The Container Developer is free to utilize any app authentication method they deem fit. Container Developers and app developers will need to work together to finalize the authentication details.
-
-
Using Encrypted URLs
-
Implementing SSO using encrypted URLs is a simple and straight-forward authentication mechanism for securing cross-domain multi-provider apps. To guarantee security between the Container and App Developer, secure API contracts must be negotiated. This includes, but is not limited to, the choice of cryptographic algorithm (such as AES) and the exchange of public keys.
-
When the container provider calls F2.registerApps(), custom logic should be added to append encrypted user credentials—on a need-to-know basis—to each app requiring authentication.
Authentication is a critical part of any container-app relationship. There are a plethora of SSO implementations and there are many considerations for both Container and App Developers alike.
-
Further details around container and app single sign-on will be forthcoming as the F2 specification evolves.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/js/f2.js b/docs/js/f2.js
index 78bb3ace..121000a6 100644
--- a/docs/js/f2.js
+++ b/docs/js/f2.js
@@ -120,7 +120,7 @@ F2.extend("Constants",{Css:function(){var e="f2-";return{APP:e+"app",APP_CONTAIN
F2.extend("Events",function(){var e=new EventEmitter2({wildcard:!0});return e.setMaxListeners(0),{_socketEmit:function(){return EventEmitter2.prototype.emit.apply(e,[].slice.call(arguments))},emit:function(){return F2.Rpc.broadcast(F2.Constants.Sockets.EVENT,[].slice.call(arguments)),EventEmitter2.prototype.emit.apply(e,[].slice.call(arguments))},many:function(t,n,r){return e.many(t,n,r)},off:function(t,n){return e.off(t,n)},on:function(t,n){return e.on(t,n)},once:function(t,n){return e.once(t,n)}}}());
F2.extend("Rpc",function(){var e={},t="",n={},r=new RegExp("^"+F2.Constants.Sockets.EVENT),i=new RegExp("^"+F2.Constants.Sockets.RPC),s=new RegExp("^"+F2.Constants.Sockets.RPC_CALLBACK),o=new RegExp("^"+F2.Constants.Sockets.LOAD),u=new RegExp("^"+F2.Constants.Sockets.UI_RPC),a=function(){var e,t=!1,r=[],i=new easyXDM.Socket({onMessage:function(s,u){if(!t&&o.test(s)){s=s.replace(o,"");var a=F2.parse(s);a.length==2&&(e=a[0],n[e.instanceId]={config:e,socket:i},F2.registerApps([e],[a[1]]),jQuery.each(r,function(t,n){c(e,s,u)}),t=!0)}else t?c(e,s,u):r.push(s)}})},f=function(e,n){var r=jQuery(e.root);r=r.is("."+F2.Constants.Css.APP_CONTAINER)?r:r.find("."+F2.Constants.Css.APP_CONTAINER);if(!r.length){F2.log("Unable to locate app in order to establish secure connection.");return}var i={scrolling:"no",style:{width:"100%"}};e.height&&(i.style.height=e.height+"px");var s=new easyXDM.Socket({remote:t,container:r.get(0),props:i,onMessage:function(t,n){c(e,t,n)},onReady:function(){s.postMessage(F2.Constants.Sockets.LOAD+F2.stringify([e,n],F2.appConfigReplacer))}});return s},l=function(e,t){return function(){F2.Rpc.call(e,F2.Constants.Sockets.RPC_CALLBACK,t,[].slice.call(arguments).slice(2))}},c=function(t,n,o){function f(e,t){var n=String(t).split(".");for(var r=0;r','',"
Alert!
","",'
',"
",e,"
","
",'",""].join("")},n=function(e){return['
','',"
Confirm
","",'
',"
",e,"
","
",'","
"].join("")};return{alert:function(n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.alert()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.alert",[].slice.call(arguments)):jQuery(e(n)).on("show",function(){var e=this;jQuery(e).find(".btn-primary").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()})}).modal({backdrop:!0})},confirm:function(e,r,i){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.confirm()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.confirm",[].slice.call(arguments)):jQuery(n(e)).on("show",function(){var e=this;jQuery(e).find(".btn-ok").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()}),jQuery(e).find(".btn-cancel").on("click",function(){jQuery(e).modal("hide").remove(),(i||jQuery.noop)()})}).modal({backdrop:!0})}}}(),setTitle:function(e){F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"setTitle",[e]):jQuery(t.root).find("."+F2.Constants.Css.APP_TITLE).text(e)},showMask:function(e,n){F2.UI.showMask(t.instanceId,e,n)},updateHeight:r,Views:function(){var e=new EventEmitter2,i=/change/i;e.setMaxListeners(0);var s=function(e){return i.test(e)?!0:(F2.log('"'+e+'" is not a valid F2.UI.Views event name'),!1)};return{change:function(i){typeof i=="function"?this.on("change",i):typeof i=="string"&&(t.isSecure&&!F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Views.change",[].slice.call(arguments)):F2.inArray(i,t.views)&&(jQuery("."+F2.Constants.Css.APP_VIEW,n).addClass("hide").filter('[data-f2-view="'+i+'"]',n).removeClass("hide"),r(),e.emit("change",i)))},off:function(t,n){s(t)&&e.off(t,n)},on:function(t,n){s(t)&&e.on(t,n)}}}()}};return t.hideMask=function(e,t){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.hideMask()");return}if(F2.Rpc.isRemote(e)&&!jQuery(t).is("."+F2.Constants.Css.APP))F2.Rpc.call(e,F2.Constants.Sockets.RPC,"F2.UI.hideMask",[e,jQuery(t).selector]);else{var n=jQuery(t),r=n.find("> ."+F2.Constants.Css.MASK).remove();n.removeClass(F2.Constants.Css.MASK_CONTAINER),n.data(F2.Constants.Css.MASK_CONTAINER)&&n.css({position:"static"})}},t.init=function(t){e=t,e.UI=jQuery.extend(!0,{},F2.ContainerConfig.UI,e.UI||{})},t.showMask=function(t,n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.showMask()");return}if(F2.Rpc.isRemote(t)&&jQuery(n).is("."+F2.Constants.Css.APP))F2.Rpc.call(t,F2.Constants.Sockets.RPC,"F2.UI.showMask",[t,jQuery(n).selector,r]);else{r&&!e.UI.Mask.loadingIcon&&F2.log("Unable to display loading icon. Please set F2.ContainerConfig.UI.Mask.loadingIcon when calling F2.init();");var i=jQuery(n).addClass(F2.Constants.Css.MASK_CONTAINER),s=jQuery("
").height("100%").width("100%").addClass(F2.Constants.Css.MASK);e.UI.Mask.useClasses||s.css({"background-color":e.UI.Mask.backgroundColor,"background-image":e.UI.Mask.loadingIcon?"url("+e.UI.Mask.loadingIcon+")":"","background-position":"50% 50%","background-repeat":"no-repeat",display:"block",left:0,"min-height":30,padding:0,position:"absolute",top:0,"z-index":e.UI.Mask.zIndex,filter:"alpha(opacity="+e.UI.Mask.opacity*100+")",opacity:e.UI.Mask.opacity}),i.css("position")==="static"&&(i.css({position:"relative"}),i.data(F2.Constants.Css.MASK_CONTAINER,!0)),i.append(s)}},t}());
-F2.extend("",function(){var _apps={},_config=!1,_afterAppRender=function(e,t){var n=_config.afterAppRender||function(e,t){return jQuery(t).appendTo("body")},r=n(e,t);if(!!_config.afterAppRender&&!r){F2.log("F2.ContainerConfig.afterAppRender() must return the DOM Element that contains the app");return}return jQuery(r).addClass(F2.Constants.Css.APP),r.get(0)},_appRender=function(e,t){function n(e){return jQuery("").append(e).html()}return t=n(jQuery(t).addClass(F2.Constants.Css.APP_CONTAINER+" "+e.appId)),_config.appRender&&(t=_config.appRender(e,t)),n(t)},_beforeAppRender=function(e){var t=_config.beforeAppRender||jQuery.noop;return t(e)},_hydrateAppConfig=function(e){e.instanceId=e.instanceId||F2.guid(),e.views=e.views||[],F2.inArray(F2.Constants.Views.HOME,e.views)||e.views.push(F2.Constants.Views.HOME)},_initAppEvents=function(e){jQuery(e.root).on("click","."+F2.Constants.Css.APP_VIEW_TRIGGER+"["+F2.Constants.Views.DATA_ATTRIBUTE+"]",function(t){t.preventDefault();var n=jQuery(this).attr(F2.Constants.Views.DATA_ATTRIBUTE).toLowerCase();n==F2.Constants.Views.REMOVE?F2.removeApp(e.instanceId):e.ui.Views.change(n)})},_initContainerEvents=function(){var e,t=function(){F2.Events.emit(F2.Constants.Events.CONTAINER_WIDTH_CHANGE)};jQuery(window).on("resize",function(){clearTimeout(e),e=setTimeout(t,100)})},_isInit=function(){return!!_config},_loadApps=function(appConfigs,appManifest){appConfigs=[].concat(appConfigs);if(appConfigs.length==1&&appConfigs[0].isSecure&&!_config.isSecureAppPage){_loadSecureApp(appConfigs[0],appManifest);return}if(appConfigs.length!=appManifest.apps.length){F2.log("The number of apps defined in the AppManifest do not match the number requested.",appManifest);return}var scripts=appManifest.scripts||[],styles=appManifest.styles||[],inlines=appManifest.inlineScripts||[],scriptCount=scripts.length,scriptsLoaded=0,appInit=function(){jQuery.each(appConfigs,function(e,t){t.ui=new F2.UI(t),F2.Apps[t.appId]!==undefined&&(typeof F2.Apps[t.appId]=="function"?setTimeout(function(){_apps[t.instanceId].app=new F2.Apps[t.appId](t,appManifest.apps[e],t.root),_apps[t.instanceId].app.init!==undefined&&_apps[t.instanceId].app.init()},0):F2.log("app initialization class is defined but not a function. ("+t.appId+")"))})},stylesFragment=[];jQuery.each(styles,function(e,t){stylesFragment.push('')}),jQuery("head").append(stylesFragment.join("")),jQuery.each(appManifest.apps,function(e,t){appConfigs[e].root=_afterAppRender(appConfigs[e],_appRender(appConfigs[e],t.html)),_initAppEvents(appConfigs[e])}),jQuery.each(scripts,function(i,e){jQuery.ajax({url:e,cache:!0,async:!1,dataType:"script",type:"GET",success:function(){++scriptsLoaded==scriptCount&&(jQuery.each(inlines,function(i,e){try{eval(e)}catch(exception){F2.log("Error loading inline script: "+exception+"\n\n"+e)}}),appInit())},error:function(t,n,r){F2.log(["Failed to load script ("+e+")",r.toString()])}})}),scriptCount||appInit()},_loadSecureApp=function(e,t){_config.secureAppPagePath?(e.root=_afterAppRender(e,_appRender(e,"")),e.ui=new F2.UI(e),_initAppEvents(e),F2.Rpc.register(e,t)):F2.log('Unable to load secure app: "secureAppPagePath" is not defined in F2.ContainerConfig.')},_validateApp=function(e){return e.appId?e.manifestUrl?!0:(F2.log('manifestUrl" missing from app object'),!1):(F2.log('"appId" missing from app object'),!1)};return{getContainerState:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.getContainerState()");return}return jQuery.map(_apps,function(e,t){return{appId:e.config.appId}})},init:function(e){_config=e||{},(!!_config.secureAppPagePath||_config.isSecureAppPage)&&F2.Rpc.init(_config.secureAppPagePath?_config.secureAppPagePath:!1),F2.UI.init(_config),_config.isSecureAppPage||_initContainerEvents()},isInit:_isInit,registerApps:function(e,t){if(!_isInit()){F2.log("F2.init() must be called before F2.registerApps()");return}if(!e){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}var n=[],r={},i={},s=!1;e=[].concat(e),t=t||[],s=!!t.length;if(!e.length){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}if(e.length&&s&&e.length!=t.length){F2.log('The length of "apps" does not equal the length of "appManifests"');return}jQuery.each(e,function(e,i){if(!_validateApp(i))return;_hydrateAppConfig(i),i.root=_beforeAppRender(i),_apps[i.instanceId]={config:i},s?_loadApps(i,t[e]):i.enableBatchRequests&&!i.isSecure?(r[i.manifestUrl.toLowerCase()]=r[i.manifestUrl.toLowerCase()]||[],r[i.manifestUrl.toLowerCase()].push(i)):n.push({apps:[i],url:i.manifestUrl})}),s||(jQuery.each(r,function(e,t){n.push({url:e,apps:t})}),jQuery.each(n,function(e,t){var n=F2.Constants.JSONP_CALLBACK+t.apps[0].appId;i[n]=i[n]||[],i[n].push(t)}),jQuery.each(i,function(e,t){var n=function(r,i){if(!i)return;jQuery.ajax({url:i.url,data:{params:F2.stringify(i.apps,F2.appConfigReplacer)},jsonp:!1,jsonpCallback:r,dataType:"jsonp",success:function(e){_loadApps(i.apps,e)},error:function(e,t,n){F2.log("Failed to load app(s)",n.toString(),i.apps),jQuery.each(i.apps,function(e,t){F2.log("Removed failed "+t.name+" app",t),F2.removeApp(t.instanceId)})},complete:function(){n(e,t.pop())}})};n(e,t.pop())}))},removeAllApps:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.removeAllApps()");return}jQuery.each(_apps,function(e,t){F2.removeApp(t.config.instanceId)})},removeApp:function(e){if(!_isInit()){F2.log("F2.init() must be called before F2.removeApp()");return}_apps[e]&&(jQuery(_apps[e].config.root).fadeOut(function(){jQuery(this).remove()}),delete _apps[e])}}}());
+F2.extend("",function(){var _apps={},_config=!1,_afterAppRender=function(e,t){var n=_config.afterAppRender||function(e,t){return jQuery(t).appendTo("body")},r=n(e,t);if(!!_config.afterAppRender&&!r){F2.log("F2.ContainerConfig.afterAppRender() must return the DOM Element that contains the app");return}return jQuery(r).addClass(F2.Constants.Css.APP),r.get(0)},_appRender=function(e,t){function n(e){return jQuery("").append(e).html()}return t=n(jQuery(t).addClass(F2.Constants.Css.APP_CONTAINER+" "+e.appId)),_config.appRender&&(t=_config.appRender(e,t)),n(t)},_beforeAppRender=function(e){var t=_config.beforeAppRender||jQuery.noop;return t(e)},_hydrateAppConfig=function(e){e.instanceId=e.instanceId||F2.guid(),e.views=e.views||[],F2.inArray(F2.Constants.Views.HOME,e.views)||e.views.push(F2.Constants.Views.HOME)},_initAppEvents=function(e){jQuery(e.root).on("click","."+F2.Constants.Css.APP_VIEW_TRIGGER+"["+F2.Constants.Views.DATA_ATTRIBUTE+"]",function(t){t.preventDefault();var n=jQuery(this).attr(F2.Constants.Views.DATA_ATTRIBUTE).toLowerCase();n==F2.Constants.Views.REMOVE?F2.removeApp(e.instanceId):e.ui.Views.change(n)})},_initContainerEvents=function(){var e,t=function(){F2.Events.emit(F2.Constants.Events.CONTAINER_WIDTH_CHANGE)};jQuery(window).on("resize",function(){clearTimeout(e),e=setTimeout(t,100)})},_isInit=function(){return!!_config},_loadApps=function(appConfigs,appManifest){appConfigs=[].concat(appConfigs);if(appConfigs.length==1&&appConfigs[0].isSecure&&!_config.isSecureAppPage){_loadSecureApp(appConfigs[0],appManifest);return}if(appConfigs.length!=appManifest.apps.length){F2.log("The number of apps defined in the AppManifest do not match the number requested.",appManifest);return}var scripts=appManifest.scripts||[],styles=appManifest.styles||[],inlines=appManifest.inlineScripts||[],scriptCount=scripts.length,scriptsLoaded=0,appInit=function(){jQuery.each(appConfigs,function(e,t){t.ui=new F2.UI(t),F2.Apps[t.appId]!==undefined&&(typeof F2.Apps[t.appId]=="function"?setTimeout(function(){_apps[t.instanceId].app=new F2.Apps[t.appId](t,appManifest.apps[e],t.root),_apps[t.instanceId].app.init!==undefined&&_apps[t.instanceId].app.init()},0):F2.log("app initialization class is defined but not a function. ("+t.appId+")"))})},stylesFragment=[];jQuery.each(styles,function(e,t){stylesFragment.push('')}),jQuery("head").append(stylesFragment.join("")),jQuery.each(appManifest.apps,function(e,t){appConfigs[e].root=_afterAppRender(appConfigs[e],_appRender(appConfigs[e],t.html)),_initAppEvents(appConfigs[e])}),jQuery.each(scripts,function(i,e){jQuery.ajax({url:e,cache:!0,async:!1,dataType:"script",type:"GET",success:function(){++scriptsLoaded==scriptCount&&(jQuery.each(inlines,function(i,e){try{eval(e)}catch(exception){F2.log("Error loading inline script: "+exception+"\n\n"+e)}}),appInit())},error:function(t,n,r){F2.log(["Failed to load script ("+e+")",r.toString()])}})}),scriptCount||appInit()},_loadSecureApp=function(e,t){_config.secureAppPagePath?(e.root=_afterAppRender(e,_appRender(e,"")),e.ui=new F2.UI(e),_initAppEvents(e),F2.Rpc.register(e,t)):F2.log('Unable to load secure app: "secureAppPagePath" is not defined in F2.ContainerConfig.')},_validateApp=function(e){return e.appId?e.manifestUrl?!0:(F2.log('manifestUrl" missing from app object'),!1):(F2.log('"appId" missing from app object'),!1)};return{getContainerState:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.getContainerState()");return}return jQuery.map(_apps,function(e,t){return{appId:e.config.appId}})},init:function(e){_config=e||{},(!!_config.secureAppPagePath||_config.isSecureAppPage)&&F2.Rpc.init(_config.secureAppPagePath?_config.secureAppPagePath:!1),F2.UI.init(_config),_config.isSecureAppPage||_initContainerEvents()},isInit:_isInit,IAmFromAli:function(){},registerApps:function(e,t){if(!_isInit()){F2.log("F2.init() must be called before F2.registerApps()");return}if(!e){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}var n=[],r={},i={},s=!1;e=[].concat(e),t=t||[],s=!!t.length;if(!e.length){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}if(e.length&&s&&e.length!=t.length){F2.log('The length of "apps" does not equal the length of "appManifests"');return}jQuery.each(e,function(e,i){if(!_validateApp(i))return;_hydrateAppConfig(i),i.root=_beforeAppRender(i),_apps[i.instanceId]={config:i},s?_loadApps(i,t[e]):i.enableBatchRequests&&!i.isSecure?(r[i.manifestUrl.toLowerCase()]=r[i.manifestUrl.toLowerCase()]||[],r[i.manifestUrl.toLowerCase()].push(i)):n.push({apps:[i],url:i.manifestUrl})}),s||(jQuery.each(r,function(e,t){n.push({url:e,apps:t})}),jQuery.each(n,function(e,t){var n=F2.Constants.JSONP_CALLBACK+t.apps[0].appId;i[n]=i[n]||[],i[n].push(t)}),jQuery.each(i,function(e,t){var n=function(r,i){if(!i)return;jQuery.ajax({url:i.url,data:{params:F2.stringify(i.apps,F2.appConfigReplacer)},jsonp:!1,jsonpCallback:r,dataType:"jsonp",success:function(e){_loadApps(i.apps,e)},error:function(e,t,n){F2.log("Failed to load app(s)",n.toString(),i.apps),jQuery.each(i.apps,function(e,t){F2.log("Removed failed "+t.name+" app",t),F2.removeApp(t.instanceId)})},complete:function(){n(e,t.pop())}})};n(e,t.pop())}))},removeAllApps:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.removeAllApps()");return}jQuery.each(_apps,function(e,t){F2.removeApp(t.config.instanceId)})},removeApp:function(e){if(!_isInit()){F2.log("F2.init() must be called before F2.removeApp()");return}_apps[e]&&(jQuery(_apps[e].config.root).fadeOut(function(){jQuery(this).remove()}),delete _apps[e])}}}());
exports.F2 = F2;
diff --git a/docs/sdk/api.js b/docs/sdk/api.js
index 86ca014d..dede5733 100644
--- a/docs/sdk/api.js
+++ b/docs/sdk/api.js
@@ -4,6 +4,7 @@ YUI.add("yuidoc-meta", function(Y) {
"F2",
"F2.App",
"F2.AppConfig",
+ "F2.AppHandlers",
"F2.AppManifest",
"F2.AppManifest.AppContent",
"F2.Constants",
diff --git a/docs/sdk/assets/js/apidocs.js b/docs/sdk/assets/js/apidocs.js
index c080d16a..c64bb463 100644
--- a/docs/sdk/assets/js/apidocs.js
+++ b/docs/sdk/assets/js/apidocs.js
@@ -247,6 +247,11 @@ pjax.updateTabState = function (src) {
} else {
tab = Y.one('#classdocs .api-class-tab.' + defaultTab);
+ // When the `defaultTab` node isn't found, `localStorage` is stale.
+ if (!tab && defaultTab !== 'index') {
+ tab = Y.one('#classdocs .api-class-tab.index');
+ }
+
if (classTabView.get('rendered')) {
Y.Widget.getByNode(tab).set('selected', 1);
} else {
diff --git a/docs/sdk/classes/F2.App.html b/docs/sdk/classes/F2.App.html
index 7115dcbb..a9162181 100644
--- a/docs/sdk/classes/F2.App.html
+++ b/docs/sdk/classes/F2.App.html
@@ -110,6 +110,8 @@
* * * *
diff --git a/docs/src/index.md b/docs/src/index.md
index 48fb7bb5..1207f3bc 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -77,9 +77,9 @@ To achieve steady growth and stable release cycles, F2 will be maintained under
### Track
-F2 v1.0 was released on October 15, 2012. The latest version of the F2 specification is {{docs.version}} released on {{docs.lastUpdateDateFormatted}}. To provide transparency into the future of F2, a roadmap wiki will be available on GitHub. A [changelog](https://github.com/OpenF2/F2/wiki/Docs-Changelog) that tracks version-to-version changes, upgrades and deprecated features will offer a historical look at F2's evolution.
+F2 v1.0 was released on October 15, 2012. The latest version of the F2 specification is 1.1.0 released on 21 March 2013. To provide transparency into the future of F2, a roadmap wiki will be available on GitHub. A [changelog](https://github.com/OpenF2/F2/wiki/Docs-Changelog) that tracks version-to-version changes, upgrades and deprecated features will offer a historical look at F2's evolution.
-Note There is a [separate changelog](https://github.com/OpenF2/F2/wiki/SDK-Changelog) for the [F2.js SDK](f2js-sdk.html) which is currently version {{sdk.version}}.
+Note There is a [separate changelog](https://github.com/OpenF2/F2/wiki/SDK-Changelog) for the [F2.js SDK](f2js-sdk.html) which is currently version 1.1.1.
### Collaborate
diff --git a/docs/src/template/baseTemplate.html b/docs/src/template/baseTemplate.html
index bd9a2885..315fba25 100644
--- a/docs/src/template/baseTemplate.html
+++ b/docs/src/template/baseTemplate.html
@@ -6,8 +6,8 @@
$for(author-meta)$
$endfor$
-
-
+
+
diff --git a/docs/src/template/footer.html b/docs/src/template/footer.html
index 6211f7d6..308af2db 100644
--- a/docs/src/template/footer.html
+++ b/docs/src/template/footer.html
@@ -5,7 +5,7 @@
@@ -14,6 +14,6 @@
-
-
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/docs/src/template/header.html b/docs/src/template/header.html
index 979751c0..62fb78ae 100644
--- a/docs/src/template/header.html
+++ b/docs/src/template/header.html
@@ -30,8 +30,8 @@
diff --git a/docs/src/template/style.html b/docs/src/template/style.html
index ed48ab0a..57e4858d 100644
--- a/docs/src/template/style.html
+++ b/docs/src/template/style.html
@@ -3,7 +3,7 @@
-
+
@@ -12,7 +12,7 @@
-
+
+
+
+
@@ -33,6 +36,13 @@
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
+
+ // standard Jasmine init code
+ jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
+
+ // what you need to add
+ var console_reporter = new jasmine.ConsoleReporter()
+ jasmine.getEnv().addReporter(console_reporter);
var currentWindowOnload = window.onload;
diff --git a/tests/js/console-runner.js b/tests/js/console-runner.js
new file mode 100644
index 00000000..e1975959
--- /dev/null
+++ b/tests/js/console-runner.js
@@ -0,0 +1,104 @@
+/**
+ Jasmine Reporter that outputs test results to the browser console.
+ Useful for running in a headless environment such as PhantomJs, ZombieJs etc.
+
+ Usage:
+ // From your html file that loads jasmine:
+ jasmine.getEnv().addReporter(new jasmine.ConsoleReporter());
+ jasmine.getEnv().execute();
+*/
+
+(function(jasmine, console) {
+ if (!jasmine) {
+ throw "jasmine library isn't loaded!";
+ }
+
+ var ANSI = {}
+ ANSI.color_map = {
+ "green" : 32,
+ "red" : 31
+ }
+
+ ANSI.colorize_text = function(text, color) {
+ var color_code = this.color_map[color];
+ return "\033[" + color_code + "m" + text + "\033[0m";
+ }
+
+ var ConsoleReporter = function() {
+ if (!console || !console.log) { throw "console isn't present!"; }
+ this.status = this.statuses.stopped;
+ };
+
+ var proto = ConsoleReporter.prototype;
+ proto.statuses = {
+ stopped : "stopped",
+ running : "running",
+ fail : "fail",
+ success : "success"
+ };
+
+ proto.reportRunnerStarting = function(runner) {
+ this.status = this.statuses.running;
+ this.start_time = (new Date()).getTime();
+ this.executed_specs = 0;
+ this.passed_specs = 0;
+ this.log("Starting...");
+ };
+
+ proto.reportRunnerResults = function(runner) {
+ var failed = this.executed_specs - this.passed_specs;
+ var spec_str = this.executed_specs + (this.executed_specs === 1 ? " spec, " : " specs, ");
+ var fail_str = failed + (failed === 1 ? " failure in " : " failures in ");
+ var color = (failed > 0)? "red" : "green";
+ var dur = (new Date()).getTime() - this.start_time;
+
+ this.log("");
+ this.log("Finished");
+ this.log("-----------------");
+ this.log(spec_str + fail_str + (dur/1000) + "s.", color);
+
+ this.status = (failed > 0)? this.statuses.fail : this.statuses.success;
+
+ /* Print something that signals that testing is over so that headless browsers
+ like PhantomJs know when to terminate. */
+ this.log("");
+ this.log("ConsoleReporter finished");
+ };
+
+
+ proto.reportSpecStarting = function(spec) {
+ this.executed_specs++;
+ };
+
+ proto.reportSpecResults = function(spec) {
+ if (spec.results().passed()) {
+ this.passed_specs++;
+ return;
+ }
+
+ var resultText = spec.suite.description + " : " + spec.description;
+ this.log(resultText, "red");
+
+ var items = spec.results().getItems()
+ for (var i = 0; i < items.length; i++) {
+ var trace = items[i].trace.stack || items[i].trace;
+ this.log(trace, "red");
+ }
+ };
+
+ proto.reportSuiteResults = function(suite) {
+ if (!suite.parentSuite) { return; }
+ var results = suite.results();
+ var failed = results.totalCount - results.passedCount;
+ var color = (failed > 0)? "red" : "green";
+ this.log(suite.getFullName() + ": " + results.passedCount + " of " + results.totalCount + " passed.", color);
+ };
+
+ proto.log = function(str, color) {
+ var text = (color != undefined)? ANSI.colorize_text(str, color) : str;
+ console.log(text)
+ };
+
+ jasmine.ConsoleReporter = ConsoleReporter;
+})(jasmine, console);
+
From cf3a8221ab0c1b0a1780b40170fd79ff3bd9ac92 Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Thu, 21 Mar 2013 16:04:06 -0600
Subject: [PATCH 007/181] Trying things..
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 683a331f..b774b05a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,4 +3,4 @@ node_js:
- "0.10"
- "0.8"
before_install: npm install phantom-jasmine -g
-script: phantom-jasmine tests/index.html
\ No newline at end of file
+script: phantom-jasmine /tests/index.html
\ No newline at end of file
From 022b104e678f4aa4479c15bb0ddeaba31485d9ec Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Thu, 21 Mar 2013 16:23:26 -0600
Subject: [PATCH 008/181] Adding some test files/changes to test integration.
---
.travis.yml | 2 +-
tests/index-console.html | 28 ++++++++++++++++++++++++++++
tests/spec/console-test.js | 9 +++++++++
3 files changed, 38 insertions(+), 1 deletion(-)
create mode 100644 tests/index-console.html
create mode 100644 tests/spec/console-test.js
diff --git a/.travis.yml b/.travis.yml
index b774b05a..07a92f65 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,4 +3,4 @@ node_js:
- "0.10"
- "0.8"
before_install: npm install phantom-jasmine -g
-script: phantom-jasmine /tests/index.html
\ No newline at end of file
+script: phantom-jasmine tests/index-console.html
\ No newline at end of file
diff --git a/tests/index-console.html b/tests/index-console.html
new file mode 100644
index 00000000..250c8f92
--- /dev/null
+++ b/tests/index-console.html
@@ -0,0 +1,28 @@
+
+
+
+ Jasmine Console Spec Runner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/spec/console-test.js b/tests/spec/console-test.js
new file mode 100644
index 00000000..62bd471e
--- /dev/null
+++ b/tests/spec/console-test.js
@@ -0,0 +1,9 @@
+describe("Console", function() {
+ it ("should fail", function() {
+ expect(false).toBeTruthy();
+ });
+
+ it ("should succeed", function() {
+ expect(true).toBeTruthy();
+ });
+});
\ No newline at end of file
From 808b60fa5ceefdd3f7549fcf0964b131810fd167 Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Thu, 21 Mar 2013 16:32:50 -0600
Subject: [PATCH 009/181] trying another version...
---
.travis.yml | 29 +++++++++++++++++++++++------
1 file changed, 23 insertions(+), 6 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 07a92f65..6f968670 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,23 @@
-language: node_js
-node_js:
- - "0.10"
- - "0.8"
-before_install: npm install phantom-jasmine -g
-script: phantom-jasmine tests/index-console.html
\ No newline at end of file
+# This version of .travis.yml always downloads the stable version of
+# phantom-qunit.js before running the tests, so you don't have to check
+# phantom-xxx.js into your repository. Make sure to change qunit to jasmine or
+# mocha if necessary. If you aren't sure which version of .travis.yml to use,
+# pick this one.
+
+# We aren't actually using any particular language here, so let's use Ruby
+# since that's probably the most likely Travis worker to be available
+language: ruby
+rvm:
+ - 1.9.3
+notifications:
+ email: false
+before_script:
+ # Travis needs this stuff to start Phantom1.4...
+ # When they get Phantom1.5 this section can go away
+ - "export DISPLAY=:99.0"
+ - "sh -e /etc/init.d/xvfb start"
+script:
+ # Change these to jasmine or mocha if necessary
+ - "wget https://raw.github.com/mark-rushakoff/OpenPhantomScripts/master/phantom-qunit.js"
+ # Make sure to change test/test.html to the path to your test page
+ - "phantomjs phantom-qunit.js tests/index.html"
From c55c8d42710495ac619aec4e6c1d0eae879b5673 Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Thu, 21 Mar 2013 16:35:27 -0600
Subject: [PATCH 010/181] changed quint to jasmine.
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 6f968670..99b82c9c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,6 +18,6 @@ before_script:
- "sh -e /etc/init.d/xvfb start"
script:
# Change these to jasmine or mocha if necessary
- - "wget https://raw.github.com/mark-rushakoff/OpenPhantomScripts/master/phantom-qunit.js"
+ - "wget https://raw.github.com/mark-rushakoff/OpenPhantomScripts/master/phantom-jasmine.js"
# Make sure to change test/test.html to the path to your test page
- "phantomjs phantom-qunit.js tests/index.html"
From fe0fe936e5eade29e05edc7c83fa4338711c2179 Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Thu, 21 Mar 2013 16:48:52 -0600
Subject: [PATCH 011/181] changed quint to jasmine
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 99b82c9c..b85e0827 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,4 +20,4 @@ script:
# Change these to jasmine or mocha if necessary
- "wget https://raw.github.com/mark-rushakoff/OpenPhantomScripts/master/phantom-jasmine.js"
# Make sure to change test/test.html to the path to your test page
- - "phantomjs phantom-qunit.js tests/index.html"
+ - "phantomjs phantom-jasmine.js tests/index.html"
From 1865fe5d5760f4436a83723b2902befc6b619a2a Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Thu, 21 Mar 2013 23:14:27 -0600
Subject: [PATCH 012/181] Rebuilt. Fixed js errors in app_handlers.js and doing
some more testing with travis ci
---
.travis.yml | 2 +-
docs/js/f2.js | 287 ++++++++++++++++++++++++++++++++++++++-
sdk/f2.debug.js | 287 +++++++++++++++++++++++++++++++++++++++
sdk/f2.min.js | 1 +
sdk/f2.no-third-party.js | 287 +++++++++++++++++++++++++++++++++++++++
sdk/src/app_handlers.js | 73 ++++------
6 files changed, 890 insertions(+), 47 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index b85e0827..58a29891 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,4 +20,4 @@ script:
# Change these to jasmine or mocha if necessary
- "wget https://raw.github.com/mark-rushakoff/OpenPhantomScripts/master/phantom-jasmine.js"
# Make sure to change test/test.html to the path to your test page
- - "phantomjs phantom-jasmine.js tests/index.html"
+ - "phantomjs phantom-jasmine.js tests/index-console.html"
diff --git a/docs/js/f2.js b/docs/js/f2.js
index 121000a6..43070c7d 100644
--- a/docs/js/f2.js
+++ b/docs/js/f2.js
@@ -120,7 +120,292 @@ F2.extend("Constants",{Css:function(){var e="f2-";return{APP:e+"app",APP_CONTAIN
F2.extend("Events",function(){var e=new EventEmitter2({wildcard:!0});return e.setMaxListeners(0),{_socketEmit:function(){return EventEmitter2.prototype.emit.apply(e,[].slice.call(arguments))},emit:function(){return F2.Rpc.broadcast(F2.Constants.Sockets.EVENT,[].slice.call(arguments)),EventEmitter2.prototype.emit.apply(e,[].slice.call(arguments))},many:function(t,n,r){return e.many(t,n,r)},off:function(t,n){return e.off(t,n)},on:function(t,n){return e.on(t,n)},once:function(t,n){return e.once(t,n)}}}());
F2.extend("Rpc",function(){var e={},t="",n={},r=new RegExp("^"+F2.Constants.Sockets.EVENT),i=new RegExp("^"+F2.Constants.Sockets.RPC),s=new RegExp("^"+F2.Constants.Sockets.RPC_CALLBACK),o=new RegExp("^"+F2.Constants.Sockets.LOAD),u=new RegExp("^"+F2.Constants.Sockets.UI_RPC),a=function(){var e,t=!1,r=[],i=new easyXDM.Socket({onMessage:function(s,u){if(!t&&o.test(s)){s=s.replace(o,"");var a=F2.parse(s);a.length==2&&(e=a[0],n[e.instanceId]={config:e,socket:i},F2.registerApps([e],[a[1]]),jQuery.each(r,function(t,n){c(e,s,u)}),t=!0)}else t?c(e,s,u):r.push(s)}})},f=function(e,n){var r=jQuery(e.root);r=r.is("."+F2.Constants.Css.APP_CONTAINER)?r:r.find("."+F2.Constants.Css.APP_CONTAINER);if(!r.length){F2.log("Unable to locate app in order to establish secure connection.");return}var i={scrolling:"no",style:{width:"100%"}};e.height&&(i.style.height=e.height+"px");var s=new easyXDM.Socket({remote:t,container:r.get(0),props:i,onMessage:function(t,n){c(e,t,n)},onReady:function(){s.postMessage(F2.Constants.Sockets.LOAD+F2.stringify([e,n],F2.appConfigReplacer))}});return s},l=function(e,t){return function(){F2.Rpc.call(e,F2.Constants.Sockets.RPC_CALLBACK,t,[].slice.call(arguments).slice(2))}},c=function(t,n,o){function f(e,t){var n=String(t).split(".");for(var r=0;r','',"
Alert!
","",'
',"
",e,"
","
",'",""].join("")},n=function(e){return['
','',"
Confirm
","",'
',"
",e,"
","
",'","
"].join("")};return{alert:function(n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.alert()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.alert",[].slice.call(arguments)):jQuery(e(n)).on("show",function(){var e=this;jQuery(e).find(".btn-primary").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()})}).modal({backdrop:!0})},confirm:function(e,r,i){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.confirm()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.confirm",[].slice.call(arguments)):jQuery(n(e)).on("show",function(){var e=this;jQuery(e).find(".btn-ok").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()}),jQuery(e).find(".btn-cancel").on("click",function(){jQuery(e).modal("hide").remove(),(i||jQuery.noop)()})}).modal({backdrop:!0})}}}(),setTitle:function(e){F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"setTitle",[e]):jQuery(t.root).find("."+F2.Constants.Css.APP_TITLE).text(e)},showMask:function(e,n){F2.UI.showMask(t.instanceId,e,n)},updateHeight:r,Views:function(){var e=new EventEmitter2,i=/change/i;e.setMaxListeners(0);var s=function(e){return i.test(e)?!0:(F2.log('"'+e+'" is not a valid F2.UI.Views event name'),!1)};return{change:function(i){typeof i=="function"?this.on("change",i):typeof i=="string"&&(t.isSecure&&!F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Views.change",[].slice.call(arguments)):F2.inArray(i,t.views)&&(jQuery("."+F2.Constants.Css.APP_VIEW,n).addClass("hide").filter('[data-f2-view="'+i+'"]',n).removeClass("hide"),r(),e.emit("change",i)))},off:function(t,n){s(t)&&e.off(t,n)},on:function(t,n){s(t)&&e.on(t,n)}}}()}};return t.hideMask=function(e,t){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.hideMask()");return}if(F2.Rpc.isRemote(e)&&!jQuery(t).is("."+F2.Constants.Css.APP))F2.Rpc.call(e,F2.Constants.Sockets.RPC,"F2.UI.hideMask",[e,jQuery(t).selector]);else{var n=jQuery(t),r=n.find("> ."+F2.Constants.Css.MASK).remove();n.removeClass(F2.Constants.Css.MASK_CONTAINER),n.data(F2.Constants.Css.MASK_CONTAINER)&&n.css({position:"static"})}},t.init=function(t){e=t,e.UI=jQuery.extend(!0,{},F2.ContainerConfig.UI,e.UI||{})},t.showMask=function(t,n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.showMask()");return}if(F2.Rpc.isRemote(t)&&jQuery(n).is("."+F2.Constants.Css.APP))F2.Rpc.call(t,F2.Constants.Sockets.RPC,"F2.UI.showMask",[t,jQuery(n).selector,r]);else{r&&!e.UI.Mask.loadingIcon&&F2.log("Unable to display loading icon. Please set F2.ContainerConfig.UI.Mask.loadingIcon when calling F2.init();");var i=jQuery(n).addClass(F2.Constants.Css.MASK_CONTAINER),s=jQuery("
").height("100%").width("100%").addClass(F2.Constants.Css.MASK);e.UI.Mask.useClasses||s.css({"background-color":e.UI.Mask.backgroundColor,"background-image":e.UI.Mask.loadingIcon?"url("+e.UI.Mask.loadingIcon+")":"","background-position":"50% 50%","background-repeat":"no-repeat",display:"block",left:0,"min-height":30,padding:0,position:"absolute",top:0,"z-index":e.UI.Mask.zIndex,filter:"alpha(opacity="+e.UI.Mask.opacity*100+")",opacity:e.UI.Mask.opacity}),i.css("position")==="static"&&(i.css({position:"relative"}),i.data(F2.Constants.Css.MASK_CONTAINER,!0)),i.append(s)}},t}());
-F2.extend("",function(){var _apps={},_config=!1,_afterAppRender=function(e,t){var n=_config.afterAppRender||function(e,t){return jQuery(t).appendTo("body")},r=n(e,t);if(!!_config.afterAppRender&&!r){F2.log("F2.ContainerConfig.afterAppRender() must return the DOM Element that contains the app");return}return jQuery(r).addClass(F2.Constants.Css.APP),r.get(0)},_appRender=function(e,t){function n(e){return jQuery("").append(e).html()}return t=n(jQuery(t).addClass(F2.Constants.Css.APP_CONTAINER+" "+e.appId)),_config.appRender&&(t=_config.appRender(e,t)),n(t)},_beforeAppRender=function(e){var t=_config.beforeAppRender||jQuery.noop;return t(e)},_hydrateAppConfig=function(e){e.instanceId=e.instanceId||F2.guid(),e.views=e.views||[],F2.inArray(F2.Constants.Views.HOME,e.views)||e.views.push(F2.Constants.Views.HOME)},_initAppEvents=function(e){jQuery(e.root).on("click","."+F2.Constants.Css.APP_VIEW_TRIGGER+"["+F2.Constants.Views.DATA_ATTRIBUTE+"]",function(t){t.preventDefault();var n=jQuery(this).attr(F2.Constants.Views.DATA_ATTRIBUTE).toLowerCase();n==F2.Constants.Views.REMOVE?F2.removeApp(e.instanceId):e.ui.Views.change(n)})},_initContainerEvents=function(){var e,t=function(){F2.Events.emit(F2.Constants.Events.CONTAINER_WIDTH_CHANGE)};jQuery(window).on("resize",function(){clearTimeout(e),e=setTimeout(t,100)})},_isInit=function(){return!!_config},_loadApps=function(appConfigs,appManifest){appConfigs=[].concat(appConfigs);if(appConfigs.length==1&&appConfigs[0].isSecure&&!_config.isSecureAppPage){_loadSecureApp(appConfigs[0],appManifest);return}if(appConfigs.length!=appManifest.apps.length){F2.log("The number of apps defined in the AppManifest do not match the number requested.",appManifest);return}var scripts=appManifest.scripts||[],styles=appManifest.styles||[],inlines=appManifest.inlineScripts||[],scriptCount=scripts.length,scriptsLoaded=0,appInit=function(){jQuery.each(appConfigs,function(e,t){t.ui=new F2.UI(t),F2.Apps[t.appId]!==undefined&&(typeof F2.Apps[t.appId]=="function"?setTimeout(function(){_apps[t.instanceId].app=new F2.Apps[t.appId](t,appManifest.apps[e],t.root),_apps[t.instanceId].app.init!==undefined&&_apps[t.instanceId].app.init()},0):F2.log("app initialization class is defined but not a function. ("+t.appId+")"))})},stylesFragment=[];jQuery.each(styles,function(e,t){stylesFragment.push('')}),jQuery("head").append(stylesFragment.join("")),jQuery.each(appManifest.apps,function(e,t){appConfigs[e].root=_afterAppRender(appConfigs[e],_appRender(appConfigs[e],t.html)),_initAppEvents(appConfigs[e])}),jQuery.each(scripts,function(i,e){jQuery.ajax({url:e,cache:!0,async:!1,dataType:"script",type:"GET",success:function(){++scriptsLoaded==scriptCount&&(jQuery.each(inlines,function(i,e){try{eval(e)}catch(exception){F2.log("Error loading inline script: "+exception+"\n\n"+e)}}),appInit())},error:function(t,n,r){F2.log(["Failed to load script ("+e+")",r.toString()])}})}),scriptCount||appInit()},_loadSecureApp=function(e,t){_config.secureAppPagePath?(e.root=_afterAppRender(e,_appRender(e,"")),e.ui=new F2.UI(e),_initAppEvents(e),F2.Rpc.register(e,t)):F2.log('Unable to load secure app: "secureAppPagePath" is not defined in F2.ContainerConfig.')},_validateApp=function(e){return e.appId?e.manifestUrl?!0:(F2.log('manifestUrl" missing from app object'),!1):(F2.log('"appId" missing from app object'),!1)};return{getContainerState:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.getContainerState()");return}return jQuery.map(_apps,function(e,t){return{appId:e.config.appId}})},init:function(e){_config=e||{},(!!_config.secureAppPagePath||_config.isSecureAppPage)&&F2.Rpc.init(_config.secureAppPagePath?_config.secureAppPagePath:!1),F2.UI.init(_config),_config.isSecureAppPage||_initContainerEvents()},isInit:_isInit,IAmFromAli:function(){},registerApps:function(e,t){if(!_isInit()){F2.log("F2.init() must be called before F2.registerApps()");return}if(!e){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}var n=[],r={},i={},s=!1;e=[].concat(e),t=t||[],s=!!t.length;if(!e.length){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}if(e.length&&s&&e.length!=t.length){F2.log('The length of "apps" does not equal the length of "appManifests"');return}jQuery.each(e,function(e,i){if(!_validateApp(i))return;_hydrateAppConfig(i),i.root=_beforeAppRender(i),_apps[i.instanceId]={config:i},s?_loadApps(i,t[e]):i.enableBatchRequests&&!i.isSecure?(r[i.manifestUrl.toLowerCase()]=r[i.manifestUrl.toLowerCase()]||[],r[i.manifestUrl.toLowerCase()].push(i)):n.push({apps:[i],url:i.manifestUrl})}),s||(jQuery.each(r,function(e,t){n.push({url:e,apps:t})}),jQuery.each(n,function(e,t){var n=F2.Constants.JSONP_CALLBACK+t.apps[0].appId;i[n]=i[n]||[],i[n].push(t)}),jQuery.each(i,function(e,t){var n=function(r,i){if(!i)return;jQuery.ajax({url:i.url,data:{params:F2.stringify(i.apps,F2.appConfigReplacer)},jsonp:!1,jsonpCallback:r,dataType:"jsonp",success:function(e){_loadApps(i.apps,e)},error:function(e,t,n){F2.log("Failed to load app(s)",n.toString(),i.apps),jQuery.each(i.apps,function(e,t){F2.log("Removed failed "+t.name+" app",t),F2.removeApp(t.instanceId)})},complete:function(){n(e,t.pop())}})};n(e,t.pop())}))},removeAllApps:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.removeAllApps()");return}jQuery.each(_apps,function(e,t){F2.removeApp(t.config.instanceId)})},removeApp:function(e){if(!_isInit()){F2.log("F2.init() must be called before F2.removeApp()");return}_apps[e]&&(jQuery(_apps[e].config.root).fadeOut(function(){jQuery(this).remove()}),delete _apps[e])}}}());
+F2.extend("",function(){var _apps={},_config=!1,_afterAppRender=function(e,t){var n=_config.afterAppRender||function(e,t){return jQuery(t).appendTo("body")},r=n(e,t);if(!!_config.afterAppRender&&!r){F2.log("F2.ContainerConfig.afterAppRender() must return the DOM Element that contains the app");return}return jQuery(r).addClass(F2.Constants.Css.APP),r.get(0)},_appRender=function(e,t){function n(e){return jQuery("").append(e).html()}return t=n(jQuery(t).addClass(F2.Constants.Css.APP_CONTAINER+" "+e.appId)),_config.appRender&&(t=_config.appRender(e,t)),n(t)},_beforeAppRender=function(e){var t=_config.beforeAppRender||jQuery.noop;return t(e)},_hydrateAppConfig=function(e){e.instanceId=e.instanceId||F2.guid(),e.views=e.views||[],F2.inArray(F2.Constants.Views.HOME,e.views)||e.views.push(F2.Constants.Views.HOME)},_initAppEvents=function(e){jQuery(e.root).on("click","."+F2.Constants.Css.APP_VIEW_TRIGGER+"["+F2.Constants.Views.DATA_ATTRIBUTE+"]",function(t){t.preventDefault();var n=jQuery(this).attr(F2.Constants.Views.DATA_ATTRIBUTE).toLowerCase();n==F2.Constants.Views.REMOVE?F2.removeApp(e.instanceId):e.ui.Views.change(n)})},_initContainerEvents=function(){var e,t=function(){F2.Events.emit(F2.Constants.Events.CONTAINER_WIDTH_CHANGE)};jQuery(window).on("resize",function(){clearTimeout(e),e=setTimeout(t,100)})},_isInit=function(){return!!_config},_loadApps=function(appConfigs,appManifest){appConfigs=[].concat(appConfigs);if(appConfigs.length==1&&appConfigs[0].isSecure&&!_config.isSecureAppPage){_loadSecureApp(appConfigs[0],appManifest);return}if(appConfigs.length!=appManifest.apps.length){F2.log("The number of apps defined in the AppManifest do not match the number requested.",appManifest);return}var scripts=appManifest.scripts||[],styles=appManifest.styles||[],inlines=appManifest.inlineScripts||[],scriptCount=scripts.length,scriptsLoaded=0,appInit=function(){jQuery.each(appConfigs,function(e,t){t.ui=new F2.UI(t),F2.Apps[t.appId]!==undefined&&(typeof F2.Apps[t.appId]=="function"?setTimeout(function(){_apps[t.instanceId].app=new F2.Apps[t.appId](t,appManifest.apps[e],t.root),_apps[t.instanceId].app.init!==undefined&&_apps[t.instanceId].app.init()},0):F2.log("app initialization class is defined but not a function. ("+t.appId+")"))})},stylesFragment=[];jQuery.each(styles,function(e,t){stylesFragment.push('')}),jQuery("head").append(stylesFragment.join("")),jQuery.each(appManifest.apps,function(e,t){appConfigs[e].root=_afterAppRender(appConfigs[e],_appRender(appConfigs[e],t.html)),_initAppEvents(appConfigs[e])}),jQuery.each(scripts,function(i,e){jQuery.ajax({url:e,cache:!0,async:!1,dataType:"script",type:"GET",success:function(){++scriptsLoaded==scriptCount&&(jQuery.each(inlines,function(i,e){try{eval(e)}catch(exception){F2.log("Error loading inline script: "+exception+"\n\n"+e)}}),appInit())},error:function(t,n,r){F2.log(["Failed to load script ("+e+")",r.toString()])}})}),scriptCount||appInit()},_loadSecureApp=function(e,t){_config.secureAppPagePath?(e.root=_afterAppRender(e,_appRender(e,"")),e.ui=new F2.UI(e),_initAppEvents(e),F2.Rpc.register(e,t)):F2.log('Unable to load secure app: "secureAppPagePath" is not defined in F2.ContainerConfig.')},_validateApp=function(e){return e.appId?e.manifestUrl?!0:(F2.log('manifestUrl" missing from app object'),!1):(F2.log('"appId" missing from app object'),!1)};return{getContainerState:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.getContainerState()");return}return jQuery.map(_apps,function(e,t){return{appId:e.config.appId}})},init:function(e){_config=e||{},(!!_config.secureAppPagePath||_config.isSecureAppPage)&&F2.Rpc.init(_config.secureAppPagePath?_config.secureAppPagePath:!1),F2.UI.init(_config),_config.isSecureAppPage||_initContainerEvents()},isInit:_isInit,registerApps:function(e,t){if(!_isInit()){F2.log("F2.init() must be called before F2.registerApps()");return}if(!e){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}var n=[],r={},i={},s=!1;e=[].concat(e),t=t||[],s=!!t.length;if(!e.length){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}if(e.length&&s&&e.length!=t.length){F2.log('The length of "apps" does not equal the length of "appManifests"');return}jQuery.each(e,function(e,i){if(!_validateApp(i))return;_hydrateAppConfig(i),i.root=_beforeAppRender(i),_apps[i.instanceId]={config:i},s?_loadApps(i,t[e]):i.enableBatchRequests&&!i.isSecure?(r[i.manifestUrl.toLowerCase()]=r[i.manifestUrl.toLowerCase()]||[],r[i.manifestUrl.toLowerCase()].push(i)):n.push({apps:[i],url:i.manifestUrl})}),s||(jQuery.each(r,function(e,t){n.push({url:e,apps:t})}),jQuery.each(n,function(e,t){var n=F2.Constants.JSONP_CALLBACK+t.apps[0].appId;i[n]=i[n]||[],i[n].push(t)}),jQuery.each(i,function(e,t){var n=function(r,i){if(!i)return;jQuery.ajax({url:i.url,data:{params:F2.stringify(i.apps,F2.appConfigReplacer)},jsonp:!1,jsonpCallback:r,dataType:"jsonp",success:function(e){_loadApps(i.apps,e)},error:function(e,t,n){F2.log("Failed to load app(s)",n.toString(),i.apps),jQuery.each(i.apps,function(e,t){F2.log("Removed failed "+t.name+" app",t),F2.removeApp(t.instanceId)})},complete:function(){n(e,t.pop())}})};n(e,t.pop())}))},removeAllApps:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.removeAllApps()");return}jQuery.each(_apps,function(e,t){F2.removeApp(t.config.instanceId)})},removeApp:function(e){if(!_isInit()){F2.log("F2.init() must be called before F2.removeApp()");return}_apps[e]&&(jQuery(_apps[e].config.root).fadeOut(function(){jQuery(this).remove()}),delete _apps[e])}}}());
+/**
+ * Allows container developers more flexibility when it comes to handling app interaction.
+ * @class F2.AppHandlers
+ */
+F2.extend('AppHandlers', (function() {
+
+ // the hidden token that we will check against every time someone tries to add, remove, fire handler
+ var _ct = F2.guid();
+ var _f2t = F2.guid();
+
+ var _handlerCollection = {
+ beforeApp:
+ {
+ render: [],
+ reload: [],
+ destroy: []
+ },
+ afterApp:
+ {
+ render: [],
+ reload: [],
+ destroy: []
+ },
+ app:
+ {
+ render: [],
+ reload: [],
+ destroy: []
+ }
+ };
+
+ //Returns true if it is a DOM node
+ function _isNode(o){
+ return (
+ typeof Node === "object" ? o instanceof Node :
+ o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string"
+ );
+ }
+
+ //Returns true if it is a DOM element
+ function _isElement(o){
+ return (
+ typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
+ o && typeof o === "object" && o.nodeType === 1 && typeof o.nodeName==="string"
+ );
+ }
+
+ var _createHandler = function(arOriginalArgs, bDomNodeAppropriate)
+ {
+ if(!arOriginalArgs || !arOriginalArgs.length) { throw "Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again." }
+
+ // will throw an exception and stop execution if the token is invalid
+ _validateToken(arOriginalArgs[0]);
+
+ // remove the token from the arguments since we have validated it and no longer need it
+ arOriginalArgs.shift();
+
+ var iArgCount = arOriginalArgs.length;
+
+ // create handler structure. Not all arguments properties will be populated/used.
+ var handler = {
+ func: null,
+ namespace: null,
+ app_id: null,
+ domNode: null
+ };
+
+ // based on the argument count try to create a handler.
+ switch(iArgCount)
+ {
+ case 1:
+ // method signature(oDomNode)
+ if(arOriginalArgs[0] && bDomNodeAppropriate && (_isNode(arOriginalArgs[0]) || _isElement(arOriginalArgs[0]))
+ {
+ handler.domNode = arOriginalArgs[0];
+ }
+ // method signature (function(){})
+ else if(arOriginalArgs[0] && typeof(arOriginalArgs[0]) == "function")
+ {
+ handler.func = arOriginalArgs[0];
+ }
+ // error
+ else
+ {
+ throw "Invalid or null argument passed. Argument must be of type function or a native dom node";
+ }
+ break;
+ case 2:
+ // method signature ("APP_ID" ,oDomNode)
+ if(
+ arOriginalArgs[0] &&
+ arOriginalArgs[1] &&
+ typeof(arOriginalArgs[0]) == "string" &&
+ bDomNodeAppropriate &&
+ (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1]))
+ {
+ handler.app_id = arOriginalArgs[0];
+ handler.domNode = arOriginalArgs[1];
+ }
+ // method signature ("APP_ID" ,function(){})
+ else if(
+ arOriginalArgs[0] &&
+ arOriginalArgs[1] &&
+ typeof(arOriginalArgs[0]) == "string" &&
+ typeof(arOriginalArgs[1]) == "function"
+ )
+ {
+ handler.app_id = arOriginalArgs[0];
+ handler.func = arOriginalArgs[1];
+ }
+ // method signature (function(){} ,"NAMESPACE")
+ else if(
+ arOriginalArgs[0] &&
+ arOriginalArgs[1] &&
+ typeof(arOriginalArgs[0]) == "function" &&
+ typeof(arOriginalArgs[1]) == "string"
+ )
+ {
+ handler.func = arOriginalArgs[0];
+ handler.namespace = arOriginalArgs[1];
+ }
+ // error
+ else
+ {
+ throw "Invalid or null argument(s) passed. Argument[0] must be of type function or string (to represent app_id). Argument[1] must be native domnode, function, or string (to represent namespace)";
+ }
+ break;
+ case 3:
+ // method signature ("APP_ID", oDomNode, "NAMESPACE")
+ if(
+ arOriginalArgs[0] &&
+ arOriginalArgs[1] &&
+ typeof(arOriginalArgs[0]) == "string" &&
+ bDomNodeAppropriate &&
+ (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1])) &&
+ typeof(arOriginalArgs[2]) == "string"
+ {
+ handler.app_id = arOriginalArgs[0];
+ handler.domNode = arOriginalArgs[1];
+ handler.namespace = arOriginalArgs[2];
+ }
+ // method signature ("APP_ID", function(){}, "NAMESPACE")
+ else if(
+ arOriginalArgs[0] &&
+ arOriginalArgs[1] &&
+ typeof(arOriginalArgs[0]) == "string" &&
+ typeof(arOriginalArgs[1]) == "function" &&
+ typeof(arOriginalArgs[2]) == "string"
+ )
+ {
+ handler.app_id = arOriginalArgs[0];
+ handler.func = arOriginalArgs[1];
+ handler.namespace = arOriginalArgs[2];
+ }
+ else
+ {
+ throw "Invalid or null argument(s) passed. Argument[0] must be of type string that represents the app_id. Argument[1] must be native domnode or function. Argument[2] must be of type string to represent a namespace.";
+ }
+ break;
+ // throw exception if there are 0 or 4+ arguments
+ default:
+ throw "Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again.";
+ }
+
+ return handler;
+ };
+
+ var _validateToken = function(sToken)
+ {
+ // check token against F2 and Container
+ if(_ct != sToken && _f2t != sToken) { throw "Invalid token passed. Please verify that you have correctly received and stored token from F2.AppHandlers.getToken()."; }
+ };
+
+ var _removeHandler = function(arHandleCollection, sNamespaceOrApp_ID, sToken)
+ {
+ // will throw an exception and stop execution if the token is invalid
+ _validateToken(sToken);
+
+ if(!sNamespaceOrApp_ID && !arHandleCollection)
+ {
+ return;
+ }
+ else if(!sNamespaceOrApp_ID && arHandleCollection)
+ {
+ arHandleCollection = [];
+ }
+ else if(sNamespaceOrApp_ID && arHandleCollection)
+ {
+ sNamespaceOrApp_ID = sNamespaceOrApp_ID.toLowerCase();
+
+ var newEvents = [];
+
+ for(var i = 0, j = arHandleCollection.length; i < j; i++)
+ {
+ var currentHandler = arHandleCollection[i];
+ if(currentHandler)
+ {
+ if(currentHandler.app_id != sNamespaceOrApp_ID) && currentHandler.namespace != sNamespaceOrApp_ID)
+ {
+ newEvents.push(currentHandler);
+ }
+ }
+ }
+
+ arHandleCollection = newEvents;
+ }
+ };
+
+ return {
+ /**
+ * Allows container developer to retrieve a special token which must be passed to
+ * all On and Off methods. This function will self destruct so be sure to keep the response
+ * inside of a closure somewhere.
+ * @method getToken
+ */
+ getToken: function()
+ {
+ // delete this method for security that way only the container has access to the token 1 time.
+ // kind of James Bond-ish, this message will self destruct immediately.
+ delete this.getToken;
+ // return the token, which we validate against.
+ return _ct;
+ },
+ /**
+ * Allows F2 to get a token internally
+ * @method __f2GetToken
+ * @private
+ */
+ __f2GetToken: function()
+ {
+ // delete this method for security that way only the F2 internally has access to the token 1 time.
+ // kind of James Bond-ish, this message will self destruct immediately.
+ delete this.__f2GetToken;
+ // return the token, which we validate against.
+ return _f2t;
+ },
+ /**
+ * Allows F2 to trigger events internally
+ * @method __f2Trigger
+ * @private
+ */
+ __f2Trigger: {
+ },
+ on: {
+ beforeApp:
+ {
+ render: function() { _handlerCollection.beforeApp.render.push(_createHandler(arguments)); },
+ reload: function(){ _handlerCollection.beforeApp.reload.push(_createHandler(arguments)); },
+ destroy: function(){ _handlerCollection.beforeApp.destroy.push(_createHandler(arguments)); }
+ },
+ afterApp:
+ {
+ render: function() { _handlerCollection.afterApp.render.push(_createHandler(arguments)); },
+ reload: function(){ _handlerCollection.afterApp.reload.push(_createHandler(arguments)); },
+ destroy: function(){ _handlerCollection.afterApp.destroy.push(_createHandler(arguments)); }
+ },
+ app:
+ {
+ render: function() { _handlerCollection.app.render.push(_createHandler(arguments), true); },
+ reload: function(){ _handlerCollection.app.reload.push(_createHandler(arguments)); },
+ destroy: function(){ _handlerCollection.app.destroy.push(_createHandler(arguments)); }
+ }
+ },
+ off: {
+ beforeApp:
+ {
+ render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.render, sNamespaceOrApp_ID, sToken); },
+ reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.reload, sNamespaceOrApp_ID, sToken); },
+ destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.beforeApp.destroy, sNamespaceOrApp_ID, sToken); }
+ },
+ afterApp:
+ {
+ render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.render, sNamespaceOrApp_ID, sToken); },
+ reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.reload, sNamespaceOrApp_ID, sToken); },
+ destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.afterApp.destroy, sNamespaceOrApp_ID, sToken); }
+ },
+ app:
+ {
+ render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.render, sNamespaceOrApp_ID, sToken); },
+ reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.reload, sNamespaceOrApp_ID, sToken); },
+ destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.app.destroy, sNamespaceOrApp_ID, sToken); }
+ }
+ }
+ };
+})());;
exports.F2 = F2;
diff --git a/sdk/f2.debug.js b/sdk/f2.debug.js
index e38cabdb..7dde48d2 100644
--- a/sdk/f2.debug.js
+++ b/sdk/f2.debug.js
@@ -3484,6 +3484,293 @@ F2.extend('', (function(){
}
};
})());
+/**
+ * Allows container developers more flexibility when it comes to handling app interaction.
+ * @class F2.AppHandlers
+ */
+F2.extend('AppHandlers', (function() {
+
+ // the hidden token that we will check against every time someone tries to add, remove, fire handler
+ var _ct = F2.guid();
+ var _f2t = F2.guid();
+
+ var _handlerCollection = {
+ beforeApp:
+ {
+ render: [],
+ reload: [],
+ destroy: []
+ },
+ afterApp:
+ {
+ render: [],
+ reload: [],
+ destroy: []
+ },
+ app:
+ {
+ render: [],
+ reload: [],
+ destroy: []
+ }
+ };
+
+ //Returns true if it is a DOM node
+ function _isNode(o){
+ return (
+ typeof Node === "object" ? o instanceof Node :
+ o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string"
+ );
+ }
+
+ //Returns true if it is a DOM element
+ function _isElement(o){
+ return (
+ typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
+ o && typeof o === "object" && o.nodeType === 1 && typeof o.nodeName==="string"
+ );
+ }
+
+ var _createHandler = function(arOriginalArgs, bDomNodeAppropriate)
+ {
+ if(!arOriginalArgs || !arOriginalArgs.length) { throw ("Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again."); }
+
+ // will throw an exception and stop execution if the token is invalid
+ _validateToken(arOriginalArgs[0]);
+
+ // remove the token from the arguments since we have validated it and no longer need it
+ arOriginalArgs.shift();
+
+ var iArgCount = arOriginalArgs.length;
+
+ // create handler structure. Not all arguments properties will be populated/used.
+ var handler = {
+ func: null,
+ namespace: null,
+ app_id: null,
+ domNode: null
+ };
+
+ // based on the argument count try to create a handler.
+ switch(iArgCount)
+ {
+ case 1:
+ // method signature(oDomNode)
+ if(arOriginalArgs[0] && bDomNodeAppropriate && (_isNode(arOriginalArgs[0]) || _isElement(arOriginalArgs[0])))
+ {
+ handler.domNode = arOriginalArgs[0];
+ }
+ // method signature (function(){})
+ else if(arOriginalArgs[0] && typeof(arOriginalArgs[0]) == "function")
+ {
+ handler.func = arOriginalArgs[0];
+ }
+ // error
+ else
+ {
+ throw ("Invalid or null argument passed. Argument must be of type function or a native dom node");
+ }
+ break;
+ case 2:
+ // method signature ("APP_ID" ,oDomNode)
+ if(
+ arOriginalArgs[0] &&
+ arOriginalArgs[1] &&
+ typeof(arOriginalArgs[0]) == "string" &&
+ bDomNodeAppropriate &&
+ (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1]))
+ )
+ {
+ handler.app_id = arOriginalArgs[0];
+ handler.domNode = arOriginalArgs[1];
+ }
+ // method signature ("APP_ID" ,function(){})
+ else if(
+ arOriginalArgs[0] &&
+ arOriginalArgs[1] &&
+ typeof(arOriginalArgs[0]) == "string" &&
+ typeof(arOriginalArgs[1]) == "function"
+ )
+ {
+ handler.app_id = arOriginalArgs[0];
+ handler.func = arOriginalArgs[1];
+ }
+ // method signature (function(){} ,"NAMESPACE")
+ else if(
+ arOriginalArgs[0] &&
+ arOriginalArgs[1] &&
+ typeof(arOriginalArgs[0]) == "function" &&
+ typeof(arOriginalArgs[1]) == "string"
+ )
+ {
+ handler.func = arOriginalArgs[0];
+ handler.namespace = arOriginalArgs[1];
+ }
+ // error
+ else
+ {
+ throw ("Invalid or null argument(s) passed. Argument[0] must be of type function or string (to represent app_id). Argument[1] must be native domnode, function, or string (to represent namespace)");
+ }
+ break;
+ case 3:
+ // method signature ("APP_ID", oDomNode, "NAMESPACE")
+ if(
+ arOriginalArgs[0] &&
+ arOriginalArgs[1] &&
+ typeof(arOriginalArgs[0]) == "string" &&
+ bDomNodeAppropriate &&
+ (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1])) &&
+ typeof(arOriginalArgs[2]) == "string"
+ )
+ {
+ handler.app_id = arOriginalArgs[0];
+ handler.domNode = arOriginalArgs[1];
+ handler.namespace = arOriginalArgs[2];
+ }
+ // method signature ("APP_ID", function(){}, "NAMESPACE")
+ else if(
+ arOriginalArgs[0] &&
+ arOriginalArgs[1] &&
+ typeof(arOriginalArgs[0]) == "string" &&
+ typeof(arOriginalArgs[1]) == "function" &&
+ typeof(arOriginalArgs[2]) == "string"
+ )
+ {
+ handler.app_id = arOriginalArgs[0];
+ handler.func = arOriginalArgs[1];
+ handler.namespace = arOriginalArgs[2];
+ }
+ else
+ {
+ throw ("Invalid or null argument(s) passed. Argument[0] must be of type string that represents the app_id. Argument[1] must be native domnode or function. Argument[2] must be of type string to represent a namespace.");
+ }
+ break;
+ // throw exception if there are 0 or 4+ arguments
+ default:
+ throw ("Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again.");
+ }
+
+ return handler;
+ };
+
+ var _validateToken = function(sToken)
+ {
+ // check token against F2 and Container
+ if(_ct != sToken && _f2t != sToken) { throw ("Invalid token passed. Please verify that you have correctly received and stored token from F2.AppHandlers.getToken()."); }
+ };
+
+ var _removeHandler = function(arHandleCollection, sNamespaceOrApp_ID, sToken)
+ {
+ // will throw an exception and stop execution if the token is invalid
+ _validateToken(sToken);
+
+ if(!sNamespaceOrApp_ID && !arHandleCollection)
+ {
+ return;
+ }
+ else if(!sNamespaceOrApp_ID && arHandleCollection)
+ {
+ arHandleCollection = [];
+ }
+ else if(sNamespaceOrApp_ID && arHandleCollection)
+ {
+ sNamespaceOrApp_ID = sNamespaceOrApp_ID.toLowerCase();
+
+ var newEvents = [];
+
+ for(var i = 0, j = arHandleCollection.length; i < j; i++)
+ {
+ var currentHandler = arHandleCollection[i];
+ if(currentHandler)
+ {
+ if(currentHandler.app_id != sNamespaceOrApp_ID && currentHandler.namespace != sNamespaceOrApp_ID)
+ {
+ newEvents.push(currentHandler);
+ }
+ }
+ }
+
+ arHandleCollection = newEvents;
+ }
+ };
+
+ return {
+ /**
+ * Allows container developer to retrieve a special token which must be passed to
+ * all On and Off methods. This function will self destruct so be sure to keep the response
+ * inside of a closure somewhere.
+ * @method getToken
+ */
+ getToken: function()
+ {
+ // delete this method for security that way only the container has access to the token 1 time.
+ // kind of James Bond-ish, this message will self destruct immediately.
+ delete this.getToken;
+ // return the token, which we validate against.
+ return _ct;
+ },
+ /**
+ * Allows F2 to get a token internally
+ * @method __f2GetToken
+ * @private
+ */
+ __f2GetToken: function()
+ {
+ // delete this method for security that way only the F2 internally has access to the token 1 time.
+ // kind of James Bond-ish, this message will self destruct immediately.
+ delete this.__f2GetToken;
+ // return the token, which we validate against.
+ return _f2t;
+ },
+ /**
+ * Allows F2 to trigger events internally
+ * @method __f2Trigger
+ * @private
+ */
+ __f2Trigger: {
+ },
+ on: {
+ beforeApp:
+ {
+ render: function() { _handlerCollection.beforeApp.render.push(_createHandler(arguments)); },
+ reload: function(){ _handlerCollection.beforeApp.reload.push(_createHandler(arguments)); },
+ destroy: function(){ _handlerCollection.beforeApp.destroy.push(_createHandler(arguments)); }
+ },
+ afterApp:
+ {
+ render: function() { _handlerCollection.afterApp.render.push(_createHandler(arguments)); },
+ reload: function(){ _handlerCollection.afterApp.reload.push(_createHandler(arguments)); },
+ destroy: function(){ _handlerCollection.afterApp.destroy.push(_createHandler(arguments)); }
+ },
+ app:
+ {
+ render: function() { _handlerCollection.app.render.push(_createHandler(arguments), true); },
+ reload: function(){ _handlerCollection.app.reload.push(_createHandler(arguments)); },
+ destroy: function(){ _handlerCollection.app.destroy.push(_createHandler(arguments)); }
+ }
+ },
+ off: {
+ beforeApp:
+ {
+ render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.render, sNamespaceOrApp_ID, sToken); },
+ reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.reload, sNamespaceOrApp_ID, sToken); },
+ destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.beforeApp.destroy, sNamespaceOrApp_ID, sToken); }
+ },
+ afterApp:
+ {
+ render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.render, sNamespaceOrApp_ID, sToken); },
+ reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.reload, sNamespaceOrApp_ID, sToken); },
+ destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.afterApp.destroy, sNamespaceOrApp_ID, sToken); }
+ },
+ app:
+ {
+ render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.render, sNamespaceOrApp_ID, sToken); },
+ reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.reload, sNamespaceOrApp_ID, sToken); },
+ destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.app.destroy, sNamespaceOrApp_ID, sToken); }
+ }
+ }
+ };
+})());
exports.F2 = F2;
diff --git a/sdk/f2.min.js b/sdk/f2.min.js
index 78bb3ace..88c73abf 100644
--- a/sdk/f2.min.js
+++ b/sdk/f2.min.js
@@ -121,6 +121,7 @@ F2.extend("Events",function(){var e=new EventEmitter2({wildcard:!0});return e.se
F2.extend("Rpc",function(){var e={},t="",n={},r=new RegExp("^"+F2.Constants.Sockets.EVENT),i=new RegExp("^"+F2.Constants.Sockets.RPC),s=new RegExp("^"+F2.Constants.Sockets.RPC_CALLBACK),o=new RegExp("^"+F2.Constants.Sockets.LOAD),u=new RegExp("^"+F2.Constants.Sockets.UI_RPC),a=function(){var e,t=!1,r=[],i=new easyXDM.Socket({onMessage:function(s,u){if(!t&&o.test(s)){s=s.replace(o,"");var a=F2.parse(s);a.length==2&&(e=a[0],n[e.instanceId]={config:e,socket:i},F2.registerApps([e],[a[1]]),jQuery.each(r,function(t,n){c(e,s,u)}),t=!0)}else t?c(e,s,u):r.push(s)}})},f=function(e,n){var r=jQuery(e.root);r=r.is("."+F2.Constants.Css.APP_CONTAINER)?r:r.find("."+F2.Constants.Css.APP_CONTAINER);if(!r.length){F2.log("Unable to locate app in order to establish secure connection.");return}var i={scrolling:"no",style:{width:"100%"}};e.height&&(i.style.height=e.height+"px");var s=new easyXDM.Socket({remote:t,container:r.get(0),props:i,onMessage:function(t,n){c(e,t,n)},onReady:function(){s.postMessage(F2.Constants.Sockets.LOAD+F2.stringify([e,n],F2.appConfigReplacer))}});return s},l=function(e,t){return function(){F2.Rpc.call(e,F2.Constants.Sockets.RPC_CALLBACK,t,[].slice.call(arguments).slice(2))}},c=function(t,n,o){function f(e,t){var n=String(t).split(".");for(var r=0;r','',"
Alert!
","",'
',"
",e,"
","
",'","
"].join("")},n=function(e){return['
','',"
Confirm
","",'
',"
",e,"
","
",'","
"].join("")};return{alert:function(n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.alert()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.alert",[].slice.call(arguments)):jQuery(e(n)).on("show",function(){var e=this;jQuery(e).find(".btn-primary").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()})}).modal({backdrop:!0})},confirm:function(e,r,i){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.confirm()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.confirm",[].slice.call(arguments)):jQuery(n(e)).on("show",function(){var e=this;jQuery(e).find(".btn-ok").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()}),jQuery(e).find(".btn-cancel").on("click",function(){jQuery(e).modal("hide").remove(),(i||jQuery.noop)()})}).modal({backdrop:!0})}}}(),setTitle:function(e){F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"setTitle",[e]):jQuery(t.root).find("."+F2.Constants.Css.APP_TITLE).text(e)},showMask:function(e,n){F2.UI.showMask(t.instanceId,e,n)},updateHeight:r,Views:function(){var e=new EventEmitter2,i=/change/i;e.setMaxListeners(0);var s=function(e){return i.test(e)?!0:(F2.log('"'+e+'" is not a valid F2.UI.Views event name'),!1)};return{change:function(i){typeof i=="function"?this.on("change",i):typeof i=="string"&&(t.isSecure&&!F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Views.change",[].slice.call(arguments)):F2.inArray(i,t.views)&&(jQuery("."+F2.Constants.Css.APP_VIEW,n).addClass("hide").filter('[data-f2-view="'+i+'"]',n).removeClass("hide"),r(),e.emit("change",i)))},off:function(t,n){s(t)&&e.off(t,n)},on:function(t,n){s(t)&&e.on(t,n)}}}()}};return t.hideMask=function(e,t){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.hideMask()");return}if(F2.Rpc.isRemote(e)&&!jQuery(t).is("."+F2.Constants.Css.APP))F2.Rpc.call(e,F2.Constants.Sockets.RPC,"F2.UI.hideMask",[e,jQuery(t).selector]);else{var n=jQuery(t),r=n.find("> ."+F2.Constants.Css.MASK).remove();n.removeClass(F2.Constants.Css.MASK_CONTAINER),n.data(F2.Constants.Css.MASK_CONTAINER)&&n.css({position:"static"})}},t.init=function(t){e=t,e.UI=jQuery.extend(!0,{},F2.ContainerConfig.UI,e.UI||{})},t.showMask=function(t,n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.showMask()");return}if(F2.Rpc.isRemote(t)&&jQuery(n).is("."+F2.Constants.Css.APP))F2.Rpc.call(t,F2.Constants.Sockets.RPC,"F2.UI.showMask",[t,jQuery(n).selector,r]);else{r&&!e.UI.Mask.loadingIcon&&F2.log("Unable to display loading icon. Please set F2.ContainerConfig.UI.Mask.loadingIcon when calling F2.init();");var i=jQuery(n).addClass(F2.Constants.Css.MASK_CONTAINER),s=jQuery("
").height("100%").width("100%").addClass(F2.Constants.Css.MASK);e.UI.Mask.useClasses||s.css({"background-color":e.UI.Mask.backgroundColor,"background-image":e.UI.Mask.loadingIcon?"url("+e.UI.Mask.loadingIcon+")":"","background-position":"50% 50%","background-repeat":"no-repeat",display:"block",left:0,"min-height":30,padding:0,position:"absolute",top:0,"z-index":e.UI.Mask.zIndex,filter:"alpha(opacity="+e.UI.Mask.opacity*100+")",opacity:e.UI.Mask.opacity}),i.css("position")==="static"&&(i.css({position:"relative"}),i.data(F2.Constants.Css.MASK_CONTAINER,!0)),i.append(s)}},t}());
F2.extend("",function(){var _apps={},_config=!1,_afterAppRender=function(e,t){var n=_config.afterAppRender||function(e,t){return jQuery(t).appendTo("body")},r=n(e,t);if(!!_config.afterAppRender&&!r){F2.log("F2.ContainerConfig.afterAppRender() must return the DOM Element that contains the app");return}return jQuery(r).addClass(F2.Constants.Css.APP),r.get(0)},_appRender=function(e,t){function n(e){return jQuery("").append(e).html()}return t=n(jQuery(t).addClass(F2.Constants.Css.APP_CONTAINER+" "+e.appId)),_config.appRender&&(t=_config.appRender(e,t)),n(t)},_beforeAppRender=function(e){var t=_config.beforeAppRender||jQuery.noop;return t(e)},_hydrateAppConfig=function(e){e.instanceId=e.instanceId||F2.guid(),e.views=e.views||[],F2.inArray(F2.Constants.Views.HOME,e.views)||e.views.push(F2.Constants.Views.HOME)},_initAppEvents=function(e){jQuery(e.root).on("click","."+F2.Constants.Css.APP_VIEW_TRIGGER+"["+F2.Constants.Views.DATA_ATTRIBUTE+"]",function(t){t.preventDefault();var n=jQuery(this).attr(F2.Constants.Views.DATA_ATTRIBUTE).toLowerCase();n==F2.Constants.Views.REMOVE?F2.removeApp(e.instanceId):e.ui.Views.change(n)})},_initContainerEvents=function(){var e,t=function(){F2.Events.emit(F2.Constants.Events.CONTAINER_WIDTH_CHANGE)};jQuery(window).on("resize",function(){clearTimeout(e),e=setTimeout(t,100)})},_isInit=function(){return!!_config},_loadApps=function(appConfigs,appManifest){appConfigs=[].concat(appConfigs);if(appConfigs.length==1&&appConfigs[0].isSecure&&!_config.isSecureAppPage){_loadSecureApp(appConfigs[0],appManifest);return}if(appConfigs.length!=appManifest.apps.length){F2.log("The number of apps defined in the AppManifest do not match the number requested.",appManifest);return}var scripts=appManifest.scripts||[],styles=appManifest.styles||[],inlines=appManifest.inlineScripts||[],scriptCount=scripts.length,scriptsLoaded=0,appInit=function(){jQuery.each(appConfigs,function(e,t){t.ui=new F2.UI(t),F2.Apps[t.appId]!==undefined&&(typeof F2.Apps[t.appId]=="function"?setTimeout(function(){_apps[t.instanceId].app=new F2.Apps[t.appId](t,appManifest.apps[e],t.root),_apps[t.instanceId].app.init!==undefined&&_apps[t.instanceId].app.init()},0):F2.log("app initialization class is defined but not a function. ("+t.appId+")"))})},stylesFragment=[];jQuery.each(styles,function(e,t){stylesFragment.push('')}),jQuery("head").append(stylesFragment.join("")),jQuery.each(appManifest.apps,function(e,t){appConfigs[e].root=_afterAppRender(appConfigs[e],_appRender(appConfigs[e],t.html)),_initAppEvents(appConfigs[e])}),jQuery.each(scripts,function(i,e){jQuery.ajax({url:e,cache:!0,async:!1,dataType:"script",type:"GET",success:function(){++scriptsLoaded==scriptCount&&(jQuery.each(inlines,function(i,e){try{eval(e)}catch(exception){F2.log("Error loading inline script: "+exception+"\n\n"+e)}}),appInit())},error:function(t,n,r){F2.log(["Failed to load script ("+e+")",r.toString()])}})}),scriptCount||appInit()},_loadSecureApp=function(e,t){_config.secureAppPagePath?(e.root=_afterAppRender(e,_appRender(e,"")),e.ui=new F2.UI(e),_initAppEvents(e),F2.Rpc.register(e,t)):F2.log('Unable to load secure app: "secureAppPagePath" is not defined in F2.ContainerConfig.')},_validateApp=function(e){return e.appId?e.manifestUrl?!0:(F2.log('manifestUrl" missing from app object'),!1):(F2.log('"appId" missing from app object'),!1)};return{getContainerState:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.getContainerState()");return}return jQuery.map(_apps,function(e,t){return{appId:e.config.appId}})},init:function(e){_config=e||{},(!!_config.secureAppPagePath||_config.isSecureAppPage)&&F2.Rpc.init(_config.secureAppPagePath?_config.secureAppPagePath:!1),F2.UI.init(_config),_config.isSecureAppPage||_initContainerEvents()},isInit:_isInit,registerApps:function(e,t){if(!_isInit()){F2.log("F2.init() must be called before F2.registerApps()");return}if(!e){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}var n=[],r={},i={},s=!1;e=[].concat(e),t=t||[],s=!!t.length;if(!e.length){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}if(e.length&&s&&e.length!=t.length){F2.log('The length of "apps" does not equal the length of "appManifests"');return}jQuery.each(e,function(e,i){if(!_validateApp(i))return;_hydrateAppConfig(i),i.root=_beforeAppRender(i),_apps[i.instanceId]={config:i},s?_loadApps(i,t[e]):i.enableBatchRequests&&!i.isSecure?(r[i.manifestUrl.toLowerCase()]=r[i.manifestUrl.toLowerCase()]||[],r[i.manifestUrl.toLowerCase()].push(i)):n.push({apps:[i],url:i.manifestUrl})}),s||(jQuery.each(r,function(e,t){n.push({url:e,apps:t})}),jQuery.each(n,function(e,t){var n=F2.Constants.JSONP_CALLBACK+t.apps[0].appId;i[n]=i[n]||[],i[n].push(t)}),jQuery.each(i,function(e,t){var n=function(r,i){if(!i)return;jQuery.ajax({url:i.url,data:{params:F2.stringify(i.apps,F2.appConfigReplacer)},jsonp:!1,jsonpCallback:r,dataType:"jsonp",success:function(e){_loadApps(i.apps,e)},error:function(e,t,n){F2.log("Failed to load app(s)",n.toString(),i.apps),jQuery.each(i.apps,function(e,t){F2.log("Removed failed "+t.name+" app",t),F2.removeApp(t.instanceId)})},complete:function(){n(e,t.pop())}})};n(e,t.pop())}))},removeAllApps:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.removeAllApps()");return}jQuery.each(_apps,function(e,t){F2.removeApp(t.config.instanceId)})},removeApp:function(e){if(!_isInit()){F2.log("F2.init() must be called before F2.removeApp()");return}_apps[e]&&(jQuery(_apps[e].config.root).fadeOut(function(){jQuery(this).remove()}),delete _apps[e])}}}());
+F2.extend("AppHandlers",function(){function r(e){return typeof Node=="object"?e instanceof Node:e&&typeof e=="object"&&typeof e.nodeType=="number"&&typeof e.nodeName=="string"}function i(e){return typeof HTMLElement=="object"?e instanceof HTMLElement:e&&typeof e=="object"&&e.nodeType===1&&typeof e.nodeName=="string"}var e=F2.guid(),t=F2.guid(),n={beforeApp:{render:[],reload:[],destroy:[]},afterApp:{render:[],reload:[],destroy:[]},app:{render:[],reload:[],destroy:[]}},s=function(e,t){if(!e||!e.length)throw"Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again.";o(e[0]),e.shift();var n=e.length,s={func:null,namespace:null,app_id:null,domNode:null};switch(n){case 1:if(e[0]&&t&&(r(e[0])||i(e[0])))s.domNode=e[0];else{if(!e[0]||typeof e[0]!="function")throw"Invalid or null argument passed. Argument must be of type function or a native dom node";s.func=e[0]}break;case 2:if(e[0]&&e[1]&&typeof e[0]=="string"&&t&&(r(e[1])||i(e[1])))s.app_id=e[0],s.domNode=e[1];else if(e[0]&&e[1]&&typeof e[0]=="string"&&typeof e[1]=="function")s.app_id=e[0],s.func=e[1];else{if(!e[0]||!e[1]||typeof e[0]!="function"||typeof e[1]!="string")throw"Invalid or null argument(s) passed. Argument[0] must be of type function or string (to represent app_id). Argument[1] must be native domnode, function, or string (to represent namespace)";s.func=e[0],s.namespace=e[1]}break;case 3:if(e[0]&&e[1]&&typeof e[0]=="string"&&t&&(r(e[1])||i(e[1]))&&typeof e[2]=="string")s.app_id=e[0],s.domNode=e[1],s.namespace=e[2];else{if(!e[0]||!e[1]||typeof e[0]!="string"||typeof e[1]!="function"||typeof e[2]!="string")throw"Invalid or null argument(s) passed. Argument[0] must be of type string that represents the app_id. Argument[1] must be native domnode or function. Argument[2] must be of type string to represent a namespace.";s.app_id=e[0],s.func=e[1],s.namespace=e[2]}break;default:throw"Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again."}return s},o=function(n){if(e!=n&&t!=n)throw"Invalid token passed. Please verify that you have correctly received and stored token from F2.AppHandlers.getToken()."},u=function(e,t,n){o(n);if(!t&&!e)return;if(!t&&e)e=[];else if(t&&e){t=t.toLowerCase();var r=[];for(var i=0,s=e.length;i
Date: Thu, 21 Mar 2013 23:17:33 -0600
Subject: [PATCH 013/181] Fixed console-test.js
---
tests/spec/console-test.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/spec/console-test.js b/tests/spec/console-test.js
index 62bd471e..b4896c9f 100644
--- a/tests/spec/console-test.js
+++ b/tests/spec/console-test.js
@@ -1,6 +1,6 @@
describe("Console", function() {
it ("should fail", function() {
- expect(false).toBeTruthy();
+ expect(false).toBeFalsy();
});
it ("should succeed", function() {
From 29d5e4fd9dfcd4186e6606dcf74e35cdc05124a9 Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Thu, 21 Mar 2013 23:21:19 -0600
Subject: [PATCH 014/181] Added Travis CI Image to README.md.
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index 86c7cbe8..064f6e05 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+[![Build Status](https://travis-ci.org/Ali-Khatami/F2.png?branch=Container-Event_Rendering-Additions)](https://travis-ci.org/Ali-Khatami/F2)
+
# An open framework for the financial services industry.
F2 is an open and free web integration framework designed to help you and other financial industry participants develop custom solutions that combine the best tools and content from multiple providers into one, privately-labeled, seamlessly integrated front-end. The [essential components](http://docs.openf2.org/index.html#framework) defined by the F2 specification are the Container, Apps, Context and Store—all supported under the hood by **[F2.js](http://docs.openf2.org/f2js-sdk.html)**, a JavaScript SDK which provides an extensible foundation powering all F2-based web applications.
From 0bae15e8b14a6c0527dd240feac9dd1ec3addce7 Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Thu, 21 Mar 2013 23:22:12 -0600
Subject: [PATCH 015/181] Rebuilt so it minified app_handlers.
---
docs/js/f2.js | 286 +-------------------------------------------------
1 file changed, 1 insertion(+), 285 deletions(-)
diff --git a/docs/js/f2.js b/docs/js/f2.js
index 43070c7d..88c73abf 100644
--- a/docs/js/f2.js
+++ b/docs/js/f2.js
@@ -121,291 +121,7 @@ F2.extend("Events",function(){var e=new EventEmitter2({wildcard:!0});return e.se
F2.extend("Rpc",function(){var e={},t="",n={},r=new RegExp("^"+F2.Constants.Sockets.EVENT),i=new RegExp("^"+F2.Constants.Sockets.RPC),s=new RegExp("^"+F2.Constants.Sockets.RPC_CALLBACK),o=new RegExp("^"+F2.Constants.Sockets.LOAD),u=new RegExp("^"+F2.Constants.Sockets.UI_RPC),a=function(){var e,t=!1,r=[],i=new easyXDM.Socket({onMessage:function(s,u){if(!t&&o.test(s)){s=s.replace(o,"");var a=F2.parse(s);a.length==2&&(e=a[0],n[e.instanceId]={config:e,socket:i},F2.registerApps([e],[a[1]]),jQuery.each(r,function(t,n){c(e,s,u)}),t=!0)}else t?c(e,s,u):r.push(s)}})},f=function(e,n){var r=jQuery(e.root);r=r.is("."+F2.Constants.Css.APP_CONTAINER)?r:r.find("."+F2.Constants.Css.APP_CONTAINER);if(!r.length){F2.log("Unable to locate app in order to establish secure connection.");return}var i={scrolling:"no",style:{width:"100%"}};e.height&&(i.style.height=e.height+"px");var s=new easyXDM.Socket({remote:t,container:r.get(0),props:i,onMessage:function(t,n){c(e,t,n)},onReady:function(){s.postMessage(F2.Constants.Sockets.LOAD+F2.stringify([e,n],F2.appConfigReplacer))}});return s},l=function(e,t){return function(){F2.Rpc.call(e,F2.Constants.Sockets.RPC_CALLBACK,t,[].slice.call(arguments).slice(2))}},c=function(t,n,o){function f(e,t){var n=String(t).split(".");for(var r=0;r','',"
Alert!
","",'
',"
",e,"
","
",'","
"].join("")},n=function(e){return['
','',"
Confirm
","",'
',"
",e,"
","
",'","
"].join("")};return{alert:function(n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.alert()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.alert",[].slice.call(arguments)):jQuery(e(n)).on("show",function(){var e=this;jQuery(e).find(".btn-primary").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()})}).modal({backdrop:!0})},confirm:function(e,r,i){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.confirm()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.confirm",[].slice.call(arguments)):jQuery(n(e)).on("show",function(){var e=this;jQuery(e).find(".btn-ok").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()}),jQuery(e).find(".btn-cancel").on("click",function(){jQuery(e).modal("hide").remove(),(i||jQuery.noop)()})}).modal({backdrop:!0})}}}(),setTitle:function(e){F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"setTitle",[e]):jQuery(t.root).find("."+F2.Constants.Css.APP_TITLE).text(e)},showMask:function(e,n){F2.UI.showMask(t.instanceId,e,n)},updateHeight:r,Views:function(){var e=new EventEmitter2,i=/change/i;e.setMaxListeners(0);var s=function(e){return i.test(e)?!0:(F2.log('"'+e+'" is not a valid F2.UI.Views event name'),!1)};return{change:function(i){typeof i=="function"?this.on("change",i):typeof i=="string"&&(t.isSecure&&!F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Views.change",[].slice.call(arguments)):F2.inArray(i,t.views)&&(jQuery("."+F2.Constants.Css.APP_VIEW,n).addClass("hide").filter('[data-f2-view="'+i+'"]',n).removeClass("hide"),r(),e.emit("change",i)))},off:function(t,n){s(t)&&e.off(t,n)},on:function(t,n){s(t)&&e.on(t,n)}}}()}};return t.hideMask=function(e,t){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.hideMask()");return}if(F2.Rpc.isRemote(e)&&!jQuery(t).is("."+F2.Constants.Css.APP))F2.Rpc.call(e,F2.Constants.Sockets.RPC,"F2.UI.hideMask",[e,jQuery(t).selector]);else{var n=jQuery(t),r=n.find("> ."+F2.Constants.Css.MASK).remove();n.removeClass(F2.Constants.Css.MASK_CONTAINER),n.data(F2.Constants.Css.MASK_CONTAINER)&&n.css({position:"static"})}},t.init=function(t){e=t,e.UI=jQuery.extend(!0,{},F2.ContainerConfig.UI,e.UI||{})},t.showMask=function(t,n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.showMask()");return}if(F2.Rpc.isRemote(t)&&jQuery(n).is("."+F2.Constants.Css.APP))F2.Rpc.call(t,F2.Constants.Sockets.RPC,"F2.UI.showMask",[t,jQuery(n).selector,r]);else{r&&!e.UI.Mask.loadingIcon&&F2.log("Unable to display loading icon. Please set F2.ContainerConfig.UI.Mask.loadingIcon when calling F2.init();");var i=jQuery(n).addClass(F2.Constants.Css.MASK_CONTAINER),s=jQuery("
").height("100%").width("100%").addClass(F2.Constants.Css.MASK);e.UI.Mask.useClasses||s.css({"background-color":e.UI.Mask.backgroundColor,"background-image":e.UI.Mask.loadingIcon?"url("+e.UI.Mask.loadingIcon+")":"","background-position":"50% 50%","background-repeat":"no-repeat",display:"block",left:0,"min-height":30,padding:0,position:"absolute",top:0,"z-index":e.UI.Mask.zIndex,filter:"alpha(opacity="+e.UI.Mask.opacity*100+")",opacity:e.UI.Mask.opacity}),i.css("position")==="static"&&(i.css({position:"relative"}),i.data(F2.Constants.Css.MASK_CONTAINER,!0)),i.append(s)}},t}());
F2.extend("",function(){var _apps={},_config=!1,_afterAppRender=function(e,t){var n=_config.afterAppRender||function(e,t){return jQuery(t).appendTo("body")},r=n(e,t);if(!!_config.afterAppRender&&!r){F2.log("F2.ContainerConfig.afterAppRender() must return the DOM Element that contains the app");return}return jQuery(r).addClass(F2.Constants.Css.APP),r.get(0)},_appRender=function(e,t){function n(e){return jQuery("").append(e).html()}return t=n(jQuery(t).addClass(F2.Constants.Css.APP_CONTAINER+" "+e.appId)),_config.appRender&&(t=_config.appRender(e,t)),n(t)},_beforeAppRender=function(e){var t=_config.beforeAppRender||jQuery.noop;return t(e)},_hydrateAppConfig=function(e){e.instanceId=e.instanceId||F2.guid(),e.views=e.views||[],F2.inArray(F2.Constants.Views.HOME,e.views)||e.views.push(F2.Constants.Views.HOME)},_initAppEvents=function(e){jQuery(e.root).on("click","."+F2.Constants.Css.APP_VIEW_TRIGGER+"["+F2.Constants.Views.DATA_ATTRIBUTE+"]",function(t){t.preventDefault();var n=jQuery(this).attr(F2.Constants.Views.DATA_ATTRIBUTE).toLowerCase();n==F2.Constants.Views.REMOVE?F2.removeApp(e.instanceId):e.ui.Views.change(n)})},_initContainerEvents=function(){var e,t=function(){F2.Events.emit(F2.Constants.Events.CONTAINER_WIDTH_CHANGE)};jQuery(window).on("resize",function(){clearTimeout(e),e=setTimeout(t,100)})},_isInit=function(){return!!_config},_loadApps=function(appConfigs,appManifest){appConfigs=[].concat(appConfigs);if(appConfigs.length==1&&appConfigs[0].isSecure&&!_config.isSecureAppPage){_loadSecureApp(appConfigs[0],appManifest);return}if(appConfigs.length!=appManifest.apps.length){F2.log("The number of apps defined in the AppManifest do not match the number requested.",appManifest);return}var scripts=appManifest.scripts||[],styles=appManifest.styles||[],inlines=appManifest.inlineScripts||[],scriptCount=scripts.length,scriptsLoaded=0,appInit=function(){jQuery.each(appConfigs,function(e,t){t.ui=new F2.UI(t),F2.Apps[t.appId]!==undefined&&(typeof F2.Apps[t.appId]=="function"?setTimeout(function(){_apps[t.instanceId].app=new F2.Apps[t.appId](t,appManifest.apps[e],t.root),_apps[t.instanceId].app.init!==undefined&&_apps[t.instanceId].app.init()},0):F2.log("app initialization class is defined but not a function. ("+t.appId+")"))})},stylesFragment=[];jQuery.each(styles,function(e,t){stylesFragment.push('')}),jQuery("head").append(stylesFragment.join("")),jQuery.each(appManifest.apps,function(e,t){appConfigs[e].root=_afterAppRender(appConfigs[e],_appRender(appConfigs[e],t.html)),_initAppEvents(appConfigs[e])}),jQuery.each(scripts,function(i,e){jQuery.ajax({url:e,cache:!0,async:!1,dataType:"script",type:"GET",success:function(){++scriptsLoaded==scriptCount&&(jQuery.each(inlines,function(i,e){try{eval(e)}catch(exception){F2.log("Error loading inline script: "+exception+"\n\n"+e)}}),appInit())},error:function(t,n,r){F2.log(["Failed to load script ("+e+")",r.toString()])}})}),scriptCount||appInit()},_loadSecureApp=function(e,t){_config.secureAppPagePath?(e.root=_afterAppRender(e,_appRender(e,"")),e.ui=new F2.UI(e),_initAppEvents(e),F2.Rpc.register(e,t)):F2.log('Unable to load secure app: "secureAppPagePath" is not defined in F2.ContainerConfig.')},_validateApp=function(e){return e.appId?e.manifestUrl?!0:(F2.log('manifestUrl" missing from app object'),!1):(F2.log('"appId" missing from app object'),!1)};return{getContainerState:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.getContainerState()");return}return jQuery.map(_apps,function(e,t){return{appId:e.config.appId}})},init:function(e){_config=e||{},(!!_config.secureAppPagePath||_config.isSecureAppPage)&&F2.Rpc.init(_config.secureAppPagePath?_config.secureAppPagePath:!1),F2.UI.init(_config),_config.isSecureAppPage||_initContainerEvents()},isInit:_isInit,registerApps:function(e,t){if(!_isInit()){F2.log("F2.init() must be called before F2.registerApps()");return}if(!e){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}var n=[],r={},i={},s=!1;e=[].concat(e),t=t||[],s=!!t.length;if(!e.length){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}if(e.length&&s&&e.length!=t.length){F2.log('The length of "apps" does not equal the length of "appManifests"');return}jQuery.each(e,function(e,i){if(!_validateApp(i))return;_hydrateAppConfig(i),i.root=_beforeAppRender(i),_apps[i.instanceId]={config:i},s?_loadApps(i,t[e]):i.enableBatchRequests&&!i.isSecure?(r[i.manifestUrl.toLowerCase()]=r[i.manifestUrl.toLowerCase()]||[],r[i.manifestUrl.toLowerCase()].push(i)):n.push({apps:[i],url:i.manifestUrl})}),s||(jQuery.each(r,function(e,t){n.push({url:e,apps:t})}),jQuery.each(n,function(e,t){var n=F2.Constants.JSONP_CALLBACK+t.apps[0].appId;i[n]=i[n]||[],i[n].push(t)}),jQuery.each(i,function(e,t){var n=function(r,i){if(!i)return;jQuery.ajax({url:i.url,data:{params:F2.stringify(i.apps,F2.appConfigReplacer)},jsonp:!1,jsonpCallback:r,dataType:"jsonp",success:function(e){_loadApps(i.apps,e)},error:function(e,t,n){F2.log("Failed to load app(s)",n.toString(),i.apps),jQuery.each(i.apps,function(e,t){F2.log("Removed failed "+t.name+" app",t),F2.removeApp(t.instanceId)})},complete:function(){n(e,t.pop())}})};n(e,t.pop())}))},removeAllApps:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.removeAllApps()");return}jQuery.each(_apps,function(e,t){F2.removeApp(t.config.instanceId)})},removeApp:function(e){if(!_isInit()){F2.log("F2.init() must be called before F2.removeApp()");return}_apps[e]&&(jQuery(_apps[e].config.root).fadeOut(function(){jQuery(this).remove()}),delete _apps[e])}}}());
-/**
- * Allows container developers more flexibility when it comes to handling app interaction.
- * @class F2.AppHandlers
- */
-F2.extend('AppHandlers', (function() {
-
- // the hidden token that we will check against every time someone tries to add, remove, fire handler
- var _ct = F2.guid();
- var _f2t = F2.guid();
-
- var _handlerCollection = {
- beforeApp:
- {
- render: [],
- reload: [],
- destroy: []
- },
- afterApp:
- {
- render: [],
- reload: [],
- destroy: []
- },
- app:
- {
- render: [],
- reload: [],
- destroy: []
- }
- };
-
- //Returns true if it is a DOM node
- function _isNode(o){
- return (
- typeof Node === "object" ? o instanceof Node :
- o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string"
- );
- }
-
- //Returns true if it is a DOM element
- function _isElement(o){
- return (
- typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
- o && typeof o === "object" && o.nodeType === 1 && typeof o.nodeName==="string"
- );
- }
-
- var _createHandler = function(arOriginalArgs, bDomNodeAppropriate)
- {
- if(!arOriginalArgs || !arOriginalArgs.length) { throw "Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again." }
-
- // will throw an exception and stop execution if the token is invalid
- _validateToken(arOriginalArgs[0]);
-
- // remove the token from the arguments since we have validated it and no longer need it
- arOriginalArgs.shift();
-
- var iArgCount = arOriginalArgs.length;
-
- // create handler structure. Not all arguments properties will be populated/used.
- var handler = {
- func: null,
- namespace: null,
- app_id: null,
- domNode: null
- };
-
- // based on the argument count try to create a handler.
- switch(iArgCount)
- {
- case 1:
- // method signature(oDomNode)
- if(arOriginalArgs[0] && bDomNodeAppropriate && (_isNode(arOriginalArgs[0]) || _isElement(arOriginalArgs[0]))
- {
- handler.domNode = arOriginalArgs[0];
- }
- // method signature (function(){})
- else if(arOriginalArgs[0] && typeof(arOriginalArgs[0]) == "function")
- {
- handler.func = arOriginalArgs[0];
- }
- // error
- else
- {
- throw "Invalid or null argument passed. Argument must be of type function or a native dom node";
- }
- break;
- case 2:
- // method signature ("APP_ID" ,oDomNode)
- if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- bDomNodeAppropriate &&
- (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1]))
- {
- handler.app_id = arOriginalArgs[0];
- handler.domNode = arOriginalArgs[1];
- }
- // method signature ("APP_ID" ,function(){})
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- typeof(arOriginalArgs[1]) == "function"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.func = arOriginalArgs[1];
- }
- // method signature (function(){} ,"NAMESPACE")
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "function" &&
- typeof(arOriginalArgs[1]) == "string"
- )
- {
- handler.func = arOriginalArgs[0];
- handler.namespace = arOriginalArgs[1];
- }
- // error
- else
- {
- throw "Invalid or null argument(s) passed. Argument[0] must be of type function or string (to represent app_id). Argument[1] must be native domnode, function, or string (to represent namespace)";
- }
- break;
- case 3:
- // method signature ("APP_ID", oDomNode, "NAMESPACE")
- if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- bDomNodeAppropriate &&
- (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1])) &&
- typeof(arOriginalArgs[2]) == "string"
- {
- handler.app_id = arOriginalArgs[0];
- handler.domNode = arOriginalArgs[1];
- handler.namespace = arOriginalArgs[2];
- }
- // method signature ("APP_ID", function(){}, "NAMESPACE")
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- typeof(arOriginalArgs[1]) == "function" &&
- typeof(arOriginalArgs[2]) == "string"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.func = arOriginalArgs[1];
- handler.namespace = arOriginalArgs[2];
- }
- else
- {
- throw "Invalid or null argument(s) passed. Argument[0] must be of type string that represents the app_id. Argument[1] must be native domnode or function. Argument[2] must be of type string to represent a namespace.";
- }
- break;
- // throw exception if there are 0 or 4+ arguments
- default:
- throw "Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again.";
- }
-
- return handler;
- };
-
- var _validateToken = function(sToken)
- {
- // check token against F2 and Container
- if(_ct != sToken && _f2t != sToken) { throw "Invalid token passed. Please verify that you have correctly received and stored token from F2.AppHandlers.getToken()."; }
- };
-
- var _removeHandler = function(arHandleCollection, sNamespaceOrApp_ID, sToken)
- {
- // will throw an exception and stop execution if the token is invalid
- _validateToken(sToken);
-
- if(!sNamespaceOrApp_ID && !arHandleCollection)
- {
- return;
- }
- else if(!sNamespaceOrApp_ID && arHandleCollection)
- {
- arHandleCollection = [];
- }
- else if(sNamespaceOrApp_ID && arHandleCollection)
- {
- sNamespaceOrApp_ID = sNamespaceOrApp_ID.toLowerCase();
-
- var newEvents = [];
-
- for(var i = 0, j = arHandleCollection.length; i < j; i++)
- {
- var currentHandler = arHandleCollection[i];
- if(currentHandler)
- {
- if(currentHandler.app_id != sNamespaceOrApp_ID) && currentHandler.namespace != sNamespaceOrApp_ID)
- {
- newEvents.push(currentHandler);
- }
- }
- }
-
- arHandleCollection = newEvents;
- }
- };
-
- return {
- /**
- * Allows container developer to retrieve a special token which must be passed to
- * all On and Off methods. This function will self destruct so be sure to keep the response
- * inside of a closure somewhere.
- * @method getToken
- */
- getToken: function()
- {
- // delete this method for security that way only the container has access to the token 1 time.
- // kind of James Bond-ish, this message will self destruct immediately.
- delete this.getToken;
- // return the token, which we validate against.
- return _ct;
- },
- /**
- * Allows F2 to get a token internally
- * @method __f2GetToken
- * @private
- */
- __f2GetToken: function()
- {
- // delete this method for security that way only the F2 internally has access to the token 1 time.
- // kind of James Bond-ish, this message will self destruct immediately.
- delete this.__f2GetToken;
- // return the token, which we validate against.
- return _f2t;
- },
- /**
- * Allows F2 to trigger events internally
- * @method __f2Trigger
- * @private
- */
- __f2Trigger: {
- },
- on: {
- beforeApp:
- {
- render: function() { _handlerCollection.beforeApp.render.push(_createHandler(arguments)); },
- reload: function(){ _handlerCollection.beforeApp.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.beforeApp.destroy.push(_createHandler(arguments)); }
- },
- afterApp:
- {
- render: function() { _handlerCollection.afterApp.render.push(_createHandler(arguments)); },
- reload: function(){ _handlerCollection.afterApp.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.afterApp.destroy.push(_createHandler(arguments)); }
- },
- app:
- {
- render: function() { _handlerCollection.app.render.push(_createHandler(arguments), true); },
- reload: function(){ _handlerCollection.app.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.app.destroy.push(_createHandler(arguments)); }
- }
- },
- off: {
- beforeApp:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.beforeApp.destroy, sNamespaceOrApp_ID, sToken); }
- },
- afterApp:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.afterApp.destroy, sNamespaceOrApp_ID, sToken); }
- },
- app:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.app.destroy, sNamespaceOrApp_ID, sToken); }
- }
- }
- };
-})());;
+F2.extend("AppHandlers",function(){function r(e){return typeof Node=="object"?e instanceof Node:e&&typeof e=="object"&&typeof e.nodeType=="number"&&typeof e.nodeName=="string"}function i(e){return typeof HTMLElement=="object"?e instanceof HTMLElement:e&&typeof e=="object"&&e.nodeType===1&&typeof e.nodeName=="string"}var e=F2.guid(),t=F2.guid(),n={beforeApp:{render:[],reload:[],destroy:[]},afterApp:{render:[],reload:[],destroy:[]},app:{render:[],reload:[],destroy:[]}},s=function(e,t){if(!e||!e.length)throw"Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again.";o(e[0]),e.shift();var n=e.length,s={func:null,namespace:null,app_id:null,domNode:null};switch(n){case 1:if(e[0]&&t&&(r(e[0])||i(e[0])))s.domNode=e[0];else{if(!e[0]||typeof e[0]!="function")throw"Invalid or null argument passed. Argument must be of type function or a native dom node";s.func=e[0]}break;case 2:if(e[0]&&e[1]&&typeof e[0]=="string"&&t&&(r(e[1])||i(e[1])))s.app_id=e[0],s.domNode=e[1];else if(e[0]&&e[1]&&typeof e[0]=="string"&&typeof e[1]=="function")s.app_id=e[0],s.func=e[1];else{if(!e[0]||!e[1]||typeof e[0]!="function"||typeof e[1]!="string")throw"Invalid or null argument(s) passed. Argument[0] must be of type function or string (to represent app_id). Argument[1] must be native domnode, function, or string (to represent namespace)";s.func=e[0],s.namespace=e[1]}break;case 3:if(e[0]&&e[1]&&typeof e[0]=="string"&&t&&(r(e[1])||i(e[1]))&&typeof e[2]=="string")s.app_id=e[0],s.domNode=e[1],s.namespace=e[2];else{if(!e[0]||!e[1]||typeof e[0]!="string"||typeof e[1]!="function"||typeof e[2]!="string")throw"Invalid or null argument(s) passed. Argument[0] must be of type string that represents the app_id. Argument[1] must be native domnode or function. Argument[2] must be of type string to represent a namespace.";s.app_id=e[0],s.func=e[1],s.namespace=e[2]}break;default:throw"Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again."}return s},o=function(n){if(e!=n&&t!=n)throw"Invalid token passed. Please verify that you have correctly received and stored token from F2.AppHandlers.getToken()."},u=function(e,t,n){o(n);if(!t&&!e)return;if(!t&&e)e=[];else if(t&&e){t=t.toLowerCase();var r=[];for(var i=0,s=e.length;i
Date: Thu, 21 Mar 2013 23:36:47 -0600
Subject: [PATCH 016/181] Trying to figure out $.ajax failures in Travis CI.
---
.travis.yml | 2 +-
tests/index-console.html | 28 ----------------------------
tests/index.html | 3 ++-
tests/spec/console-test.js | 5 +++++
4 files changed, 8 insertions(+), 30 deletions(-)
delete mode 100644 tests/index-console.html
diff --git a/.travis.yml b/.travis.yml
index 58a29891..b85e0827 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,4 +20,4 @@ script:
# Change these to jasmine or mocha if necessary
- "wget https://raw.github.com/mark-rushakoff/OpenPhantomScripts/master/phantom-jasmine.js"
# Make sure to change test/test.html to the path to your test page
- - "phantomjs phantom-jasmine.js tests/index-console.html"
+ - "phantomjs phantom-jasmine.js tests/index.html"
diff --git a/tests/index-console.html b/tests/index-console.html
deleted file mode 100644
index 250c8f92..00000000
--- a/tests/index-console.html
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
- Jasmine Console Spec Runner
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/index.html b/tests/index.html
index 4d106845..4cc579db 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -19,10 +19,11 @@
+
-
+
-
+
From 5bbc5f087478fb82bc9536d4c3ce711fecbf043d Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Thu, 21 Mar 2013 23:49:07 -0600
Subject: [PATCH 020/181] Looks like that was it! jQuery wasn't being loaded
because of the // trick. Forced it to https:// and life is good!
---
tests/index.html | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/tests/index.html b/tests/index.html
index 2897a13f..4c31d7f8 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -18,8 +18,7 @@
-
-
+
From 78388ab6cb99e85887ce6198b312e03c73d27180 Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Fri, 22 Mar 2013 23:10:39 -0600
Subject: [PATCH 021/181] Testing addition script in Travis CI. Also made some
updates to the docs.
---
.travis.yml | 1 +
docs/container-development.html | 786 --------------------
docs/extending-f2.html | 199 -----
docs/js/f2.js | 2 +-
docs/sdk/classes/F2.AppHandlers.html | 714 +++++++++++++++++-
docs/sdk/data.json | 125 +++-
docs/sdk/files/sdk_src_app_handlers.js.html | 375 +++++-----
docs/sdk/index.html | 6 +-
sdk/f2.debug.js | 342 +++++----
sdk/f2.min.js | 2 +-
sdk/f2.no-third-party.js | 342 +++++----
sdk/src/app_handlers.js | 342 +++++----
12 files changed, 1612 insertions(+), 1624 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index b85e0827..bbcdcf8a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,3 +21,4 @@ script:
- "wget https://raw.github.com/mark-rushakoff/OpenPhantomScripts/master/phantom-jasmine.js"
# Make sure to change test/test.html to the path to your test page
- "phantomjs phantom-jasmine.js tests/index.html"
+ - "phantomjs phantom-jasmine.js tests/index-amd.html"
diff --git a/docs/container-development.html b/docs/container-development.html
index 7b178214..e69de29b 100644
--- a/docs/container-development.html
+++ b/docs/container-development.html
@@ -1,786 +0,0 @@
-
-
-
- F2 - Container Development
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
To help you get started building an F2 container, browse through the resources below. To jump start your F2 container and app development, download the F2 template (which now includes a basic container) or follow the instructions below.
In developing a more advanced container, the HTML document's body element would contain additional markup and allow for specific positioning or placement of apps. Additionally, more advanced containers could introduce features and functionality to their apps in the form of authentication APIs, streaming data feeds, federated search, etc. All containers must follow the F2 design guidelines.
-
-
-
Basic App
-
Create your basic F2 app manifest and save it as /path/to/your/manifest.js using this code below. Note the path to this file should be specified in the manifestUrl property within the _appConfigs array in your basic container (shown above).
Now with a basic container and a basic app, you can load your F2 container and expect to see:
-
-
In getting to this point, you've only scratched the surface of F2 containers and apps. Continue reading and understanding the F2 spec to build exactly the financial solutions that our customers want.
-
-
-
Sample Apps and Container
-
Good news! In the project repo on GitHub, you will find a basic container along with a number of sample apps which demonstrate functionality far beyond the basic app above. Once you clone or download the project repository, open the sample container by pointing your browser at:
-
http://localhost/F2/examples/container/
-
-
-
Configuration
-
It is assumed you will be developing an F2 container locally and have a localhost setup. The URLs mentioned in this specification also assume you have configured your F2 container to run at http://localhost/F2/. The examples provided as part of the project repository demonstrate apps written in different languages (PHP, JavaScript, C#). While it is not a requirement you have a web server configured on your computer, it will certainly allow you to more deeply explore the sample apps.
-
To better understand F2 and the role of containers, you need to understand the role of apps. If you haven’t already, read more about apps in the Framework.
Design considerations are an important first step when creating a new container. Content can range from news to research to multimedia, and content should be presented using Progressive Enhancement, Mobile First and Responsive Design methodologies. That is to say multimedia content, for example, should be shown plugin-free (using HTML5 video or audio elements) for capable browsers and fallback to Flash-based players for browsers that do not yet support HTML5 related technologies. (VideoJS is good example of open-source JavaScript and CSS "that makes it easier to work with and build on HTML5 video, today.")
-
If App Developers embed URLs back to their own websites or to third party sites, URLs must be opened in a new window as to not interrupt the experience of someone using the container. If authentication is required on an App Developer's site, this can be accomplished with pass-through authentication using encrypted URLs as discussed in Single Sign On.
-
-
Choices
-
In order to ensure that apps built using F2 are successful, they must be accessible. As such, F2 made choices for which open-source libraries and frameworks would be leveraged to reduce the level of effort across F2 adopters.
Ultimately, the responsibility of app design falls on either the Container or App Developer. In many cases, Container Developers will provide App Developers will visual designs, style guides or other assets required to ensure apps have the form and function for a given container. Container Developers may also provide CSS for App Developers to adhere to—which should be easy since F2 enforces a consistent HTML structure across all containers and apps.
-
-
-
-
-
Developing F2 Containers
-
A container is a browser-based desktop-like application which brings F2 apps together onto a seamless user interface. It also can provide horsepower to its apps in the form of request-response web services or streaming data feeds.
You will find a basic container in the project repo on GitHub along with a number of sample apps.
-
Once the script tag has been added, it is up to the Container Developer to configure and customize the container. The first step is getting a ContainerID.
-
-
-
F2 ContainerID
-
To develop a production F2 container, you need a unique identifier called a ContainerID. This ContainerID will be unique to your container across the entire open financial framework ecosystem. The format of the ContainerID looks like this: com_container_companyName_containerName, where the companyName "namespace" is your company name and containerName is the name of your container.
-
As an example, your ContainerID could look like this:
-
com_container_acmecorp_watchlist
-
If you built more than one container while working at Acme Corporation, you could create more ContainerIDs. All of these are valid:
-
-
com_container_acmecorp_activetrader
-
com_container_acmecorp_retail
-
com_container_acmecorp_mobilestreamer
-
-
To guarantee uniqueness, we will provide a ContainerID generation service that allows customization of your ContainerID in the Developer Center.
-
-
-
Setting Up Your Project
-
Once you have your ContainerID, start by setting up your container project. You will need at least one configuration in addition to an HTML page: the app configs. (In the GitHub repository, an example is found in /examples/container/js/sampleApps.js.) This doesn't need to be a static javascript file like sampleApps.js but the structure and format of the app configs is important.
-
-
-
App Configs
-
An F2 Container Provider must deliver the app configs to its container before calling F2.init(). The app configurations are represented quite simply as a list of AppConfig objects. These could be stored in a JavaScript array or in an enterprise-class database. AppConfig objects contain app meta data provided by the App Developer when he creates his app in the Developer Center.
The F2.js JavaScript SDK provides an API for providers to configure their containers. Every container must be setup using ContainerConfig and the methods available.
-
In the container's $(document).ready(), add the F2.init():
The appRender() method allows the container to wrap an app in extra HTML. The function should accept an F2.AppConfig object and also a string of HTML. The extra HTML can provide links to edit app settings and remove an app from the container. See F2.Constants.Css for CSS classes that should be applied to elements.
-
-
-
BeforeAppRender
-
The beforeAppRender() method allows the container to render HTML for an app before the AppManifest for an app has loaded. This can be useful if the design calls for loading spinners to appear for each app before each app is loaded and rendered to the page.
-
-
-
AfterAppRender
-
The afterAppRender() method allows the container to override how an app's HTML is inserted into the page. The function should accept an F2.AppConfig object and also a string of HTML.
Container Developers have the opportunity to customize some user interface (UI) elements which propagate to the App Developers' toolkit in F2.js. One of those is F2.UI.Mask. The Mask object contains configuration defaults for the F2.UI.showMask() and F2.UI.hideMask() methods.
Included in the F2.UI.Mask configuration object are the following properties: backgroundColor, loadingIcon, opacity, useClasses, and zIndex. Each of these F2.UI.Mask properties is detailed in the F2.js SDK docs.
F2 is a web integration framework which means apps are inherently insecure—at least those non-secure apps. Following this spec, App Developers must avoid CSS collisions and JavaScript namespace issues to provide users with the best possible experience.
As discussed in Developing F2 Containers: F2 ContainerID, to develop an F2 container, you need a unique identifier called an ContainerID. This ContainerID will be unique to your container across the entire open financial framework ecosystem. The format of the ContainerID looks like this: com_container_companyName_appName, where the companyName "namespace" is your company name and appName is the name of your app.
-
To avoid styling conflicts or other display issues related to app-provided style sheets, App Developers must namespace their CSS selectors. While there are strict rules for App Developers, the same rules apply to Container Developers. This is especially true when implementing mutliple containers.
-
In the event there are multiple containers, every CSS selector in container-provided style sheets must look like this:
Adhering to one of the OpenAjax Alliance goals, F2 also promotes the concept of an uncluttered global javascript namespace. For Container and App Developers alike, this means following this spec closely and ensuring javascript code is contained inside closures or is extended as a new namespace on F2.
-
The F2.js SDK was designed with extensibility in mind and therefore custom logic can be added on the F2 namespace.
-
Example:
-
F2.extend('YourPluginName', (function(){
- return {
- doSomething: function(){
- F2.log("Something has been done.");
- }
- };
-})());
Apps are capable of sharing "context" with the container and other nearby apps. All apps have context which means the app "knows" who is using it and the content it contains. It is aware of an individual's data entitlements and user information that the container is requested to share (name, email, company, etc).
-
This means if a user wants to create a ticker-focused container so they can keep a close eye on shares of Proctor & Gamble, the container can send "symbol context" to any listening apps that are smart enough to refresh when ticker symbol PG is entered in the container's search box.
-
While apps can have context themselves, the responsibility for managing context switching or context passing falls on the container. The container assumes the role of a traffic cop—managing which data goes where. By using JavaScript events, the container can listen for events sent by apps and likewise apps can listen for events sent by the container. To provide a layer of security, this means apps cannot communicate directly with other apps on their own; apps must communicate via an F2 container to other apps since the container controls the F2.Events API.
Each container will be responsible for hosting the F2.js JavaScript SDK. The F2 SDK not only provides the consistent mechanism app developers have come to expect for loading their apps on the container, but also contains an event API for handling context.
-
Important It is important to note that while apps can have context themselves, the responsibility for managing context switching or context passing falls on the container. The container assumes the role of a traffic cop—managing which data goes where. By using JavaScript events, the container can listen for events sent by apps and likewise apps can listen for events sent by the container. This means apps cannot communicate directly with other apps on their own; apps communicate via the container to other apps since the container controls the F2.Events API.
-
Let's look at some code.
-
-
-
Container-to-App Context
-
In this example, the container broadcasts, or emits, a javascript event defined in F2.Events.Constants. The F2.Events.emit() method accepts two arguments: the event name and an optional data object.
To listen to the F2.Constants.Events.CONTAINER_SYMBOL_CHANGE event inside your F2 app, you can use this code to trigger an alert dialog with the symbol:
-
F2.Events.on(
- F2.Constants.Events.CONTAINER_SYMBOL_CHANGE,
- function(data){
- F2.log("The symbol was changed to " + data.symbol);
- }
-);
-
The F2.Events.on() method accepts the event name and listener function as arguments. Read the SDK for more information.
-
Note For a full list of support event types, browse to the SDK for F2.Constants.Events.
-
-
-
Container-to-App Context (Server)
-
Often times containers will want to send context to apps during app registration. This is possible through the AppConfig.context property. This property can contain any javascript object—a string, a number, an array or an object.
When F2.registerApps() is called, the appConfig is serialized and posted to the app's manifest URL. The serialized object converts to stringified JSON:
The appConfig object is sent to the server using the params querystring name as shown in the example below. This is the complete app manifest request sent by F2.registerApps() with the appConfig URL-encoded, of course:
This demonstrates complete flexibility of passing arbitrary context values from the container to any F2 app.
-
-
-
App-to-Container Context
-
In this example, your app emits an event indicating a user is looking at a different stock ticker within your app. Using F2.Events.emit() in your code, your app broadcasts the new symbol. As with container-to-app context passing, the F2.Events.emit() method accepts two arguments: the event name and an optional data object.
The container would need to listen to your apps' broadcasted F2.Constants.Events.APP_SYMBOL_CHANGE event using code like this:
-
F2.Events.on(
- F2.Constants.Events.APP_SYMBOL_CHANGE,
- function(data){
- F2.log("The symbol was changed to " + data.symbol);
- }
-);
-
Note For a full list of support event types, browse to the SDK for F2.Constants.Events.
-
-
-
App-to-App Context
-
Apps can also pass context between apps. If there are two or more apps on a container with similar context and the ability to receive messages (yes, through event listeners, context receiving is opt-in), apps can communicate with each other. To communicate with another app, each app will have to know the event name along with the type of data being passed. Let's take a look.
-
Within "App 1", context is sent using F2.Events.emit():
Within "App 2", context is received using F2.Events.on():
-
F2.Events.on(
- "buy_stock",
- function(data){
- if (data.isAvailableToPurchase){
- F2.log("Trade ticket order for " + data.symbol + " at $" + data.price);
- } else {
- F2.log("This stock is not available for purchase.")
- }
- }
-);
-
-
-
More Complex Context
-
The examples above demonstrate simple Context objects. In the event more complex data and/or data types are needed, F2 Context can support any JavaScript object—a string, a number, a function, an array or an object.
-
This is an example Context object demonstrating arbitrary JavaScript objects:
The F2 app listening for the buy_stock event would fire the callback function.
-
F2.Events.on(
- "buy_stock",
- function(data){
- F2.log("Trade ticket order for " + data.symbol + " at $" + data.price);
- //..populate the trade ticket...
- //fire the callback
- if (typeofdata.callback === 'function'){
- data.callback();
- }
- }
-);
-
-
-
Types of Context
-
Context is a term used to describe the state of an F2 container and its apps. At the same time, context is also the information passed from Container-to-App or from App-to-App or from App-to-Container. In the examples shown above, two types of context were shown: symbol and trade ticket context. It is important to realize F2.js allows client-side messaging between third parties using a collection of arbitrary name-value pairs. This provides the utmost flexibility and affords container providers the option to define context within their container.
-
-
Universal F2 Instrument ID
-
Said another way, while { symbol:"AAPL", name: "Apple, Inc" } can be used to communicate symbol context, developers could also use { symbol: "123456789" } to identify Apple, Inc. The latter is more likely given not all apps would programmatically understand AAPL but—given symbol lookup services—would understand 123456789 as the universal F2 identifier for Apple, Inc. It is clear Container and App Developers alike would prefer to communicate with a guaranteed-to-never-change universal ID for all instrument types across all asset classes. Further details will be forthcoming as the F2 specification evolves.
-
-
-
-
-
-
App Integration
-
The process of loading apps on a container happens through a method called F2.registerApps(). The Container Developer must call this method—which accepts two arguments, one required, one optional— after F2.init() is called. If this method isn't called, no apps can be loaded on the container.
-
The two arguments provided to registerApps() are an array of AppConfig objects and, optionally, an array of AppManifest objects. As F2.js parses each AppConfig, the apps are validated, hydrated with some additional properties, and saved in F2 memory on the container.
-
Regardless of where the container's AppConfig comes from, integrating apps is a simple process. For the purposes of this example, we will use an Acme Corp news app.
-
Let's look at some container code.
-
-
Static App Configuration
-
First, we define the AppConfigs in a hard-coded_appConfigs array. Secondly, when the document is ready, we call F2.init() and subsequently F2.registerApps() with the single argument.
This javascript code will insert the Acme Corp news app into the container's DOM, provided the appRender method is configured correctly.
-
-
-
Dynamic App Configuration
-
Alternatively, AppConfigs could live in a database—eventually the F2 Store—at which time container developers could provide their containers with AppManifests instead of relying on each AppConfig.manifestUrl property to be retrieved and parsed at run time.
-
Such an implementation would require the container developer to make a HTTP call to a Store web service to retrieve AppConfigs and AppManifests. You are already familiar with what the AppConfig looks like, but if you aren't sure what an AppManifest looks like, take note of this empty manifest.
An example of a container making a request to the F2 Store for AppConfigs and AppManifests:
-
(function(){
-
- var _appConfigs = [], _appManifests = [];
-
- //make request to Store web service
- var $req = $.ajax({
- url: 'https://store.openf2.org/getApps',
- dataType: 'jsonp'
- });
-
- //parse successful response
- $req.done(function(jqxhr,txtStatus){
- jqxhr = jqxhr || {};
- if (jqxhr.status == "good"){
- _appConfigs = jqxhr.appConfigs || [];
- _appManifests = jqxhr.appManifests || [];
- //load
- loadContainer();
- } else {
- F2.log("Store web service did not do something 'good'.", jqxhr, txtStatus);
- }
- });
-
- //handle errors
- $req.fail(function(jqxhr,txtStatus){
- F2.log("Store web service failed.", jqxhr, txtStatus);
- });
-
- //wrap this up so we can call it in $req.done()
- var loadContainer = function(){
- $(document).ready(function(){
- //init F2 container
- F2.init({
- //define ContainerConfig properties
- appRender: function(appConfig, html){ ... },
- beforeAppRender: function(appConfig, html){ ... },
- afterAppRender: function(appConfig){ ... },
-
- //setup UI
- UI:{
- Mask:{
- loadingIcon:'./img/spinner.gif',
- backgroundColor: '#fff',
- opacity: 0.5
- }
- }
- });
-
- //load apps
- F2.registerApps(_appConfigs, _appManifests);
-
- });
- }//loadContainer
-
-})();
-
Important The _appConfigs and _appManifests arrays must be of equal length, and the object at each index must be a parallel reference. This means the AppConfig and AppManifest for Acme Corp's news app must be in _appConfigs[0] and _appManifests[0].
-
There are numerous benefits to dynamic app configuration, most notably performance and security. In the dynamic model, AppManifests have already been requested and loaded before a user opens the container reducing the overall number of outbound HTTP requests. Security is improved because Container Developers have the opportunity to parse and scrub AppManifest contents before F2.js injects markup in the AppManifest.html property into the container DOM.
-
-
-
-
-
Secure Apps
-
Security is a fundamental requirement of any F2 container and many F2 apps. With that in mind, the integration of secure apps on a container requires more attention and effort. The process of app integration remains largely the same for integrating secure apps with one significant addition: a second container.
-
To support a secured container environment, one of the choices made when writing this specification was the inclusion of an open-source cross-domain in-browser secure messaging library. For this, F2 relies on easyXDM. EasyXDM helps front-end developers safely work around the Same Origin Policy using browser-supported techniques without compromising the user experience. For all browsers, the easyXDM transport stack offers bi-directionality, reliability, queueing and sender-verification.
-
-
Container Config
-
The process of configuring an F2 container to be secure is identical to that of an unsecure container. As such, every container must be setup using ContainerConfig and the methods available.
-
In the secure container's $(document).ready(), add the F2.init():
For secure containers, an additional property must be set on the ContainerConfig within F2.init(). Assuiming the container is hosted at https://www.domain.com/container, the following config would be appropriate:
This secureAppPagePath property allows the container to specify which page is used when loading secure apps. To guarantee security, the page must reside on a different domain than the parent container.
-
Important Therefore Container Developers need two containers: one non-secure (parent), one secure (child). The parent container can follow the basic template style and must call F2.init() and F2.registerApps() appropriately. Per the above, it must also define the secureAppPagePath property in its ContainerConfig. To see a working container, browse to the examples in the project repo on GitHub.
-
Since it will be loaded in an iframe and like its parent, the secure child container must also include a copy of the F2.js SDK. Additionally, it must also call F2.init() with a unique ContainerConfig.
-
F2.init({
- appRender:function(appConfig, html) {
- return [
- '<div class="span4">',
- html,
- '</div>'
- ].join('');
- },
- afterAppRender:function(appConfig, html) { ... },
-
- //now set this property to true to tell F2 this is the secure child frame.
- isSecureAppPage:true
-});
-
When the parent container calls registerApps(), F2 looks at each AppConfig for the isSecure bool. If the property is set to true, F2 inserts the secure app inside an iframe and instantiates the easyXDM transport stack. To see a working secure container, browse to the examples in the project repo on GitHub.
-
-
-
-
-
Utilities
-
The F2.js JavaScript SDK provides utility methods for Container Developers. These are available within the F2 namespace and complete details are in the Reference documentation.
-
-
-
-
F2 UI
-
There are some utility methods provided within F2.js in the UI namespace. These helpers are for controlling layout, showing (or hiding) loading spinners, modals, managing views within apps, and more. To see which UI helpers are available to App Developers, read about F2.UI for apps.
-
For Container Developers, the use of F2's UI is more than likely limited to customizing the design aesthetic (CSS) and configuring the UI properties.
User or content entitlements are the responsibility of the App developer. Many apps will need to be decoupled from the content that they need. This could include apps like research aggregation, news filtering, streaming market data, etc. Similarly to how companies build their own websites today with their own authentication and access (or content) entitlements, F2 apps are no different.
-
Further details around app entitlements will be forthcoming as the F2 specification evolves.
-
-
-
-
Single Sign-On
-
Single sign-on (SSO) will be a shared responsibility between the Container and App Developer. In some cases, containers will want all its apps to be authenticated seamlessly for users, and that will have to be negotiated between Container and App Developers. For the purposes of this documentation, it is assumed Container Developers will build and host authentication for access to their container(s).
-
Once a user is authenticated on the container, how is the user then authenticated with all of the apps? Encrypted URLs.*
-
Note The Container Developer is free to utilize any app authentication method they deem fit. Container Developers and App Developers will need to work together to finalize the authentication details.
-
-
Using Encrypted URLs
-
Implementing SSO using encrypted URLs is a simple and straight-forward authentication mechanism for securing cross-domain multi-provider apps. To guarantee security between the Container and App Developers, secure API contracts must be negotiated. This includes, but is not limited to, the choice of cryptographic algorithm (such as AES) and the exchange of public keys.
-
When the Container Developer calls F2.registerApps(), custom logic should be added to append encrypted user credentials—on a need-to-know basis—to each app requiring authentication.
-
-
-
Considerations
-
Authentication is a critical part of any container-app relationship. There are a plethora of SSO implementations and there are many considerations for both Container and App Developers alike.
-
Further details around container and app single sign-on will be forthcoming as the F2 specification evolves.
-At its core, F2 is an open framework. To create a truly open and flexible foundation with F2.js, F2 can be extended with custom plugins. Extending F2 with plugins provides direct access to F2.js SDK methods and can save your teams a lot of time.
-
-
-
-
-
Plugins
-
Now that you're comfortable with F2 and all the individual components of the framework, you are ready to extend F2 and add your own custom logic in the form of an F2 plugin.
-
There is a separate repository on GitHub dedicated to F2 plugin development. If you write a plugin you'd like to contribute back to the community, commit it to F2Plugins.
Plugins are encapsulated in JavaScript closures as demonstrated below. There are three arguments which can be passed into F2.extend(): namespace, object, and overwrite. For full details, read the F2.js SDK documentation.
-
F2.extend('YourPluginName', (function(){
- return {
- doSomething: function(){
- F2.log("Something has been done.");
- }
- };
-})());
-
To call your custom method shown above:
-
...
-F2.YourPluginName.doSomething();
-...
-
This method call writes Something has been done. to the Console.
-
-
-
-
Best Practices
-
The purpose of developing a plugin is to encapsulate clever logic in a single javascript function to save time and effort performing repetitive tasks. Here are some best practices to keep in mind:
-
-
Always use F2.extend() and wrap your plugin in a closure.
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/js/f2.js b/docs/js/f2.js
index 88c73abf..64e5d37e 100644
--- a/docs/js/f2.js
+++ b/docs/js/f2.js
@@ -121,7 +121,7 @@ F2.extend("Events",function(){var e=new EventEmitter2({wildcard:!0});return e.se
F2.extend("Rpc",function(){var e={},t="",n={},r=new RegExp("^"+F2.Constants.Sockets.EVENT),i=new RegExp("^"+F2.Constants.Sockets.RPC),s=new RegExp("^"+F2.Constants.Sockets.RPC_CALLBACK),o=new RegExp("^"+F2.Constants.Sockets.LOAD),u=new RegExp("^"+F2.Constants.Sockets.UI_RPC),a=function(){var e,t=!1,r=[],i=new easyXDM.Socket({onMessage:function(s,u){if(!t&&o.test(s)){s=s.replace(o,"");var a=F2.parse(s);a.length==2&&(e=a[0],n[e.instanceId]={config:e,socket:i},F2.registerApps([e],[a[1]]),jQuery.each(r,function(t,n){c(e,s,u)}),t=!0)}else t?c(e,s,u):r.push(s)}})},f=function(e,n){var r=jQuery(e.root);r=r.is("."+F2.Constants.Css.APP_CONTAINER)?r:r.find("."+F2.Constants.Css.APP_CONTAINER);if(!r.length){F2.log("Unable to locate app in order to establish secure connection.");return}var i={scrolling:"no",style:{width:"100%"}};e.height&&(i.style.height=e.height+"px");var s=new easyXDM.Socket({remote:t,container:r.get(0),props:i,onMessage:function(t,n){c(e,t,n)},onReady:function(){s.postMessage(F2.Constants.Sockets.LOAD+F2.stringify([e,n],F2.appConfigReplacer))}});return s},l=function(e,t){return function(){F2.Rpc.call(e,F2.Constants.Sockets.RPC_CALLBACK,t,[].slice.call(arguments).slice(2))}},c=function(t,n,o){function f(e,t){var n=String(t).split(".");for(var r=0;r','',"
Alert!
","",'
',"
",e,"
","
",'","
"].join("")},n=function(e){return['
','',"
Confirm
","",'
',"
",e,"
","
",'","
"].join("")};return{alert:function(n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.alert()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.alert",[].slice.call(arguments)):jQuery(e(n)).on("show",function(){var e=this;jQuery(e).find(".btn-primary").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()})}).modal({backdrop:!0})},confirm:function(e,r,i){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.confirm()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.confirm",[].slice.call(arguments)):jQuery(n(e)).on("show",function(){var e=this;jQuery(e).find(".btn-ok").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()}),jQuery(e).find(".btn-cancel").on("click",function(){jQuery(e).modal("hide").remove(),(i||jQuery.noop)()})}).modal({backdrop:!0})}}}(),setTitle:function(e){F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"setTitle",[e]):jQuery(t.root).find("."+F2.Constants.Css.APP_TITLE).text(e)},showMask:function(e,n){F2.UI.showMask(t.instanceId,e,n)},updateHeight:r,Views:function(){var e=new EventEmitter2,i=/change/i;e.setMaxListeners(0);var s=function(e){return i.test(e)?!0:(F2.log('"'+e+'" is not a valid F2.UI.Views event name'),!1)};return{change:function(i){typeof i=="function"?this.on("change",i):typeof i=="string"&&(t.isSecure&&!F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Views.change",[].slice.call(arguments)):F2.inArray(i,t.views)&&(jQuery("."+F2.Constants.Css.APP_VIEW,n).addClass("hide").filter('[data-f2-view="'+i+'"]',n).removeClass("hide"),r(),e.emit("change",i)))},off:function(t,n){s(t)&&e.off(t,n)},on:function(t,n){s(t)&&e.on(t,n)}}}()}};return t.hideMask=function(e,t){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.hideMask()");return}if(F2.Rpc.isRemote(e)&&!jQuery(t).is("."+F2.Constants.Css.APP))F2.Rpc.call(e,F2.Constants.Sockets.RPC,"F2.UI.hideMask",[e,jQuery(t).selector]);else{var n=jQuery(t),r=n.find("> ."+F2.Constants.Css.MASK).remove();n.removeClass(F2.Constants.Css.MASK_CONTAINER),n.data(F2.Constants.Css.MASK_CONTAINER)&&n.css({position:"static"})}},t.init=function(t){e=t,e.UI=jQuery.extend(!0,{},F2.ContainerConfig.UI,e.UI||{})},t.showMask=function(t,n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.showMask()");return}if(F2.Rpc.isRemote(t)&&jQuery(n).is("."+F2.Constants.Css.APP))F2.Rpc.call(t,F2.Constants.Sockets.RPC,"F2.UI.showMask",[t,jQuery(n).selector,r]);else{r&&!e.UI.Mask.loadingIcon&&F2.log("Unable to display loading icon. Please set F2.ContainerConfig.UI.Mask.loadingIcon when calling F2.init();");var i=jQuery(n).addClass(F2.Constants.Css.MASK_CONTAINER),s=jQuery("
").height("100%").width("100%").addClass(F2.Constants.Css.MASK);e.UI.Mask.useClasses||s.css({"background-color":e.UI.Mask.backgroundColor,"background-image":e.UI.Mask.loadingIcon?"url("+e.UI.Mask.loadingIcon+")":"","background-position":"50% 50%","background-repeat":"no-repeat",display:"block",left:0,"min-height":30,padding:0,position:"absolute",top:0,"z-index":e.UI.Mask.zIndex,filter:"alpha(opacity="+e.UI.Mask.opacity*100+")",opacity:e.UI.Mask.opacity}),i.css("position")==="static"&&(i.css({position:"relative"}),i.data(F2.Constants.Css.MASK_CONTAINER,!0)),i.append(s)}},t}());
F2.extend("",function(){var _apps={},_config=!1,_afterAppRender=function(e,t){var n=_config.afterAppRender||function(e,t){return jQuery(t).appendTo("body")},r=n(e,t);if(!!_config.afterAppRender&&!r){F2.log("F2.ContainerConfig.afterAppRender() must return the DOM Element that contains the app");return}return jQuery(r).addClass(F2.Constants.Css.APP),r.get(0)},_appRender=function(e,t){function n(e){return jQuery("").append(e).html()}return t=n(jQuery(t).addClass(F2.Constants.Css.APP_CONTAINER+" "+e.appId)),_config.appRender&&(t=_config.appRender(e,t)),n(t)},_beforeAppRender=function(e){var t=_config.beforeAppRender||jQuery.noop;return t(e)},_hydrateAppConfig=function(e){e.instanceId=e.instanceId||F2.guid(),e.views=e.views||[],F2.inArray(F2.Constants.Views.HOME,e.views)||e.views.push(F2.Constants.Views.HOME)},_initAppEvents=function(e){jQuery(e.root).on("click","."+F2.Constants.Css.APP_VIEW_TRIGGER+"["+F2.Constants.Views.DATA_ATTRIBUTE+"]",function(t){t.preventDefault();var n=jQuery(this).attr(F2.Constants.Views.DATA_ATTRIBUTE).toLowerCase();n==F2.Constants.Views.REMOVE?F2.removeApp(e.instanceId):e.ui.Views.change(n)})},_initContainerEvents=function(){var e,t=function(){F2.Events.emit(F2.Constants.Events.CONTAINER_WIDTH_CHANGE)};jQuery(window).on("resize",function(){clearTimeout(e),e=setTimeout(t,100)})},_isInit=function(){return!!_config},_loadApps=function(appConfigs,appManifest){appConfigs=[].concat(appConfigs);if(appConfigs.length==1&&appConfigs[0].isSecure&&!_config.isSecureAppPage){_loadSecureApp(appConfigs[0],appManifest);return}if(appConfigs.length!=appManifest.apps.length){F2.log("The number of apps defined in the AppManifest do not match the number requested.",appManifest);return}var scripts=appManifest.scripts||[],styles=appManifest.styles||[],inlines=appManifest.inlineScripts||[],scriptCount=scripts.length,scriptsLoaded=0,appInit=function(){jQuery.each(appConfigs,function(e,t){t.ui=new F2.UI(t),F2.Apps[t.appId]!==undefined&&(typeof F2.Apps[t.appId]=="function"?setTimeout(function(){_apps[t.instanceId].app=new F2.Apps[t.appId](t,appManifest.apps[e],t.root),_apps[t.instanceId].app.init!==undefined&&_apps[t.instanceId].app.init()},0):F2.log("app initialization class is defined but not a function. ("+t.appId+")"))})},stylesFragment=[];jQuery.each(styles,function(e,t){stylesFragment.push('')}),jQuery("head").append(stylesFragment.join("")),jQuery.each(appManifest.apps,function(e,t){appConfigs[e].root=_afterAppRender(appConfigs[e],_appRender(appConfigs[e],t.html)),_initAppEvents(appConfigs[e])}),jQuery.each(scripts,function(i,e){jQuery.ajax({url:e,cache:!0,async:!1,dataType:"script",type:"GET",success:function(){++scriptsLoaded==scriptCount&&(jQuery.each(inlines,function(i,e){try{eval(e)}catch(exception){F2.log("Error loading inline script: "+exception+"\n\n"+e)}}),appInit())},error:function(t,n,r){F2.log(["Failed to load script ("+e+")",r.toString()])}})}),scriptCount||appInit()},_loadSecureApp=function(e,t){_config.secureAppPagePath?(e.root=_afterAppRender(e,_appRender(e,"")),e.ui=new F2.UI(e),_initAppEvents(e),F2.Rpc.register(e,t)):F2.log('Unable to load secure app: "secureAppPagePath" is not defined in F2.ContainerConfig.')},_validateApp=function(e){return e.appId?e.manifestUrl?!0:(F2.log('manifestUrl" missing from app object'),!1):(F2.log('"appId" missing from app object'),!1)};return{getContainerState:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.getContainerState()");return}return jQuery.map(_apps,function(e,t){return{appId:e.config.appId}})},init:function(e){_config=e||{},(!!_config.secureAppPagePath||_config.isSecureAppPage)&&F2.Rpc.init(_config.secureAppPagePath?_config.secureAppPagePath:!1),F2.UI.init(_config),_config.isSecureAppPage||_initContainerEvents()},isInit:_isInit,registerApps:function(e,t){if(!_isInit()){F2.log("F2.init() must be called before F2.registerApps()");return}if(!e){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}var n=[],r={},i={},s=!1;e=[].concat(e),t=t||[],s=!!t.length;if(!e.length){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}if(e.length&&s&&e.length!=t.length){F2.log('The length of "apps" does not equal the length of "appManifests"');return}jQuery.each(e,function(e,i){if(!_validateApp(i))return;_hydrateAppConfig(i),i.root=_beforeAppRender(i),_apps[i.instanceId]={config:i},s?_loadApps(i,t[e]):i.enableBatchRequests&&!i.isSecure?(r[i.manifestUrl.toLowerCase()]=r[i.manifestUrl.toLowerCase()]||[],r[i.manifestUrl.toLowerCase()].push(i)):n.push({apps:[i],url:i.manifestUrl})}),s||(jQuery.each(r,function(e,t){n.push({url:e,apps:t})}),jQuery.each(n,function(e,t){var n=F2.Constants.JSONP_CALLBACK+t.apps[0].appId;i[n]=i[n]||[],i[n].push(t)}),jQuery.each(i,function(e,t){var n=function(r,i){if(!i)return;jQuery.ajax({url:i.url,data:{params:F2.stringify(i.apps,F2.appConfigReplacer)},jsonp:!1,jsonpCallback:r,dataType:"jsonp",success:function(e){_loadApps(i.apps,e)},error:function(e,t,n){F2.log("Failed to load app(s)",n.toString(),i.apps),jQuery.each(i.apps,function(e,t){F2.log("Removed failed "+t.name+" app",t),F2.removeApp(t.instanceId)})},complete:function(){n(e,t.pop())}})};n(e,t.pop())}))},removeAllApps:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.removeAllApps()");return}jQuery.each(_apps,function(e,t){F2.removeApp(t.config.instanceId)})},removeApp:function(e){if(!_isInit()){F2.log("F2.init() must be called before F2.removeApp()");return}_apps[e]&&(jQuery(_apps[e].config.root).fadeOut(function(){jQuery(this).remove()}),delete _apps[e])}}}());
-F2.extend("AppHandlers",function(){function r(e){return typeof Node=="object"?e instanceof Node:e&&typeof e=="object"&&typeof e.nodeType=="number"&&typeof e.nodeName=="string"}function i(e){return typeof HTMLElement=="object"?e instanceof HTMLElement:e&&typeof e=="object"&&e.nodeType===1&&typeof e.nodeName=="string"}var e=F2.guid(),t=F2.guid(),n={beforeApp:{render:[],reload:[],destroy:[]},afterApp:{render:[],reload:[],destroy:[]},app:{render:[],reload:[],destroy:[]}},s=function(e,t){if(!e||!e.length)throw"Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again.";o(e[0]),e.shift();var n=e.length,s={func:null,namespace:null,app_id:null,domNode:null};switch(n){case 1:if(e[0]&&t&&(r(e[0])||i(e[0])))s.domNode=e[0];else{if(!e[0]||typeof e[0]!="function")throw"Invalid or null argument passed. Argument must be of type function or a native dom node";s.func=e[0]}break;case 2:if(e[0]&&e[1]&&typeof e[0]=="string"&&t&&(r(e[1])||i(e[1])))s.app_id=e[0],s.domNode=e[1];else if(e[0]&&e[1]&&typeof e[0]=="string"&&typeof e[1]=="function")s.app_id=e[0],s.func=e[1];else{if(!e[0]||!e[1]||typeof e[0]!="function"||typeof e[1]!="string")throw"Invalid or null argument(s) passed. Argument[0] must be of type function or string (to represent app_id). Argument[1] must be native domnode, function, or string (to represent namespace)";s.func=e[0],s.namespace=e[1]}break;case 3:if(e[0]&&e[1]&&typeof e[0]=="string"&&t&&(r(e[1])||i(e[1]))&&typeof e[2]=="string")s.app_id=e[0],s.domNode=e[1],s.namespace=e[2];else{if(!e[0]||!e[1]||typeof e[0]!="string"||typeof e[1]!="function"||typeof e[2]!="string")throw"Invalid or null argument(s) passed. Argument[0] must be of type string that represents the app_id. Argument[1] must be native domnode or function. Argument[2] must be of type string to represent a namespace.";s.app_id=e[0],s.func=e[1],s.namespace=e[2]}break;default:throw"Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again."}return s},o=function(n){if(e!=n&&t!=n)throw"Invalid token passed. Please verify that you have correctly received and stored token from F2.AppHandlers.getToken()."},u=function(e,t,n){o(n);if(!t&&!e)return;if(!t&&e)e=[];else if(t&&e){t=t.toLowerCase();var r=[];for(var i=0,s=e.length;i-1){var r=t.split(".");t=r[0],n=r[1]}if(!i||!i[t])throw"Invalid EventKey passed. Check your inputs and try again.";for(var s=0,o=i[t];s-1){var s=t.split(".");t=s[0],r=s[1]}if(!i||!i[t])throw"Invalid EventKey passed. Check your inputs and try again.";return i[t].apply(F2,[e,r,n]),this},off:function(e,t){var n=null;if(t.indexOf(".")>-1){var i=t.split(".");t=i[0],n=i[1]}if(!r||!r[t])throw"Invalid EventKey passed. Check your inputs and try again.";return r[t].apply(F2,[e,n]),this},CONSTANTS:{APP_RENDER_BEFORE:"appRenderBefore",APP_RENDER:"appRender",APP_RENDER_AFTER:"appRenderAfter",APP_RELOAD_BEFORE:"appReloadBefore",APP_RELOAD:"appReload",APP_RELOAD_AFTER:"appReloadAfter",APP_DESTROY_BEFORE:"appDestroyBefore",APP_DESTROY:"appDestroy",APP_DESTROY_AFTER:"appDestroyAfter"}}}());
exports.F2 = F2;
diff --git a/docs/sdk/classes/F2.AppHandlers.html b/docs/sdk/classes/F2.AppHandlers.html
index 7f8f089e..56f7b28a 100644
--- a/docs/sdk/classes/F2.AppHandlers.html
+++ b/docs/sdk/classes/F2.AppHandlers.html
@@ -246,6 +246,8 @@
Allows container developer to retrieve a special token which must be passed to
+all On and Off methods. This function will self destruct so be sure to keep the response
+inside of a closure somewhere.
Allows container developer to retrieve a special token which must be passed to
+all On and Off methods. This function will self destruct so be sure to keep the response
+inside of a closure somewhere.
+
diff --git a/docs/sdk/data.json b/docs/sdk/data.json
index 5c39020f..15be8a1f 100644
--- a/docs/sdk/data.json
+++ b/docs/sdk/data.json
@@ -611,7 +611,16 @@
},
{
"file": "sdk\\src\\app_handlers.js",
- "line": 220,
+ "line": 170,
+ "description": "Allows container developer to retrieve a special token which must be passed to\nall On and Off methods. This function will self destruct so be sure to keep the response\ninside of a closure somewhere.",
+ "itemtype": "method",
+ "name": "getToken",
+ "class": "F2.AppHandlers",
+ "module": "f2"
+ },
+ {
+ "file": "sdk\\src\\app_handlers.js",
+ "line": 184,
"description": "Allows F2 to get a token internally",
"itemtype": "method",
"name": "__f2GetToken",
@@ -620,6 +629,112 @@
"class": "F2.AppHandlers",
"module": "f2"
},
+ {
+ "file": "sdk\\src\\app_handlers.js",
+ "line": 197,
+ "description": "Allows F2 to trigger specific app events internally.",
+ "itemtype": "method",
+ "name": "__f2Trigger",
+ "access": "private",
+ "tagname": "",
+ "class": "F2.AppHandlers",
+ "module": "f2"
+ },
+ {
+ "file": "sdk\\src\\app_handlers.js",
+ "line": 230,
+ "description": "Allows you to easily tell all apps to render in a specific location. Only valid for eventType 'appRender'.",
+ "itemtype": "method",
+ "name": "on",
+ "chainable": 1,
+ "params": [
+ {
+ "name": "token",
+ "description": "The token received from {{#crossLink \"F2.AppHandlers/getToken:methods\"}}{{/crossLink}}.",
+ "type": "String"
+ },
+ {
+ "name": "eventKey",
+ "description": "The event key to remove handler from {{#crossLink \"F2.AppHandlers/CONSTANTS:property\"}}{{/crossLink}}.",
+ "type": "String"
+ },
+ {
+ "name": "element",
+ "description": "Specific element to append your app to.",
+ "type": "HTMLElement|Node"
+ }
+ ],
+ "example": [
+ "\n\t\tF2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', $(\"#my-container\").get(0));\n\t\tF2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore.my_app_id', $(\"#my-container\").get(0));"
+ ],
+ "class": "F2.AppHandlers",
+ "module": "f2"
+ },
+ {
+ "file": "sdk\\src\\app_handlers.js",
+ "line": 241,
+ "description": "Allows you to add listener method that will be triggered when a specific event happens.",
+ "itemtype": "method",
+ "name": "on",
+ "chainable": 1,
+ "params": [
+ {
+ "name": "token",
+ "description": "The token received from {{#crossLink \"F2.AppHandlers/getToken:methods\"}}{{/crossLink}}.",
+ "type": "String"
+ },
+ {
+ "name": "eventKey",
+ "description": "The event key to remove handler from {{#crossLink \"F2.AppHandlers/CONSTANTS:property\"}}{{/crossLink}}.",
+ "type": "String"
+ },
+ {
+ "name": "listener",
+ "description": "A function that will be triggered when a specific event happens.",
+ "type": "Function"
+ }
+ ],
+ "example": [
+ "\n\t\tF2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', function() { F2.log(\"before app rendered!\"); });\t\t"
+ ],
+ "class": "F2.AppHandlers",
+ "module": "f2"
+ },
+ {
+ "file": "sdk\\src\\app_handlers.js",
+ "line": 274,
+ "description": "Allows you to remove listener methods for specific events",
+ "itemtype": "method",
+ "name": "off",
+ "chainable": 1,
+ "params": [
+ {
+ "name": "token",
+ "description": "The token received from {{#crossLink \"F2.AppHandlers/getToken:methods\"}}{{/crossLink}}.",
+ "type": "String"
+ },
+ {
+ "name": "eventKey{.namespace}",
+ "description": "The event key to determine what listeners need to be removed. If no namespace is provided all listeners for the specified event type will be removed.",
+ "type": "String"
+ }
+ ],
+ "example": [
+ "\n\t\tF2.AppHandlers.off('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore');"
+ ],
+ "class": "F2.AppHandlers",
+ "module": "f2"
+ },
+ {
+ "file": "sdk\\src\\app_handlers.js",
+ "line": 306,
+ "description": "A collection of constants for the on/off method names. Basically just here to help you.",
+ "itemtype": "property",
+ "name": "CONSTANTS",
+ "type": "Object",
+ "class": "F2.AppHandlers",
+ "module": "f2"
+ },
{
"file": "sdk\\src\\classes.js",
"line": 1,
@@ -2185,6 +2300,14 @@
}
],
"warnings": [
+ {
+ "message": "replacing incorrect tag: params with param",
+ "line": " sdk\\src\\app_handlers.js:230"
+ },
+ {
+ "message": "replacing incorrect tag: params with param",
+ "line": " sdk\\src\\app_handlers.js:241"
+ },
{
"message": "replacing incorrect tag: returns with return",
"line": " sdk\\src\\container.js:270"
diff --git a/docs/sdk/files/sdk_src_app_handlers.js.html b/docs/sdk/files/sdk_src_app_handlers.js.html
index 33e69e79..d2cafce9 100644
--- a/docs/sdk/files/sdk_src_app_handlers.js.html
+++ b/docs/sdk/files/sdk_src_app_handlers.js.html
@@ -226,7 +226,7 @@
File: sdk\src\app_handlers.js
* @class F2.AppHandlers
*/
F2.extend('AppHandlers', (function() {
-
+
// the hidden token that we will check against every time someone tries to add, remove, fire handler
var _ct = F2.guid();
var _f2t = F2.guid();
@@ -234,27 +234,52 @@
);
}
- var _createHandler = function(arOriginalArgs, bDomNodeAppropriate)
+ var _createHandler = function(token, sNamespace, func_or_element, bDomNodeAppropriate)
{
- if(!arOriginalArgs || !arOriginalArgs.length) { throw "Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again." }
+ if(!arOriginalArgs || !arOriginalArgs.length) { throw ("Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again."); }
// will throw an exception and stop execution if the token is invalid
_validateToken(arOriginalArgs[0]);
- var iArgCount = arOriginalArgs.length;
+ // remove the token from the arguments since we have validated it and no longer need it
+ arOriginalArgs.shift();
- // TODO: pop off first arg
+ var iArgCount = arOriginalArgs.length;
// create handler structure. Not all arguments properties will be populated/used.
var handler = {
- func: null,
- namespace: null,
- app_id: null,
- domNode: null
+ func: (typeof(func_or_element)) ? func_or_element : null,
+ namespace: sNamespace,
+ domNode: (_isNode(func_or_element) || _isElement(func_or_element)) ? func_or_element : null
};
- // based on the argument count try to create a handler.
- switch(iArgCount)
+ if(!handler.func && !handler.domNode)
{
- case 1:
- // method signature(oDomNode)
- if(arOriginalArgs[0] && bDomNodeAppropriate && (_isNode(arOriginalArgs[0]) || _isElement(arOriginalArgs[0]))
- {
- handler.domNode = arOriginalArgs[0];
- }
- // method signature (function(){})
- else if(arOriginalArgs[0] && typeof(arOriginalArgs[0]) == "function")
- {
- handler.func = arOriginalArgs[0];
- }
- // error
- else
- {
- throw "Invalid or null argument passed. Argument must be of type function or a native dom node";
- }
- break;
- case 2:
- // method signature ("APP_ID" ,oDomNode)
- if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- bDomNodeAppropriate &&
- (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1]))
- {
- handler.app_id = arOriginalArgs[0];
- handler.domNode = arOriginalArgs[1];
- }
- // method signature ("APP_ID" ,function(){})
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- typeof(arOriginalArgs[1]) == "function"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.func = arOriginalArgs[1];
- }
- // method signature (function(){} ,"NAMESPACE")
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "function" &&
- typeof(arOriginalArgs[1]) == "string"
- )
- {
- handler.func = arOriginalArgs[0];
- handler.namespace = arOriginalArgs[1];
- }
- // error
- else
- {
- throw "Invalid or null argument(s) passed. Argument[0] must be of type function or string (to represent app_id). Argument[1] must be native domnode, function, or string (to represent namespace)";
- }
- break;
- case 3:
- // method signature ("APP_ID", oDomNode, "NAMESPACE")
- if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- bDomNodeAppropriate &&
- (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1])) &&
- typeof(arOriginalArgs[2]) == "string"
- {
- handler.app_id = arOriginalArgs[0];
- handler.domNode = arOriginalArgs[1];
- handler.namespace = arOriginalArgs[2];
- }
- // method signature ("APP_ID", function(){}, "NAMESPACE")
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- typeof(arOriginalArgs[1]) == "function" &&
- typeof(arOriginalArgs[2]) == "string"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.func = arOriginalArgs[1];
- handler.namespace = arOriginalArgs[2];
- }
- else
- {
- throw "Invalid or null argument(s) passed. Argument[0] must be of type string that represents the app_id. Argument[1] must be native domnode or function. Argument[2] must be of type string to represent a namespace.";
- }
- break;
- // throw exception if there are 0 or 4+ arguments
- default:
- throw "Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again."
+ throw ("Invalid or null argument passed. Handler will not be added to collection. A valid dom element or callback function is required.");
+ }
+
+ if(handler.domNode && !bDomNodeAppropriate)
+ {
+ throw ("Invalid argument passed. Handler will not be added to collection. A callback function is required for this event type.");
}
return handler;
@@ -393,7 +331,7 @@
File: sdk\src\app_handlers.js
var _validateToken = function(sToken)
{
// check token against F2 and Container
- if(_ct != sToken && _f2t != sToken) { throw "Invalid token passed. Please verify that you have correctly received and stored token from F2.AppHandlers.getToken()."; }
+ if(_ct != sToken && _f2t != sToken) { throw ("Invalid token passed. Please verify that you have correctly received and stored token from F2.AppHandlers.getToken()."); }
};
var _removeHandler = function(arHandleCollection, sNamespaceOrApp_ID, sToken)
@@ -420,7 +358,7 @@
}
};
+ var _triggerEvent = function(arHandleCollection, arOriginalArgs)
+ {
+ // no errors here, basically there are no handlers to call
+ if(!arHandleCollection || !arHandleCollection.length) { return; }
+
+ // there is always 1 argument required, the first arg should always be the token.
+ if(!arOriginalArgs || !arOriginalArgs.length) { throw ("Invalid or null argument(s) passed. Token is required for all triggers. Please check your inputs and try again."); }
+
+ // will throw an exception and stop execution if the token is invalid
+ _validateToken(arOriginalArgs[0]);
+
+ // remove the token from the arguments since we have validated it and no longer need it
+ arOriginalArgs.shift();
+
+ for(var i = 0, j = arHandleCollection.length; i < j; i++)
+ {
+ arHandleCollection[i].apply(F2, arguments);
+ }
+ };
+
return {
+ /**
+ * Allows container developer to retrieve a special token which must be passed to
+ * all On and Off methods. This function will self destruct so be sure to keep the response
+ * inside of a closure somewhere.
+ * @method getToken
+ **/
getToken: function()
{
// delete this method for security that way only the container has access to the token 1 time.
@@ -441,10 +405,10 @@
File: sdk\src\app_handlers.js
return _ct;
},
/**
- * Allows F2 to get a token internally
- * @method __f2GetToken
- * @private
- */
+ * Allows F2 to get a token internally
+ * @method __f2GetToken
+ * @private
+ **/
__f2GetToken: function()
{
// delete this method for security that way only the F2 internally has access to the token 1 time.
@@ -453,75 +417,132 @@
File: sdk\src\app_handlers.js
// return the token, which we validate against.
return _f2t;
},
- __f2Trigger:
+ /**
+ * Allows F2 to trigger specific app events internally.
+ * @method __f2Trigger
+ * @private
+ **/
+ __f2Trigger: function(token, eventKey) // additional arguments will likely be passed
{
- beforeApp:
- {
- render: function(sToken) { _handlerCollection.beforeApp.render.push(_createHandler(arguments)); },
- remove: function(sToken) { _handlerCollection.beforeApp.remove.push(_createHandler(arguments)); },
- reload: function(sToken) { _handlerCollection.beforeApp.reload.push(_createHandler(arguments)); },
- destroy: function(sToken) { _handlerCollection.beforeApp.destroy.push(_createHandler(arguments)); }
- },
- afterApp:
- {
- render: function(sToken) { _handlerCollection.afterApp.render.push(_createHandler(arguments)); },
- remove: function(sToken) { _handlerCollection.afterApp.remove.push(_createHandler(arguments)); },
- reload: function(sToken) { _handlerCollection.afterApp.reload.push(_createHandler(arguments)); },
- destroy: function(sToken) { _handlerCollection.afterApp.destroy.push(_createHandler(arguments)); }
- },
- app:
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_onMethods && _onMethods[eventKey])
+ {
+ for(var i = 0, j = _onMethods[eventKey]; i < j; i++)
{
- render: function(sToken) { _handlerCollection.app.render.push(_createHandler(arguments), true); },
- remove: function(sToken) { _handlerCollection.app.remove.push(_createHandler(arguments)); },
- reload: function(sToken) { _handlerCollection.app.reload.push(_createHandler(arguments)); },
- destroy: function(sToken) { _handlerCollection.app.destroy.push(_createHandler(arguments)); }
+ var handler = _onMethods[eventKey][i];
+
+ _onMethods[eventKey][i].apply(F2, [token, sNamespace, func_or_element])
}
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
},
- on: {
- beforeApp:
- {
- render: function() { _handlerCollection.beforeApp.render.push(_createHandler(arguments)); },
- remove: function(){ _handlerCollection.beforeApp.remove.push(_createHandler(arguments)); },
- reload: function(){ _handlerCollection.beforeApp.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.beforeApp.destroy.push(_createHandler(arguments)); }
- },
- afterApp:
- {
- render: function() { _handlerCollection.afterApp.render.push(_createHandler(arguments)); },
- remove: function(){ _handlerCollection.afterApp.remove.push(_createHandler(arguments)); },
- reload: function(){ _handlerCollection.afterApp.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.afterApp.destroy.push(_createHandler(arguments)); }
- },
- app:
- {
- render: function() { _handlerCollection.app.render.push(_createHandler(arguments), true); },
- remove: function(){ _handlerCollection.app.remove.push(_createHandler(arguments)); },
- reload: function(){ _handlerCollection.app.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.app.destroy.push(_createHandler(arguments)); }
- }
+ /**
+ * Allows you to easily tell all apps to render in a specific location. Only valid for eventType 'appRender'.
+ * @method on
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey The event key to remove handler from {{#crossLink "F2.AppHandlers/CONSTANTS:property"}}{{/crossLink}}.
+ * @params {HTMLElement|Node} element Specific element to append your app to.
+ * @example
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', $("#my-container").get(0));
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore.my_app_id', $("#my-container").get(0));
+ **/
+ /**
+ * Allows you to add listener method that will be triggered when a specific event happens.
+ * @method on
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey The event key to remove handler from {{#crossLink "F2.AppHandlers/CONSTANTS:property"}}{{/crossLink}}.
+ * @params {Function} listener A function that will be triggered when a specific event happens.
+ * @example
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', function() { F2.log("before app rendered!"); });
+ **/
+ on: function(token, eventKey, func_or_element)
+ {
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_onMethods && _onMethods[eventKey])
+ {
+ _onMethods[eventKey].apply(F2, [token, sNamespace, func_or_element])
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
},
- off: {
- beforeApp:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.render, sNamespaceOrApp_ID, sToken); },
- remove: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.remove, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.beforeApp.destroy, sNamespaceOrApp_ID, sToken); }
- },
- afterApp:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.render, sNamespaceOrApp_ID, sToken); },
- remove: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.remove, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.afterApp.destroy, sNamespaceOrApp_ID, sToken); }
- },
- app:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.render, sNamespaceOrApp_ID, sToken); },
- remove: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.remove, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.app.destroy, sNamespaceOrApp_ID, sToken); }
- }
+ /**
+ * Allows you to remove listener methods for specific events
+ * @method off
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey{.namespace} The event key to determine what listeners need to be removed. If no namespace is provided all listeners for the specified event type will be removed.
+ * @example
+ * F2.AppHandlers.off('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore');
+ **/
+ off: function(token, eventKey)
+ {
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_offMethods && _offMethods[eventKey])
+ {
+ _offMethods[eventKey].apply(F2, [token, sNamespace])
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
+ },
+ /**
+ * A collection of constants for the on/off method names. Basically just here to help you.
+ * @property {Object} CONSTANTS
+ **/
+ CONSTANTS:
+ {
+ APP_RENDER_BEFORE: "appRenderBefore",
+ APP_RENDER: "appRender",
+ APP_RENDER_AFTER: "appRenderAfter",
+
+ APP_RELOAD_BEFORE: "appReloadBefore",
+ APP_RELOAD: "appReload",
+ APP_RELOAD_AFTER: "appReloadAfter",
+
+ APP_DESTROY_BEFORE: "appDestroyBefore",
+ APP_DESTROY: "appDestroy",
+ APP_DESTROY_AFTER: "appDestroyAfter"
}
};
})());
diff --git a/docs/sdk/index.html b/docs/sdk/index.html
index 3018d1a3..f61dc47e 100644
--- a/docs/sdk/index.html
+++ b/docs/sdk/index.html
@@ -217,7 +217,9 @@
-
An open framework for the financial services industry.
+
+
+
An open framework for the financial services industry.
F2 is an open and free web integration framework designed to help you and other financial industry participants develop custom solutions that combine the best tools and content from multiple providers into one, privately-labeled, seamlessly integrated front-end. The essential components defined by the F2 specification are the Container, Apps, Context and Store—all supported under the hood by F2.js, a JavaScript SDK which provides an extensible foundation powering all F2-based web applications.
diff --git a/sdk/f2.debug.js b/sdk/f2.debug.js
index 7dde48d2..096e8223 100644
--- a/sdk/f2.debug.js
+++ b/sdk/f2.debug.js
@@ -3515,6 +3515,34 @@ F2.extend('AppHandlers', (function() {
}
};
+ var _offMethods = {
+ appRenderBefore: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.beforeApp.render, sNamespaceOrApp_ID, sToken); },
+ appRender: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.app.render, sNamespaceOrApp_ID, sToken); },
+ appRenderAfter: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.afterApp.render, sNamespaceOrApp_ID, sToken); },
+
+ appReloadBefore: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.beforeApp.reload, sNamespaceOrApp_ID, sToken); },
+ appReload: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.app.reload, sNamespaceOrApp_ID, sToken); },
+ appReloadAfter: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.afterApp.reload, sNamespaceOrApp_ID, sToken); },
+
+ appDestroyBefore: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.beforeApp.destroy, sNamespaceOrApp_ID, sToken); },
+ appDestroy: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.app.destroy, sNamespaceOrApp_ID, sToken); },
+ appDestroyAfter: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.afterApp.destroy, sNamespaceOrApp_ID, sToken); }
+ };
+
+ var _onMethods = {
+ appRenderBefore: function(token, sNamespace, func_or_element) { _handlerCollection.beforeApp.render.push(_createHandler(token, sNamespace, func_or_element)); },
+ appRender: function(token, sNamespace, func_or_element) { _handlerCollection.app.render.push(_createHandler(token, sNamespace, func_or_element, true)); },
+ appRenderAfter: function(token, sNamespace, func_or_element) { _handlerCollection.afterApp.render.push(_createHandler(token, sNamespace, func_or_element)); },
+
+ appReloadBefore: function(token, sNamespace, func_or_element) { _handlerCollection.beforeApp.reload.push(_createHandler(token, sNamespace, func_or_element)); },
+ appReload: function(token, sNamespace, func_or_element) { _handlerCollection.app.reload.push(_createHandler(token, sNamespace, func_or_element)); },
+ appReloadAfter: function(token, sNamespace, func_or_element) { _handlerCollection.afterApp.reload.push(_createHandler(token, sNamespace, func_or_element)); },
+
+ appDestroyBefore: function(token, sNamespace, func_or_element) { _handlerCollection.beforeApp.destroy.push(_createHandler(token, sNamespace, func_or_element)); },
+ appDestroy: function(token, sNamespace, func_or_element) { _handlerCollection.app.destroy.push(_createHandler(token, sNamespace, func_or_element)); },
+ appDestroyAfter: function(token, sNamespace, func_or_element) { _handlerCollection.afterApp.destroy.push(_createHandler(token, sNamespace, func_or_element)); }
+ };
+
//Returns true if it is a DOM node
function _isNode(o){
return (
@@ -3531,7 +3559,7 @@ F2.extend('AppHandlers', (function() {
);
}
- var _createHandler = function(arOriginalArgs, bDomNodeAppropriate)
+ var _createHandler = function(token, sNamespace, func_or_element, bDomNodeAppropriate)
{
if(!arOriginalArgs || !arOriginalArgs.length) { throw ("Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again."); }
@@ -3545,109 +3573,19 @@ F2.extend('AppHandlers', (function() {
// create handler structure. Not all arguments properties will be populated/used.
var handler = {
- func: null,
- namespace: null,
- app_id: null,
- domNode: null
+ func: (typeof(func_or_element)) ? func_or_element : null,
+ namespace: sNamespace,
+ domNode: (_isNode(func_or_element) || _isElement(func_or_element)) ? func_or_element : null
};
- // based on the argument count try to create a handler.
- switch(iArgCount)
+ if(!handler.func && !handler.domNode)
{
- case 1:
- // method signature(oDomNode)
- if(arOriginalArgs[0] && bDomNodeAppropriate && (_isNode(arOriginalArgs[0]) || _isElement(arOriginalArgs[0])))
- {
- handler.domNode = arOriginalArgs[0];
- }
- // method signature (function(){})
- else if(arOriginalArgs[0] && typeof(arOriginalArgs[0]) == "function")
- {
- handler.func = arOriginalArgs[0];
- }
- // error
- else
- {
- throw ("Invalid or null argument passed. Argument must be of type function or a native dom node");
- }
- break;
- case 2:
- // method signature ("APP_ID" ,oDomNode)
- if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- bDomNodeAppropriate &&
- (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1]))
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.domNode = arOriginalArgs[1];
- }
- // method signature ("APP_ID" ,function(){})
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- typeof(arOriginalArgs[1]) == "function"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.func = arOriginalArgs[1];
- }
- // method signature (function(){} ,"NAMESPACE")
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "function" &&
- typeof(arOriginalArgs[1]) == "string"
- )
- {
- handler.func = arOriginalArgs[0];
- handler.namespace = arOriginalArgs[1];
- }
- // error
- else
- {
- throw ("Invalid or null argument(s) passed. Argument[0] must be of type function or string (to represent app_id). Argument[1] must be native domnode, function, or string (to represent namespace)");
- }
- break;
- case 3:
- // method signature ("APP_ID", oDomNode, "NAMESPACE")
- if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- bDomNodeAppropriate &&
- (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1])) &&
- typeof(arOriginalArgs[2]) == "string"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.domNode = arOriginalArgs[1];
- handler.namespace = arOriginalArgs[2];
- }
- // method signature ("APP_ID", function(){}, "NAMESPACE")
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- typeof(arOriginalArgs[1]) == "function" &&
- typeof(arOriginalArgs[2]) == "string"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.func = arOriginalArgs[1];
- handler.namespace = arOriginalArgs[2];
- }
- else
- {
- throw ("Invalid or null argument(s) passed. Argument[0] must be of type string that represents the app_id. Argument[1] must be native domnode or function. Argument[2] must be of type string to represent a namespace.");
- }
- break;
- // throw exception if there are 0 or 4+ arguments
- default:
- throw ("Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again.");
+ throw ("Invalid or null argument passed. Handler will not be added to collection. A valid dom element or callback function is required.");
+ }
+
+ if(handler.domNode && !bDomNodeAppropriate)
+ {
+ throw ("Invalid argument passed. Handler will not be added to collection. A callback function is required for this event type.");
}
return handler;
@@ -3694,13 +3632,33 @@ F2.extend('AppHandlers', (function() {
}
};
+ var _triggerEvent = function(arHandleCollection, arOriginalArgs)
+ {
+ // no errors here, basically there are no handlers to call
+ if(!arHandleCollection || !arHandleCollection.length) { return; }
+
+ // there is always 1 argument required, the first arg should always be the token.
+ if(!arOriginalArgs || !arOriginalArgs.length) { throw ("Invalid or null argument(s) passed. Token is required for all triggers. Please check your inputs and try again."); }
+
+ // will throw an exception and stop execution if the token is invalid
+ _validateToken(arOriginalArgs[0]);
+
+ // remove the token from the arguments since we have validated it and no longer need it
+ arOriginalArgs.shift();
+
+ for(var i = 0, j = arHandleCollection.length; i < j; i++)
+ {
+ arHandleCollection[i].apply(F2, arguments);
+ }
+ };
+
return {
/**
- * Allows container developer to retrieve a special token which must be passed to
- * all On and Off methods. This function will self destruct so be sure to keep the response
- * inside of a closure somewhere.
- * @method getToken
- */
+ * Allows container developer to retrieve a special token which must be passed to
+ * all On and Off methods. This function will self destruct so be sure to keep the response
+ * inside of a closure somewhere.
+ * @method getToken
+ **/
getToken: function()
{
// delete this method for security that way only the container has access to the token 1 time.
@@ -3710,10 +3668,10 @@ F2.extend('AppHandlers', (function() {
return _ct;
},
/**
- * Allows F2 to get a token internally
- * @method __f2GetToken
- * @private
- */
+ * Allows F2 to get a token internally
+ * @method __f2GetToken
+ * @private
+ **/
__f2GetToken: function()
{
// delete this method for security that way only the F2 internally has access to the token 1 time.
@@ -3723,51 +3681,131 @@ F2.extend('AppHandlers', (function() {
return _f2t;
},
/**
- * Allows F2 to trigger events internally
- * @method __f2Trigger
- * @private
- */
- __f2Trigger: {
- },
- on: {
- beforeApp:
- {
- render: function() { _handlerCollection.beforeApp.render.push(_createHandler(arguments)); },
- reload: function(){ _handlerCollection.beforeApp.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.beforeApp.destroy.push(_createHandler(arguments)); }
- },
- afterApp:
- {
- render: function() { _handlerCollection.afterApp.render.push(_createHandler(arguments)); },
- reload: function(){ _handlerCollection.afterApp.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.afterApp.destroy.push(_createHandler(arguments)); }
- },
- app:
+ * Allows F2 to trigger specific app events internally.
+ * @method __f2Trigger
+ * @private
+ **/
+ __f2Trigger: function(token, eventKey) // additional arguments will likely be passed
+ {
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_onMethods && _onMethods[eventKey])
+ {
+ for(var i = 0, j = _onMethods[eventKey]; i < j; i++)
{
- render: function() { _handlerCollection.app.render.push(_createHandler(arguments), true); },
- reload: function(){ _handlerCollection.app.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.app.destroy.push(_createHandler(arguments)); }
+ var handler = _onMethods[eventKey][i];
+
+ _onMethods[eventKey][i].apply(F2, [token, sNamespace, func_or_element])
}
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
},
- off: {
- beforeApp:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.beforeApp.destroy, sNamespaceOrApp_ID, sToken); }
- },
- afterApp:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.afterApp.destroy, sNamespaceOrApp_ID, sToken); }
- },
- app:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.app.destroy, sNamespaceOrApp_ID, sToken); }
- }
+ /**
+ * Allows you to easily tell all apps to render in a specific location. Only valid for eventType 'appRender'.
+ * @method on
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey The event key to remove handler from {{#crossLink "F2.AppHandlers/CONSTANTS:property"}}{{/crossLink}}.
+ * @params {HTMLElement|Node} element Specific element to append your app to.
+ * @example
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', $("#my-container").get(0));
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore.my_app_id', $("#my-container").get(0));
+ **/
+ /**
+ * Allows you to add listener method that will be triggered when a specific event happens.
+ * @method on
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey The event key to remove handler from {{#crossLink "F2.AppHandlers/CONSTANTS:property"}}{{/crossLink}}.
+ * @params {Function} listener A function that will be triggered when a specific event happens.
+ * @example
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', function() { F2.log("before app rendered!"); });
+ **/
+ on: function(token, eventKey, func_or_element)
+ {
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_onMethods && _onMethods[eventKey])
+ {
+ _onMethods[eventKey].apply(F2, [token, sNamespace, func_or_element])
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
+ },
+ /**
+ * Allows you to remove listener methods for specific events
+ * @method off
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey{.namespace} The event key to determine what listeners need to be removed. If no namespace is provided all listeners for the specified event type will be removed.
+ * @example
+ * F2.AppHandlers.off('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore');
+ **/
+ off: function(token, eventKey)
+ {
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_offMethods && _offMethods[eventKey])
+ {
+ _offMethods[eventKey].apply(F2, [token, sNamespace])
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
+ },
+ /**
+ * A collection of constants for the on/off method names. Basically just here to help you.
+ * @property {Object} CONSTANTS
+ **/
+ CONSTANTS:
+ {
+ APP_RENDER_BEFORE: "appRenderBefore",
+ APP_RENDER: "appRender",
+ APP_RENDER_AFTER: "appRenderAfter",
+
+ APP_RELOAD_BEFORE: "appReloadBefore",
+ APP_RELOAD: "appReload",
+ APP_RELOAD_AFTER: "appReloadAfter",
+
+ APP_DESTROY_BEFORE: "appDestroyBefore",
+ APP_DESTROY: "appDestroy",
+ APP_DESTROY_AFTER: "appDestroyAfter"
}
};
})());
diff --git a/sdk/f2.min.js b/sdk/f2.min.js
index 88c73abf..64e5d37e 100644
--- a/sdk/f2.min.js
+++ b/sdk/f2.min.js
@@ -121,7 +121,7 @@ F2.extend("Events",function(){var e=new EventEmitter2({wildcard:!0});return e.se
F2.extend("Rpc",function(){var e={},t="",n={},r=new RegExp("^"+F2.Constants.Sockets.EVENT),i=new RegExp("^"+F2.Constants.Sockets.RPC),s=new RegExp("^"+F2.Constants.Sockets.RPC_CALLBACK),o=new RegExp("^"+F2.Constants.Sockets.LOAD),u=new RegExp("^"+F2.Constants.Sockets.UI_RPC),a=function(){var e,t=!1,r=[],i=new easyXDM.Socket({onMessage:function(s,u){if(!t&&o.test(s)){s=s.replace(o,"");var a=F2.parse(s);a.length==2&&(e=a[0],n[e.instanceId]={config:e,socket:i},F2.registerApps([e],[a[1]]),jQuery.each(r,function(t,n){c(e,s,u)}),t=!0)}else t?c(e,s,u):r.push(s)}})},f=function(e,n){var r=jQuery(e.root);r=r.is("."+F2.Constants.Css.APP_CONTAINER)?r:r.find("."+F2.Constants.Css.APP_CONTAINER);if(!r.length){F2.log("Unable to locate app in order to establish secure connection.");return}var i={scrolling:"no",style:{width:"100%"}};e.height&&(i.style.height=e.height+"px");var s=new easyXDM.Socket({remote:t,container:r.get(0),props:i,onMessage:function(t,n){c(e,t,n)},onReady:function(){s.postMessage(F2.Constants.Sockets.LOAD+F2.stringify([e,n],F2.appConfigReplacer))}});return s},l=function(e,t){return function(){F2.Rpc.call(e,F2.Constants.Sockets.RPC_CALLBACK,t,[].slice.call(arguments).slice(2))}},c=function(t,n,o){function f(e,t){var n=String(t).split(".");for(var r=0;r','',"
Alert!
","",'
',"
",e,"
","
",'","
"].join("")},n=function(e){return['
','',"
Confirm
","",'
',"
",e,"
","
",'","
"].join("")};return{alert:function(n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.alert()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.alert",[].slice.call(arguments)):jQuery(e(n)).on("show",function(){var e=this;jQuery(e).find(".btn-primary").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()})}).modal({backdrop:!0})},confirm:function(e,r,i){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.Modals.confirm()");return}F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Modals.confirm",[].slice.call(arguments)):jQuery(n(e)).on("show",function(){var e=this;jQuery(e).find(".btn-ok").on("click",function(){jQuery(e).modal("hide").remove(),(r||jQuery.noop)()}),jQuery(e).find(".btn-cancel").on("click",function(){jQuery(e).modal("hide").remove(),(i||jQuery.noop)()})}).modal({backdrop:!0})}}}(),setTitle:function(e){F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"setTitle",[e]):jQuery(t.root).find("."+F2.Constants.Css.APP_TITLE).text(e)},showMask:function(e,n){F2.UI.showMask(t.instanceId,e,n)},updateHeight:r,Views:function(){var e=new EventEmitter2,i=/change/i;e.setMaxListeners(0);var s=function(e){return i.test(e)?!0:(F2.log('"'+e+'" is not a valid F2.UI.Views event name'),!1)};return{change:function(i){typeof i=="function"?this.on("change",i):typeof i=="string"&&(t.isSecure&&!F2.Rpc.isRemote(t.instanceId)?F2.Rpc.call(t.instanceId,F2.Constants.Sockets.UI_RPC,"Views.change",[].slice.call(arguments)):F2.inArray(i,t.views)&&(jQuery("."+F2.Constants.Css.APP_VIEW,n).addClass("hide").filter('[data-f2-view="'+i+'"]',n).removeClass("hide"),r(),e.emit("change",i)))},off:function(t,n){s(t)&&e.off(t,n)},on:function(t,n){s(t)&&e.on(t,n)}}}()}};return t.hideMask=function(e,t){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.hideMask()");return}if(F2.Rpc.isRemote(e)&&!jQuery(t).is("."+F2.Constants.Css.APP))F2.Rpc.call(e,F2.Constants.Sockets.RPC,"F2.UI.hideMask",[e,jQuery(t).selector]);else{var n=jQuery(t),r=n.find("> ."+F2.Constants.Css.MASK).remove();n.removeClass(F2.Constants.Css.MASK_CONTAINER),n.data(F2.Constants.Css.MASK_CONTAINER)&&n.css({position:"static"})}},t.init=function(t){e=t,e.UI=jQuery.extend(!0,{},F2.ContainerConfig.UI,e.UI||{})},t.showMask=function(t,n,r){if(!F2.isInit()){F2.log("F2.init() must be called before F2.UI.showMask()");return}if(F2.Rpc.isRemote(t)&&jQuery(n).is("."+F2.Constants.Css.APP))F2.Rpc.call(t,F2.Constants.Sockets.RPC,"F2.UI.showMask",[t,jQuery(n).selector,r]);else{r&&!e.UI.Mask.loadingIcon&&F2.log("Unable to display loading icon. Please set F2.ContainerConfig.UI.Mask.loadingIcon when calling F2.init();");var i=jQuery(n).addClass(F2.Constants.Css.MASK_CONTAINER),s=jQuery("
").height("100%").width("100%").addClass(F2.Constants.Css.MASK);e.UI.Mask.useClasses||s.css({"background-color":e.UI.Mask.backgroundColor,"background-image":e.UI.Mask.loadingIcon?"url("+e.UI.Mask.loadingIcon+")":"","background-position":"50% 50%","background-repeat":"no-repeat",display:"block",left:0,"min-height":30,padding:0,position:"absolute",top:0,"z-index":e.UI.Mask.zIndex,filter:"alpha(opacity="+e.UI.Mask.opacity*100+")",opacity:e.UI.Mask.opacity}),i.css("position")==="static"&&(i.css({position:"relative"}),i.data(F2.Constants.Css.MASK_CONTAINER,!0)),i.append(s)}},t}());
F2.extend("",function(){var _apps={},_config=!1,_afterAppRender=function(e,t){var n=_config.afterAppRender||function(e,t){return jQuery(t).appendTo("body")},r=n(e,t);if(!!_config.afterAppRender&&!r){F2.log("F2.ContainerConfig.afterAppRender() must return the DOM Element that contains the app");return}return jQuery(r).addClass(F2.Constants.Css.APP),r.get(0)},_appRender=function(e,t){function n(e){return jQuery("").append(e).html()}return t=n(jQuery(t).addClass(F2.Constants.Css.APP_CONTAINER+" "+e.appId)),_config.appRender&&(t=_config.appRender(e,t)),n(t)},_beforeAppRender=function(e){var t=_config.beforeAppRender||jQuery.noop;return t(e)},_hydrateAppConfig=function(e){e.instanceId=e.instanceId||F2.guid(),e.views=e.views||[],F2.inArray(F2.Constants.Views.HOME,e.views)||e.views.push(F2.Constants.Views.HOME)},_initAppEvents=function(e){jQuery(e.root).on("click","."+F2.Constants.Css.APP_VIEW_TRIGGER+"["+F2.Constants.Views.DATA_ATTRIBUTE+"]",function(t){t.preventDefault();var n=jQuery(this).attr(F2.Constants.Views.DATA_ATTRIBUTE).toLowerCase();n==F2.Constants.Views.REMOVE?F2.removeApp(e.instanceId):e.ui.Views.change(n)})},_initContainerEvents=function(){var e,t=function(){F2.Events.emit(F2.Constants.Events.CONTAINER_WIDTH_CHANGE)};jQuery(window).on("resize",function(){clearTimeout(e),e=setTimeout(t,100)})},_isInit=function(){return!!_config},_loadApps=function(appConfigs,appManifest){appConfigs=[].concat(appConfigs);if(appConfigs.length==1&&appConfigs[0].isSecure&&!_config.isSecureAppPage){_loadSecureApp(appConfigs[0],appManifest);return}if(appConfigs.length!=appManifest.apps.length){F2.log("The number of apps defined in the AppManifest do not match the number requested.",appManifest);return}var scripts=appManifest.scripts||[],styles=appManifest.styles||[],inlines=appManifest.inlineScripts||[],scriptCount=scripts.length,scriptsLoaded=0,appInit=function(){jQuery.each(appConfigs,function(e,t){t.ui=new F2.UI(t),F2.Apps[t.appId]!==undefined&&(typeof F2.Apps[t.appId]=="function"?setTimeout(function(){_apps[t.instanceId].app=new F2.Apps[t.appId](t,appManifest.apps[e],t.root),_apps[t.instanceId].app.init!==undefined&&_apps[t.instanceId].app.init()},0):F2.log("app initialization class is defined but not a function. ("+t.appId+")"))})},stylesFragment=[];jQuery.each(styles,function(e,t){stylesFragment.push('')}),jQuery("head").append(stylesFragment.join("")),jQuery.each(appManifest.apps,function(e,t){appConfigs[e].root=_afterAppRender(appConfigs[e],_appRender(appConfigs[e],t.html)),_initAppEvents(appConfigs[e])}),jQuery.each(scripts,function(i,e){jQuery.ajax({url:e,cache:!0,async:!1,dataType:"script",type:"GET",success:function(){++scriptsLoaded==scriptCount&&(jQuery.each(inlines,function(i,e){try{eval(e)}catch(exception){F2.log("Error loading inline script: "+exception+"\n\n"+e)}}),appInit())},error:function(t,n,r){F2.log(["Failed to load script ("+e+")",r.toString()])}})}),scriptCount||appInit()},_loadSecureApp=function(e,t){_config.secureAppPagePath?(e.root=_afterAppRender(e,_appRender(e,"")),e.ui=new F2.UI(e),_initAppEvents(e),F2.Rpc.register(e,t)):F2.log('Unable to load secure app: "secureAppPagePath" is not defined in F2.ContainerConfig.')},_validateApp=function(e){return e.appId?e.manifestUrl?!0:(F2.log('manifestUrl" missing from app object'),!1):(F2.log('"appId" missing from app object'),!1)};return{getContainerState:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.getContainerState()");return}return jQuery.map(_apps,function(e,t){return{appId:e.config.appId}})},init:function(e){_config=e||{},(!!_config.secureAppPagePath||_config.isSecureAppPage)&&F2.Rpc.init(_config.secureAppPagePath?_config.secureAppPagePath:!1),F2.UI.init(_config),_config.isSecureAppPage||_initContainerEvents()},isInit:_isInit,registerApps:function(e,t){if(!_isInit()){F2.log("F2.init() must be called before F2.registerApps()");return}if(!e){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}var n=[],r={},i={},s=!1;e=[].concat(e),t=t||[],s=!!t.length;if(!e.length){F2.log("At least one AppConfig must be passed when calling F2.registerApps()");return}if(e.length&&s&&e.length!=t.length){F2.log('The length of "apps" does not equal the length of "appManifests"');return}jQuery.each(e,function(e,i){if(!_validateApp(i))return;_hydrateAppConfig(i),i.root=_beforeAppRender(i),_apps[i.instanceId]={config:i},s?_loadApps(i,t[e]):i.enableBatchRequests&&!i.isSecure?(r[i.manifestUrl.toLowerCase()]=r[i.manifestUrl.toLowerCase()]||[],r[i.manifestUrl.toLowerCase()].push(i)):n.push({apps:[i],url:i.manifestUrl})}),s||(jQuery.each(r,function(e,t){n.push({url:e,apps:t})}),jQuery.each(n,function(e,t){var n=F2.Constants.JSONP_CALLBACK+t.apps[0].appId;i[n]=i[n]||[],i[n].push(t)}),jQuery.each(i,function(e,t){var n=function(r,i){if(!i)return;jQuery.ajax({url:i.url,data:{params:F2.stringify(i.apps,F2.appConfigReplacer)},jsonp:!1,jsonpCallback:r,dataType:"jsonp",success:function(e){_loadApps(i.apps,e)},error:function(e,t,n){F2.log("Failed to load app(s)",n.toString(),i.apps),jQuery.each(i.apps,function(e,t){F2.log("Removed failed "+t.name+" app",t),F2.removeApp(t.instanceId)})},complete:function(){n(e,t.pop())}})};n(e,t.pop())}))},removeAllApps:function(){if(!_isInit()){F2.log("F2.init() must be called before F2.removeAllApps()");return}jQuery.each(_apps,function(e,t){F2.removeApp(t.config.instanceId)})},removeApp:function(e){if(!_isInit()){F2.log("F2.init() must be called before F2.removeApp()");return}_apps[e]&&(jQuery(_apps[e].config.root).fadeOut(function(){jQuery(this).remove()}),delete _apps[e])}}}());
-F2.extend("AppHandlers",function(){function r(e){return typeof Node=="object"?e instanceof Node:e&&typeof e=="object"&&typeof e.nodeType=="number"&&typeof e.nodeName=="string"}function i(e){return typeof HTMLElement=="object"?e instanceof HTMLElement:e&&typeof e=="object"&&e.nodeType===1&&typeof e.nodeName=="string"}var e=F2.guid(),t=F2.guid(),n={beforeApp:{render:[],reload:[],destroy:[]},afterApp:{render:[],reload:[],destroy:[]},app:{render:[],reload:[],destroy:[]}},s=function(e,t){if(!e||!e.length)throw"Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again.";o(e[0]),e.shift();var n=e.length,s={func:null,namespace:null,app_id:null,domNode:null};switch(n){case 1:if(e[0]&&t&&(r(e[0])||i(e[0])))s.domNode=e[0];else{if(!e[0]||typeof e[0]!="function")throw"Invalid or null argument passed. Argument must be of type function or a native dom node";s.func=e[0]}break;case 2:if(e[0]&&e[1]&&typeof e[0]=="string"&&t&&(r(e[1])||i(e[1])))s.app_id=e[0],s.domNode=e[1];else if(e[0]&&e[1]&&typeof e[0]=="string"&&typeof e[1]=="function")s.app_id=e[0],s.func=e[1];else{if(!e[0]||!e[1]||typeof e[0]!="function"||typeof e[1]!="string")throw"Invalid or null argument(s) passed. Argument[0] must be of type function or string (to represent app_id). Argument[1] must be native domnode, function, or string (to represent namespace)";s.func=e[0],s.namespace=e[1]}break;case 3:if(e[0]&&e[1]&&typeof e[0]=="string"&&t&&(r(e[1])||i(e[1]))&&typeof e[2]=="string")s.app_id=e[0],s.domNode=e[1],s.namespace=e[2];else{if(!e[0]||!e[1]||typeof e[0]!="string"||typeof e[1]!="function"||typeof e[2]!="string")throw"Invalid or null argument(s) passed. Argument[0] must be of type string that represents the app_id. Argument[1] must be native domnode or function. Argument[2] must be of type string to represent a namespace.";s.app_id=e[0],s.func=e[1],s.namespace=e[2]}break;default:throw"Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again."}return s},o=function(n){if(e!=n&&t!=n)throw"Invalid token passed. Please verify that you have correctly received and stored token from F2.AppHandlers.getToken()."},u=function(e,t,n){o(n);if(!t&&!e)return;if(!t&&e)e=[];else if(t&&e){t=t.toLowerCase();var r=[];for(var i=0,s=e.length;i-1){var r=t.split(".");t=r[0],n=r[1]}if(!i||!i[t])throw"Invalid EventKey passed. Check your inputs and try again.";for(var s=0,o=i[t];s-1){var s=t.split(".");t=s[0],r=s[1]}if(!i||!i[t])throw"Invalid EventKey passed. Check your inputs and try again.";return i[t].apply(F2,[e,r,n]),this},off:function(e,t){var n=null;if(t.indexOf(".")>-1){var i=t.split(".");t=i[0],n=i[1]}if(!r||!r[t])throw"Invalid EventKey passed. Check your inputs and try again.";return r[t].apply(F2,[e,n]),this},CONSTANTS:{APP_RENDER_BEFORE:"appRenderBefore",APP_RENDER:"appRender",APP_RENDER_AFTER:"appRenderAfter",APP_RELOAD_BEFORE:"appReloadBefore",APP_RELOAD:"appReload",APP_RELOAD_AFTER:"appReloadAfter",APP_DESTROY_BEFORE:"appDestroyBefore",APP_DESTROY:"appDestroy",APP_DESTROY_AFTER:"appDestroyAfter"}}}());
exports.F2 = F2;
diff --git a/sdk/f2.no-third-party.js b/sdk/f2.no-third-party.js
index 44943254..e359dfbf 100644
--- a/sdk/f2.no-third-party.js
+++ b/sdk/f2.no-third-party.js
@@ -2181,6 +2181,34 @@ F2.extend('AppHandlers', (function() {
}
};
+ var _offMethods = {
+ appRenderBefore: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.beforeApp.render, sNamespaceOrApp_ID, sToken); },
+ appRender: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.app.render, sNamespaceOrApp_ID, sToken); },
+ appRenderAfter: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.afterApp.render, sNamespaceOrApp_ID, sToken); },
+
+ appReloadBefore: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.beforeApp.reload, sNamespaceOrApp_ID, sToken); },
+ appReload: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.app.reload, sNamespaceOrApp_ID, sToken); },
+ appReloadAfter: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.afterApp.reload, sNamespaceOrApp_ID, sToken); },
+
+ appDestroyBefore: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.beforeApp.destroy, sNamespaceOrApp_ID, sToken); },
+ appDestroy: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.app.destroy, sNamespaceOrApp_ID, sToken); },
+ appDestroyAfter: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.afterApp.destroy, sNamespaceOrApp_ID, sToken); }
+ };
+
+ var _onMethods = {
+ appRenderBefore: function(token, sNamespace, func_or_element) { _handlerCollection.beforeApp.render.push(_createHandler(token, sNamespace, func_or_element)); },
+ appRender: function(token, sNamespace, func_or_element) { _handlerCollection.app.render.push(_createHandler(token, sNamespace, func_or_element, true)); },
+ appRenderAfter: function(token, sNamespace, func_or_element) { _handlerCollection.afterApp.render.push(_createHandler(token, sNamespace, func_or_element)); },
+
+ appReloadBefore: function(token, sNamespace, func_or_element) { _handlerCollection.beforeApp.reload.push(_createHandler(token, sNamespace, func_or_element)); },
+ appReload: function(token, sNamespace, func_or_element) { _handlerCollection.app.reload.push(_createHandler(token, sNamespace, func_or_element)); },
+ appReloadAfter: function(token, sNamespace, func_or_element) { _handlerCollection.afterApp.reload.push(_createHandler(token, sNamespace, func_or_element)); },
+
+ appDestroyBefore: function(token, sNamespace, func_or_element) { _handlerCollection.beforeApp.destroy.push(_createHandler(token, sNamespace, func_or_element)); },
+ appDestroy: function(token, sNamespace, func_or_element) { _handlerCollection.app.destroy.push(_createHandler(token, sNamespace, func_or_element)); },
+ appDestroyAfter: function(token, sNamespace, func_or_element) { _handlerCollection.afterApp.destroy.push(_createHandler(token, sNamespace, func_or_element)); }
+ };
+
//Returns true if it is a DOM node
function _isNode(o){
return (
@@ -2197,7 +2225,7 @@ F2.extend('AppHandlers', (function() {
);
}
- var _createHandler = function(arOriginalArgs, bDomNodeAppropriate)
+ var _createHandler = function(token, sNamespace, func_or_element, bDomNodeAppropriate)
{
if(!arOriginalArgs || !arOriginalArgs.length) { throw ("Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again."); }
@@ -2211,109 +2239,19 @@ F2.extend('AppHandlers', (function() {
// create handler structure. Not all arguments properties will be populated/used.
var handler = {
- func: null,
- namespace: null,
- app_id: null,
- domNode: null
+ func: (typeof(func_or_element)) ? func_or_element : null,
+ namespace: sNamespace,
+ domNode: (_isNode(func_or_element) || _isElement(func_or_element)) ? func_or_element : null
};
- // based on the argument count try to create a handler.
- switch(iArgCount)
+ if(!handler.func && !handler.domNode)
{
- case 1:
- // method signature(oDomNode)
- if(arOriginalArgs[0] && bDomNodeAppropriate && (_isNode(arOriginalArgs[0]) || _isElement(arOriginalArgs[0])))
- {
- handler.domNode = arOriginalArgs[0];
- }
- // method signature (function(){})
- else if(arOriginalArgs[0] && typeof(arOriginalArgs[0]) == "function")
- {
- handler.func = arOriginalArgs[0];
- }
- // error
- else
- {
- throw ("Invalid or null argument passed. Argument must be of type function or a native dom node");
- }
- break;
- case 2:
- // method signature ("APP_ID" ,oDomNode)
- if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- bDomNodeAppropriate &&
- (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1]))
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.domNode = arOriginalArgs[1];
- }
- // method signature ("APP_ID" ,function(){})
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- typeof(arOriginalArgs[1]) == "function"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.func = arOriginalArgs[1];
- }
- // method signature (function(){} ,"NAMESPACE")
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "function" &&
- typeof(arOriginalArgs[1]) == "string"
- )
- {
- handler.func = arOriginalArgs[0];
- handler.namespace = arOriginalArgs[1];
- }
- // error
- else
- {
- throw ("Invalid or null argument(s) passed. Argument[0] must be of type function or string (to represent app_id). Argument[1] must be native domnode, function, or string (to represent namespace)");
- }
- break;
- case 3:
- // method signature ("APP_ID", oDomNode, "NAMESPACE")
- if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- bDomNodeAppropriate &&
- (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1])) &&
- typeof(arOriginalArgs[2]) == "string"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.domNode = arOriginalArgs[1];
- handler.namespace = arOriginalArgs[2];
- }
- // method signature ("APP_ID", function(){}, "NAMESPACE")
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- typeof(arOriginalArgs[1]) == "function" &&
- typeof(arOriginalArgs[2]) == "string"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.func = arOriginalArgs[1];
- handler.namespace = arOriginalArgs[2];
- }
- else
- {
- throw ("Invalid or null argument(s) passed. Argument[0] must be of type string that represents the app_id. Argument[1] must be native domnode or function. Argument[2] must be of type string to represent a namespace.");
- }
- break;
- // throw exception if there are 0 or 4+ arguments
- default:
- throw ("Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again.");
+ throw ("Invalid or null argument passed. Handler will not be added to collection. A valid dom element or callback function is required.");
+ }
+
+ if(handler.domNode && !bDomNodeAppropriate)
+ {
+ throw ("Invalid argument passed. Handler will not be added to collection. A callback function is required for this event type.");
}
return handler;
@@ -2360,13 +2298,33 @@ F2.extend('AppHandlers', (function() {
}
};
+ var _triggerEvent = function(arHandleCollection, arOriginalArgs)
+ {
+ // no errors here, basically there are no handlers to call
+ if(!arHandleCollection || !arHandleCollection.length) { return; }
+
+ // there is always 1 argument required, the first arg should always be the token.
+ if(!arOriginalArgs || !arOriginalArgs.length) { throw ("Invalid or null argument(s) passed. Token is required for all triggers. Please check your inputs and try again."); }
+
+ // will throw an exception and stop execution if the token is invalid
+ _validateToken(arOriginalArgs[0]);
+
+ // remove the token from the arguments since we have validated it and no longer need it
+ arOriginalArgs.shift();
+
+ for(var i = 0, j = arHandleCollection.length; i < j; i++)
+ {
+ arHandleCollection[i].apply(F2, arguments);
+ }
+ };
+
return {
/**
- * Allows container developer to retrieve a special token which must be passed to
- * all On and Off methods. This function will self destruct so be sure to keep the response
- * inside of a closure somewhere.
- * @method getToken
- */
+ * Allows container developer to retrieve a special token which must be passed to
+ * all On and Off methods. This function will self destruct so be sure to keep the response
+ * inside of a closure somewhere.
+ * @method getToken
+ **/
getToken: function()
{
// delete this method for security that way only the container has access to the token 1 time.
@@ -2376,10 +2334,10 @@ F2.extend('AppHandlers', (function() {
return _ct;
},
/**
- * Allows F2 to get a token internally
- * @method __f2GetToken
- * @private
- */
+ * Allows F2 to get a token internally
+ * @method __f2GetToken
+ * @private
+ **/
__f2GetToken: function()
{
// delete this method for security that way only the F2 internally has access to the token 1 time.
@@ -2389,51 +2347,131 @@ F2.extend('AppHandlers', (function() {
return _f2t;
},
/**
- * Allows F2 to trigger events internally
- * @method __f2Trigger
- * @private
- */
- __f2Trigger: {
- },
- on: {
- beforeApp:
- {
- render: function() { _handlerCollection.beforeApp.render.push(_createHandler(arguments)); },
- reload: function(){ _handlerCollection.beforeApp.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.beforeApp.destroy.push(_createHandler(arguments)); }
- },
- afterApp:
- {
- render: function() { _handlerCollection.afterApp.render.push(_createHandler(arguments)); },
- reload: function(){ _handlerCollection.afterApp.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.afterApp.destroy.push(_createHandler(arguments)); }
- },
- app:
+ * Allows F2 to trigger specific app events internally.
+ * @method __f2Trigger
+ * @private
+ **/
+ __f2Trigger: function(token, eventKey) // additional arguments will likely be passed
+ {
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_onMethods && _onMethods[eventKey])
+ {
+ for(var i = 0, j = _onMethods[eventKey]; i < j; i++)
{
- render: function() { _handlerCollection.app.render.push(_createHandler(arguments), true); },
- reload: function(){ _handlerCollection.app.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.app.destroy.push(_createHandler(arguments)); }
+ var handler = _onMethods[eventKey][i];
+
+ _onMethods[eventKey][i].apply(F2, [token, sNamespace, func_or_element])
}
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
},
- off: {
- beforeApp:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.beforeApp.destroy, sNamespaceOrApp_ID, sToken); }
- },
- afterApp:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.afterApp.destroy, sNamespaceOrApp_ID, sToken); }
- },
- app:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.app.destroy, sNamespaceOrApp_ID, sToken); }
- }
+ /**
+ * Allows you to easily tell all apps to render in a specific location. Only valid for eventType 'appRender'.
+ * @method on
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey The event key to remove handler from {{#crossLink "F2.AppHandlers/CONSTANTS:property"}}{{/crossLink}}.
+ * @params {HTMLElement|Node} element Specific element to append your app to.
+ * @example
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', $("#my-container").get(0));
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore.my_app_id', $("#my-container").get(0));
+ **/
+ /**
+ * Allows you to add listener method that will be triggered when a specific event happens.
+ * @method on
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey The event key to remove handler from {{#crossLink "F2.AppHandlers/CONSTANTS:property"}}{{/crossLink}}.
+ * @params {Function} listener A function that will be triggered when a specific event happens.
+ * @example
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', function() { F2.log("before app rendered!"); });
+ **/
+ on: function(token, eventKey, func_or_element)
+ {
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_onMethods && _onMethods[eventKey])
+ {
+ _onMethods[eventKey].apply(F2, [token, sNamespace, func_or_element])
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
+ },
+ /**
+ * Allows you to remove listener methods for specific events
+ * @method off
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey{.namespace} The event key to determine what listeners need to be removed. If no namespace is provided all listeners for the specified event type will be removed.
+ * @example
+ * F2.AppHandlers.off('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore');
+ **/
+ off: function(token, eventKey)
+ {
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_offMethods && _offMethods[eventKey])
+ {
+ _offMethods[eventKey].apply(F2, [token, sNamespace])
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
+ },
+ /**
+ * A collection of constants for the on/off method names. Basically just here to help you.
+ * @property {Object} CONSTANTS
+ **/
+ CONSTANTS:
+ {
+ APP_RENDER_BEFORE: "appRenderBefore",
+ APP_RENDER: "appRender",
+ APP_RENDER_AFTER: "appRenderAfter",
+
+ APP_RELOAD_BEFORE: "appReloadBefore",
+ APP_RELOAD: "appReload",
+ APP_RELOAD_AFTER: "appReloadAfter",
+
+ APP_DESTROY_BEFORE: "appDestroyBefore",
+ APP_DESTROY: "appDestroy",
+ APP_DESTROY_AFTER: "appDestroyAfter"
}
};
})());
diff --git a/sdk/src/app_handlers.js b/sdk/src/app_handlers.js
index fd73a398..2c6498f5 100644
--- a/sdk/src/app_handlers.js
+++ b/sdk/src/app_handlers.js
@@ -29,6 +29,34 @@ F2.extend('AppHandlers', (function() {
}
};
+ var _offMethods = {
+ appRenderBefore: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.beforeApp.render, sNamespaceOrApp_ID, sToken); },
+ appRender: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.app.render, sNamespaceOrApp_ID, sToken); },
+ appRenderAfter: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.afterApp.render, sNamespaceOrApp_ID, sToken); },
+
+ appReloadBefore: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.beforeApp.reload, sNamespaceOrApp_ID, sToken); },
+ appReload: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.app.reload, sNamespaceOrApp_ID, sToken); },
+ appReloadAfter: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.afterApp.reload, sNamespaceOrApp_ID, sToken); },
+
+ appDestroyBefore: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.beforeApp.destroy, sNamespaceOrApp_ID, sToken); },
+ appDestroy: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.app.destroy, sNamespaceOrApp_ID, sToken); },
+ appDestroyAfter: function(sToken, sNamespaceOrApp_ID) { _removeHandler(_handlerCollection.afterApp.destroy, sNamespaceOrApp_ID, sToken); }
+ };
+
+ var _onMethods = {
+ appRenderBefore: function(token, sNamespace, func_or_element) { _handlerCollection.beforeApp.render.push(_createHandler(token, sNamespace, func_or_element)); },
+ appRender: function(token, sNamespace, func_or_element) { _handlerCollection.app.render.push(_createHandler(token, sNamespace, func_or_element, true)); },
+ appRenderAfter: function(token, sNamespace, func_or_element) { _handlerCollection.afterApp.render.push(_createHandler(token, sNamespace, func_or_element)); },
+
+ appReloadBefore: function(token, sNamespace, func_or_element) { _handlerCollection.beforeApp.reload.push(_createHandler(token, sNamespace, func_or_element)); },
+ appReload: function(token, sNamespace, func_or_element) { _handlerCollection.app.reload.push(_createHandler(token, sNamespace, func_or_element)); },
+ appReloadAfter: function(token, sNamespace, func_or_element) { _handlerCollection.afterApp.reload.push(_createHandler(token, sNamespace, func_or_element)); },
+
+ appDestroyBefore: function(token, sNamespace, func_or_element) { _handlerCollection.beforeApp.destroy.push(_createHandler(token, sNamespace, func_or_element)); },
+ appDestroy: function(token, sNamespace, func_or_element) { _handlerCollection.app.destroy.push(_createHandler(token, sNamespace, func_or_element)); },
+ appDestroyAfter: function(token, sNamespace, func_or_element) { _handlerCollection.afterApp.destroy.push(_createHandler(token, sNamespace, func_or_element)); }
+ };
+
//Returns true if it is a DOM node
function _isNode(o){
return (
@@ -45,7 +73,7 @@ F2.extend('AppHandlers', (function() {
);
}
- var _createHandler = function(arOriginalArgs, bDomNodeAppropriate)
+ var _createHandler = function(token, sNamespace, func_or_element, bDomNodeAppropriate)
{
if(!arOriginalArgs || !arOriginalArgs.length) { throw ("Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again."); }
@@ -59,109 +87,19 @@ F2.extend('AppHandlers', (function() {
// create handler structure. Not all arguments properties will be populated/used.
var handler = {
- func: null,
- namespace: null,
- app_id: null,
- domNode: null
+ func: (typeof(func_or_element)) ? func_or_element : null,
+ namespace: sNamespace,
+ domNode: (_isNode(func_or_element) || _isElement(func_or_element)) ? func_or_element : null
};
- // based on the argument count try to create a handler.
- switch(iArgCount)
+ if(!handler.func && !handler.domNode)
{
- case 1:
- // method signature(oDomNode)
- if(arOriginalArgs[0] && bDomNodeAppropriate && (_isNode(arOriginalArgs[0]) || _isElement(arOriginalArgs[0])))
- {
- handler.domNode = arOriginalArgs[0];
- }
- // method signature (function(){})
- else if(arOriginalArgs[0] && typeof(arOriginalArgs[0]) == "function")
- {
- handler.func = arOriginalArgs[0];
- }
- // error
- else
- {
- throw ("Invalid or null argument passed. Argument must be of type function or a native dom node");
- }
- break;
- case 2:
- // method signature ("APP_ID" ,oDomNode)
- if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- bDomNodeAppropriate &&
- (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1]))
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.domNode = arOriginalArgs[1];
- }
- // method signature ("APP_ID" ,function(){})
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- typeof(arOriginalArgs[1]) == "function"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.func = arOriginalArgs[1];
- }
- // method signature (function(){} ,"NAMESPACE")
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "function" &&
- typeof(arOriginalArgs[1]) == "string"
- )
- {
- handler.func = arOriginalArgs[0];
- handler.namespace = arOriginalArgs[1];
- }
- // error
- else
- {
- throw ("Invalid or null argument(s) passed. Argument[0] must be of type function or string (to represent app_id). Argument[1] must be native domnode, function, or string (to represent namespace)");
- }
- break;
- case 3:
- // method signature ("APP_ID", oDomNode, "NAMESPACE")
- if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- bDomNodeAppropriate &&
- (_isNode(arOriginalArgs[1]) || _isElement(arOriginalArgs[1])) &&
- typeof(arOriginalArgs[2]) == "string"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.domNode = arOriginalArgs[1];
- handler.namespace = arOriginalArgs[2];
- }
- // method signature ("APP_ID", function(){}, "NAMESPACE")
- else if(
- arOriginalArgs[0] &&
- arOriginalArgs[1] &&
- typeof(arOriginalArgs[0]) == "string" &&
- typeof(arOriginalArgs[1]) == "function" &&
- typeof(arOriginalArgs[2]) == "string"
- )
- {
- handler.app_id = arOriginalArgs[0];
- handler.func = arOriginalArgs[1];
- handler.namespace = arOriginalArgs[2];
- }
- else
- {
- throw ("Invalid or null argument(s) passed. Argument[0] must be of type string that represents the app_id. Argument[1] must be native domnode or function. Argument[2] must be of type string to represent a namespace.");
- }
- break;
- // throw exception if there are 0 or 4+ arguments
- default:
- throw ("Invalid or null argument(s) passed. Handler will not be added to collection. Please check your inputs and try again.");
+ throw ("Invalid or null argument passed. Handler will not be added to collection. A valid dom element or callback function is required.");
+ }
+
+ if(handler.domNode && !bDomNodeAppropriate)
+ {
+ throw ("Invalid argument passed. Handler will not be added to collection. A callback function is required for this event type.");
}
return handler;
@@ -208,13 +146,33 @@ F2.extend('AppHandlers', (function() {
}
};
+ var _triggerEvent = function(arHandleCollection, arOriginalArgs)
+ {
+ // no errors here, basically there are no handlers to call
+ if(!arHandleCollection || !arHandleCollection.length) { return; }
+
+ // there is always 1 argument required, the first arg should always be the token.
+ if(!arOriginalArgs || !arOriginalArgs.length) { throw ("Invalid or null argument(s) passed. Token is required for all triggers. Please check your inputs and try again."); }
+
+ // will throw an exception and stop execution if the token is invalid
+ _validateToken(arOriginalArgs[0]);
+
+ // remove the token from the arguments since we have validated it and no longer need it
+ arOriginalArgs.shift();
+
+ for(var i = 0, j = arHandleCollection.length; i < j; i++)
+ {
+ arHandleCollection[i].apply(F2, arguments);
+ }
+ };
+
return {
/**
- * Allows container developer to retrieve a special token which must be passed to
- * all On and Off methods. This function will self destruct so be sure to keep the response
- * inside of a closure somewhere.
- * @method getToken
- */
+ * Allows container developer to retrieve a special token which must be passed to
+ * all On and Off methods. This function will self destruct so be sure to keep the response
+ * inside of a closure somewhere.
+ * @method getToken
+ **/
getToken: function()
{
// delete this method for security that way only the container has access to the token 1 time.
@@ -224,10 +182,10 @@ F2.extend('AppHandlers', (function() {
return _ct;
},
/**
- * Allows F2 to get a token internally
- * @method __f2GetToken
- * @private
- */
+ * Allows F2 to get a token internally
+ * @method __f2GetToken
+ * @private
+ **/
__f2GetToken: function()
{
// delete this method for security that way only the F2 internally has access to the token 1 time.
@@ -237,51 +195,131 @@ F2.extend('AppHandlers', (function() {
return _f2t;
},
/**
- * Allows F2 to trigger events internally
- * @method __f2Trigger
- * @private
- */
- __f2Trigger: {
- },
- on: {
- beforeApp:
- {
- render: function() { _handlerCollection.beforeApp.render.push(_createHandler(arguments)); },
- reload: function(){ _handlerCollection.beforeApp.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.beforeApp.destroy.push(_createHandler(arguments)); }
- },
- afterApp:
- {
- render: function() { _handlerCollection.afterApp.render.push(_createHandler(arguments)); },
- reload: function(){ _handlerCollection.afterApp.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.afterApp.destroy.push(_createHandler(arguments)); }
- },
- app:
+ * Allows F2 to trigger specific app events internally.
+ * @method __f2Trigger
+ * @private
+ **/
+ __f2Trigger: function(token, eventKey) // additional arguments will likely be passed
+ {
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_onMethods && _onMethods[eventKey])
+ {
+ for(var i = 0, j = _onMethods[eventKey]; i < j; i++)
{
- render: function() { _handlerCollection.app.render.push(_createHandler(arguments), true); },
- reload: function(){ _handlerCollection.app.reload.push(_createHandler(arguments)); },
- destroy: function(){ _handlerCollection.app.destroy.push(_createHandler(arguments)); }
+ var handler = _onMethods[eventKey][i];
+
+ _onMethods[eventKey][i].apply(F2, [token, sNamespace, func_or_element])
}
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
},
- off: {
- beforeApp:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.beforeApp.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.beforeApp.destroy, sNamespaceOrApp_ID, sToken); }
- },
- afterApp:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.afterApp.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.afterApp.destroy, sNamespaceOrApp_ID, sToken); }
- },
- app:
- {
- render: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.render, sNamespaceOrApp_ID, sToken); },
- reload: function(sNamespaceOrApp_ID, sToken) { _removeHandler(_handlerCollection.app.reload, sNamespaceOrApp_ID, sToken); },
- destroy: function(sNamespaceOrApp_ID, sToken){ _removeHandler(_handlerCollection.app.destroy, sNamespaceOrApp_ID, sToken); }
- }
+ /**
+ * Allows you to easily tell all apps to render in a specific location. Only valid for eventType 'appRender'.
+ * @method on
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey The event key to remove handler from {{#crossLink "F2.AppHandlers/CONSTANTS:property"}}{{/crossLink}}.
+ * @params {HTMLElement|Node} element Specific element to append your app to.
+ * @example
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', $("#my-container").get(0));
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore.my_app_id', $("#my-container").get(0));
+ **/
+ /**
+ * Allows you to add listener method that will be triggered when a specific event happens.
+ * @method on
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey The event key to remove handler from {{#crossLink "F2.AppHandlers/CONSTANTS:property"}}{{/crossLink}}.
+ * @params {Function} listener A function that will be triggered when a specific event happens.
+ * @example
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', function() { F2.log("before app rendered!"); });
+ **/
+ on: function(token, eventKey, func_or_element)
+ {
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_onMethods && _onMethods[eventKey])
+ {
+ _onMethods[eventKey].apply(F2, [token, sNamespace, func_or_element])
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
+ },
+ /**
+ * Allows you to remove listener methods for specific events
+ * @method off
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} eventKey{.namespace} The event key to determine what listeners need to be removed. If no namespace is provided all listeners for the specified event type will be removed.
+ * @example
+ * F2.AppHandlers.off('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore');
+ **/
+ off: function(token, eventKey)
+ {
+ var sNamespace = null;
+
+ // we need to check the key for a namespace
+ if(eventKey.indexOf(".") > -1)
+ {
+ var arData = eventKey.split(".");
+ eventKey = arData[0];
+ sNamespace = arData[1];
+ }
+
+ if(_offMethods && _offMethods[eventKey])
+ {
+ _offMethods[eventKey].apply(F2, [token, sNamespace])
+ }
+ else
+ {
+ throw ("Invalid EventKey passed. Check your inputs and try again.")
+ }
+
+ return this;
+ },
+ /**
+ * A collection of constants for the on/off method names. Basically just here to help you.
+ * @property {Object} CONSTANTS
+ **/
+ CONSTANTS:
+ {
+ APP_RENDER_BEFORE: "appRenderBefore",
+ APP_RENDER: "appRender",
+ APP_RENDER_AFTER: "appRenderAfter",
+
+ APP_RELOAD_BEFORE: "appReloadBefore",
+ APP_RELOAD: "appReload",
+ APP_RELOAD_AFTER: "appReloadAfter",
+
+ APP_DESTROY_BEFORE: "appDestroyBefore",
+ APP_DESTROY: "appDestroy",
+ APP_DESTROY_AFTER: "appDestroyAfter"
}
};
})());
\ No newline at end of file
From f20205e2e9a32cc8f6d48d32c73a3f6e2d30be63 Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Fri, 22 Mar 2013 23:13:06 -0600
Subject: [PATCH 022/181] More travis testing.
---
.travis.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.travis.yml b/.travis.yml
index bbcdcf8a..a6cbc524 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,4 +21,5 @@ script:
- "wget https://raw.github.com/mark-rushakoff/OpenPhantomScripts/master/phantom-jasmine.js"
# Make sure to change test/test.html to the path to your test page
- "phantomjs phantom-jasmine.js tests/index.html"
+ - "wget https://raw.github.com/mark-rushakoff/OpenPhantomScripts/master/phantom-jasmine.js"
- "phantomjs phantom-jasmine.js tests/index-amd.html"
From 264e73e5dc0e3946a683fa8b481e9feb1e288cc1 Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Fri, 22 Mar 2013 23:18:56 -0600
Subject: [PATCH 023/181] Think I fixed the issue for travis...
---
tests/index-amd.html | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/tests/index-amd.html b/tests/index-amd.html
index bc4a429d..d32fff9a 100644
--- a/tests/index-amd.html
+++ b/tests/index-amd.html
@@ -9,9 +9,12 @@
+
+
+
-
-
+
+
@@ -33,6 +36,10 @@
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
+
+ // what you need to add
+ var console_reporter = new jasmine.ConsoleReporter()
+ jasmine.getEnv().addReporter(console_reporter);
var currentWindowOnload = window.onload;
From 4953c515339eb942a0391aecac37be7696c7e53a Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Fri, 22 Mar 2013 23:20:54 -0600
Subject: [PATCH 024/181] Hoping we don't need that script twice.
---
.travis.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index a6cbc524..bbcdcf8a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,5 +21,4 @@ script:
- "wget https://raw.github.com/mark-rushakoff/OpenPhantomScripts/master/phantom-jasmine.js"
# Make sure to change test/test.html to the path to your test page
- "phantomjs phantom-jasmine.js tests/index.html"
- - "wget https://raw.github.com/mark-rushakoff/OpenPhantomScripts/master/phantom-jasmine.js"
- "phantomjs phantom-jasmine.js tests/index-amd.html"
From 92516d8ce196b167329cf0eb1183b85961e8e5e9 Mon Sep 17 00:00:00 2001
From: Ali Khatami
Date: Tue, 26 Mar 2013 17:14:07 -0600
Subject: [PATCH 025/181] Have a solid saving point. Still a lot of work to do
here.
---
build/build.js | 4 +-
docs/js/f2.js | 118 +--
docs/js/f2.min.js | 3 +-
docs/sdk/classes/F2.AppHandlers.html | 22 +-
docs/sdk/classes/F2.html | 191 +++-
docs/sdk/data.json | 88 +-
docs/sdk/files/sdk_src_F2.js.html | 19 +
docs/sdk/files/sdk_src_app_handlers.js.html | 192 ++--
docs/sdk/files/sdk_src_container.js.html | 134 ++-
docs/sdk/index.html | 2 +-
examples/container/js/container.js | 78 +-
f2.js | 19 +-
sdk/f2.debug.js | 963 ++++++++++++--------
sdk/f2.min.js | 6 +-
sdk/f2.no-third-party.js | 963 ++++++++++++--------
sdk/src/F2.js | 19 +
sdk/src/app_handlers.js | 192 ++--
sdk/src/container.js | 134 ++-
18 files changed, 1892 insertions(+), 1255 deletions(-)
diff --git a/build/build.js b/build/build.js
index ad65529c..0d7e4c30 100755
--- a/build/build.js
+++ b/build/build.js
@@ -46,13 +46,13 @@ var JS_FOOTER = { src: 'sdk/src/template/footer.js.tmpl', minify: false };
// only the files that represent f2
var CORE_FILES = [
{ src: 'sdk/src/F2.js', minify: true },
+ { src: 'sdk/src/app_handlers.js', minify: true },
{ src: 'sdk/src/classes.js', minify: true },
{ src: 'sdk/src/constants.js', minify: true },
{ src: 'sdk/src/events.js', minify: true },
{ src: 'sdk/src/rpc.js', minify: true },
{ src: 'sdk/src/ui.js', minify: true },
- { src: 'sdk/src/container.js', minify: true },
- { src: 'sdk/src/app_handlers.js', minify: true }
+ { src: 'sdk/src/container.js', minify: true }
];
var ENCODING = 'utf-8';
var EOL = '\n';
diff --git a/docs/js/f2.js b/docs/js/f2.js
index 64e5d37e..b7a5c6d2 100644
--- a/docs/js/f2.js
+++ b/docs/js/f2.js
@@ -17,120 +17,4 @@
*/
;typeof JSON!="object"&&(JSON={}),function(){"use strict";function f(e){return e<10?"0"+e:e}function quote(e){return escapable.lastIndex=0,escapable.test(e)?'"'+e.replace(escapable,function(e){var t=meta[e];return typeof t=="string"?t:"\\u"+("0000"+e.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+e+'"'}function str(e,t){var n,r,i,s,o=gap,u,a=t[e];a&&typeof a=="object"&&typeof a.toJSON=="function"&&(a=a.toJSON(e)),typeof rep=="function"&&(a=rep.call(t,e,a));switch(typeof a){case"string":return quote(a);case"number":return isFinite(a)?String(a):"null";case"boolean":case"null":return String(a);case"object":if(!a)return"null";gap+=indent,u=[];if(Object.prototype.toString.apply(a)==="[object Array]"){s=a.length;for(n=0;n=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
/**
- * Allows container developers more flexibility when it comes to handling app interaction.
+ * Allows container developers more flexibility when it comes to handling app interaction. Starting with version 1.3 this is the preferred method
+ * for choosing how app rendering/interaction happens. This replaces the config versions of beforeAppRender, appRender, and afterAppRender. It also
+ * adds hooks into an app being removed/destroyed. As F2 evolves more hooks will be added to aid in container development.
* @class F2.AppHandlers
*/
-
F2.extend('AppHandlers', (function() {
// the hidden token that we will check against every time someone tries to add, remove, fire handler
@@ -427,7 +432,9 @@
File: sdk\src\app_handlers.js
return _ct;
},
/**
- * Allows F2 to get a token internally
+ * Allows F2 to get a token internally. Token is required to call {{#crossLink "F2.AppHandlers/\_\_trigger:method"}}{{/crossLink}}.
+ * This function will self destruct to eliminate other sources from using the {{#crossLink "F2.AppHandlers/\_\_trigger:method"}}{{/crossLink}}
+ * and other internal methods.
* @method __f2GetToken
* @private
**/
@@ -443,6 +450,9 @@
File: sdk\src\app_handlers.js
* Allows F2 to trigger specific app events internally.
* @method __trigger
* @private
+ * @chainable
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/\_\_f2GetToken:method"}}{{/crossLink}}.
+ * @param {String} eventKey The event you want to fire. Complete list of event keys available in {{#crossLink "F2.Constants/AppHandlers:property"}}{{/crossLink}}.
**/
__trigger: function(token, eventKey) // additional arguments will likely be passed
{
@@ -500,24 +510,27 @@
File: sdk\src\app_handlers.js
* Allows you to easily tell all apps to render in a specific location. Only valid for eventType 'appRender'.
* @method on
* @chainable
- * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
- * @param {String} eventKey The event key to remove handler from {{#crossLink "F2.AppHandlers/CONSTANTS:property"}}{{/crossLink}}.
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}} or {{#crossLink "F2.AppHandlers/\_\_trigger:method"}}{{/crossLink}}.
+ * @param {String} eventKey{.namespace} The event key to determine what event you want to bind to. The namespace is useful for removal
+ * purposes. At this time it does not affect when an event is fired. Complete list of event keys available in
+ * {{#crossLink "F2.Constants/AppHandlers:property"}}{{/crossLink}}.
* @params {HTMLElement|Node} element Specific element to append your app to.
* @example
* F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', $("#my-container").get(0));
- * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore.my_app_id', $("#my-container").get(0));
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore.myNamespace', $("#my-container").get(0));
**/
/**
* Allows you to add listener method that will be triggered when a specific event happens.
* @method on
* @chainable
- * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
- * @param {String} eventKey{.namespace} The event key to determine what listeners need to be removed. If no namespace is provided all
- * listeners for the specified event type will be removed.
- * Complete list available in {{#crossLink "F2.Constants/AppHandlers:property"}}{{/crossLink}}.
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:method"}}{{/crossLink}} or {{#crossLink "F2.AppHandlers/\_\_trigger:method"}}{{/crossLink}}.
+ * @param {String} eventKey{.namespace} The event key to determine what event you want to bind to. The namespace is useful for removal
+ * purposes. At this time it does not affect when an event is fired. Complete list of event keys available in
+ * {{#crossLink "F2.Constants/AppHandlers:property"}}{{/crossLink}}.
* @params {Function} listener A function that will be triggered when a specific event happens.
* @example
- * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', function() { F2.log("before app rendered!"); });
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore', function() { F2.log("before app rendered!"); });
+ * F2.AppHandlers.on('3123-asd12-asd123dwase-123d-123d', 'appRenderBefore.myNamespace', function() { F2.log("before app rendered!"); });
**/
on: function(token, eventKey, func_or_element)
{
@@ -558,7 +571,7 @@
File: sdk\src\app_handlers.js
* Allows you to remove listener methods for specific events
* @method off
* @chainable
- * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:methods"}}{{/crossLink}}.
+ * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:method"}}{{/crossLink}}.
* @param {String} eventKey{.namespace} The event key to determine what listeners need to be removed. If no namespace is provided all
* listeners for the specified event type will be removed.
* Complete list available in {{#crossLink "F2.Constants/AppHandlers:property"}}{{/crossLink}}.
@@ -600,6 +613,10 @@
File: sdk\src\app_handlers.js
};
})());
+/**
+ * A convenient collection of all available appHandler events.
+ * @class F2.Constants.AppHandlers
+ */
F2.extend('Constants', {
/**
* A collection of constants for the on/off method names in F2.AppHandlers.
diff --git a/docs/sdk/files/sdk_src_classes.js.html b/docs/sdk/files/sdk_src_classes.js.html
index dfea6a60..bdc2be12 100644
--- a/docs/sdk/files/sdk_src_classes.js.html
+++ b/docs/sdk/files/sdk_src_classes.js.html
@@ -118,6 +118,8 @@