|
This tutorial will walk you through the creation of a Fluid Component called Flutter, a simple Twitter client. This tutorial is not intended to provide in depth information into what Flutter is or how it works, but to give an understanding of the process of creating a component in general. In order to complete this tutorial, it is assumed that you know and understand HTML, CSS, and JS. These are the basic tutorial steps:
The rest of this tutorial will explain each of these steps in detail. Set Up Your EnvironmentCreate a directory structureFor this tutorial, create the following directory layout in your favourite development environment. We use Aptana. Add Fluid Infusion dependencies
Add Flutter dependenciesCopy the files below into the appropriate directories.
|
On This Page
See Also Still need help? Join the infusion-users mailing list and ask your questions there. |
Parts of a Fluid Component
A Fluid Component is made up of three main parts: a creator function, fluid.defaults, and helper functions.
The creator function is used to create a component. It is not a constructor, and does not use the "new" keyword. Public functions can be included inside the creator function.
fluid.defaults is where all of the default options for a component are specified. The Fluid Framework manages the component's options. It makes it easier to find selectors in the DOM, handle programmatically changed styles, and reference all of the options.
The helper functions are the private functions that a component uses, and does not expose. These are optional and will not be discussed in this tutorial.
The Creator Function
The creator function is not a constructor and does not make use of the "new" keyword. Rather, it replaces the need for a constructor and allows the ability to separate public and private methods.
The code for the creator function is marked in the code below.
(function (nameSpace) {
var privateFunc = function () { some private function };
//start of creator function
nameSpace.componentName = function (container, options) {
var that = fluid.initView("nameSpace.componentName ", container, options);
that.refreshView() {
some refresh function
}
return that;
};
//end of creator function
})(nameSpace);
Let's look at this in more detail.
Anonymous functions
By wrapping all of the javascript for the component inside an anonymous function, we can separate private and public methods. The anonymous function is accomplished with the code below.
(function (nameSpace) {
// place javascript here
})(nameSpace)
The creator function signature
The next step is to prepare the creator function for the component. It's signature is defined by giving the function a name and assigning it to a function with the parameters (container, options).
- componentName is the name of the component you are creating.
- container is a "jQueriable" element (a string representing a selector, a DOM element, or a jQuery) which will contain the component.
- options is a JavaScript object specifying the configuration details of the component
The result is shown below.
(function (nameSpace) {
nameSpace.componentName = function (container, options) {};
})(nameSpace)
Read more about the creator function:
initView
To make componentName an actual component, the first task within the creator is to create a new object called that, which is assigned the value of fluid.initView(componentName, container, options).
(function (nameSpace) {
nameSpace.componentName = function (container, options) {
var that = fluid.initView("nameSpace.componentName", container, options);
return that; //should always be the last line of the creator function
};
})(nameSpace)
Read more about the creator function and initView:
More detailed information about the initView function can be found here.
Public functions
To add public functions to the component, you can define a function within the scope of the creator function and add it to the that object. For example if we wanted to add a refreshView function to the component, the code would look like...
(function (nameSpace) {
nameSpace.componentName = function (container, options) {
var that = fluid.initView("nameSpace.componentName", container, options);
that.refreshView() = function { some refresh function };
return that;
};
})(nameSpace)
To call this function you use:
nameSpace.componentName.refreshView();
Private functions
Functions defined within the anonymous function but outside the creator function and without having the nameSpace prefix, will be private and inaccessible to the global namespace. Thus, a private function can be defined as shown below.
(function (nameSpace) {
var privateFunc = function () { some private function };
nameSpace.componentName = function (container, options) {
var that = fluid.initView("nameSpace.componentName", container, options);
that.refreshView() = function { some refresh function };
return that;
};
})(nameSpace)
Read more about private functions:
Writing a creator function for Flutter
Try writing a creator function for Flutter's status view. Download the Sample Code. Rename the file to StatusView.js and place into the "flutter-infused" directory. The following tutorial code for the creator function should be added starting at line 41 (see comments within the code).
Flutter's views take two extra arguments which are not usually required for Fluid components, however Fluid components are flexible and can accommodate additional arguments. These two arguments, "twitter" and "events", are added to the statusView creator function as shown below.
/**
* A View representing the Flutter status panel.
*
* @param {Object} container
* @param {Object} options
*/
fluid.flutter.statusView = function (container, twitter, events, options) {
};
Next call initView
/**
* A View representing the Flutter status panel.
*
* @param {Object} container
* @param {Object} options
*/
fluid.flutter.statusView = function (container, twitter, events, options) {
var that = fluid.initView("fluid.flutter.statusView", container, options);
that.twitter = twitter;
that.events = events;
};
Now add the public methods and any other function that the creator function needs to call.
/**
* A View representing the Flutter status panel.
*
* @param {Object} container
* @param {Object} options
*/
fluid.flutter.statusView = function (container, twitter, events, options) {
var that = fluid.initView("fluid.flutter.statusView", container, options);
that.twitter = twitter;
that.events = events;
/**
* Updates the user's status on Twitter.
*
* @param {String} statusMessage the status message to send to Twitter
*/
that.updateStatus = function (statusMessage) {
// Post the status to the server
that.twitter.postStatus(statusMessage,
that.events.onStatusSaveSuccess.fire,
that.events.onStatusSaveError.fire);
};
/**
* Shows a message to the user when their status has been successfully updated on Twitter.
*/
that.showStatusUpdateSuccess = function () {
showStatusUpdateMessage(that, "Your status was successfully updated!");
// Clear the edit field so the user has another affordance showing that their status update was a success.
that.locate("statusEdit").val("");
};
/**
* Displays a polite error message to the user if there was a problem updating their status on Twitter.
*/
that.showStatusUpdateError = function () {
showStatusUpdateMessage(that, "There was a problem updating your status on Twitter.");
};
setupStatusView(that);
return that;
};
When you are done, the code should look like this - Complete Code
The fluid.defaults function
The fluid.defaults function allows you specify options for a component and set their defaults. The Fluid Framework manages the options set in the fluid.defaults function, including merging them when any of the default options are overridden.
The fluid.defaults function signature
fluid.defaults is a function that takes two arguments, the component and an object of key value pairs representing the component options. Typical options include "selectors", "styles", "events", and "strings", however, you are free to create your own options. Options are specified with a meaningful name for the key, and their default value.
fluid.defaults("component.name", {
selectors: {key: value},
styles: {key: value, ...},
strings: {key: value, ...},
events: {key: value, ...},
optionName: {key: value, ...},
...
}
Default selectors
The selectors option is used to specify the default selectors used to find elements in the DOM. The default selectors can be any "jQueriable" element (a string representing a selector, a DOM element, or a jQuery).
Default selectors for Flutter
Try writing an example using Flutter's Tweets view. Download the Sample Code. Rename the file to TweetsView.js and place into the "flutter-infused" directory. Start writing the fluid.defaults function on line 115.
fluid.defaults("fluid.flutter.tweetsView", { //options go here });
Now add the selectors options, specifying default values for tweets and tweetTemplate. Notice that tweets uses a DOM element and tweetTemplate uses a class as its selector.
fluid.defaults("fluid.flutter.tweetsView", { selectors: { tweets: "li", tweetTemplate: ".flutter-tweet-template" } });
Now that the selectors are defined they can be used in the code. On line 21 the selectableSelector key can be assigned the default value specified for tweets.
selectableSelector: that.options.selectors.tweets
Often you'll want to use the default selectors for finding elements in the DOM. The Fluid Framework simplifies this task for you, by automatically searching within the scope of the container specified in the creator function. All you have to do is pass the name of the selector to that.locate and the Fluid Framework will take care of finding it for you. For example, add the following code on line 32 to remove an element.
that.tweetTemplate = that.locate("tweetTemplate").remove();
When you are done, the finished code will look like this
Default styles
The styles option is used to specify default CSS classes. It can be used to store all CSS classes that a component uses, but is most useful at storing those classes which are added/removed programmatically. The default values for styles take the name of a CSS class. The specific styling applicable to that CSS class should be defined in a separate CSS style sheet that is linked to by the HTML document.
Default styles for Flutter
Try writing an example using Flutter's Settings view. Download the Sample Code. Rename the file to SettingsView.js and place into the "flutter-infused" directory. Start writing the fluid.defaults function on line 70. As before, pass in the name of the component, but start with an options object that only has the selectors specified (as shown below).
fluid.defaults("fluid.flutter.settingsView", { selectors: { usernameField: ".flutter-username-edit", passwordField: ".flutter-password-edit" } });
Now add the styles options and specify the the defaults for hidden.
fluid.defaults("fluid.flutter.settingsView", { selectors: { usernameField: ".flutter-username-edit", passwordField: ".flutter-password-edit" }, styles: { hidden: "flutter-hidden" } });
Put the styles option to use in the code at line 55. This function checks if the hidden class exists in the container and returns the result.
that.isVisible = function () {
return !that.container.hasClass(that.options.styles.hidden);
};
When you are done, the finished code will look like this
Default strings
The strings option is used to specify the defaults for messages, warnings, and other text that is programmatically set by the component. It is useful for localization, by allowing users to replace the default text with that of another language. Adding default strings to Flutter is similar to adding other defaults.
Default events
The events object is used to specify the events used by the component. (Please see below for how to add listeners via a listeners option)
Defining events is slightly different than other defaults. Most importantly, the events object is included in the defaults for the declaration of events, not as an option. This means that they are not intended to be overriden by a value passed to the creator function, as this would could change behaviour.
The structure of events is different, the key is the name of the event, the value can be one of: null, "unicast", or "preventable". Note that null is not a string value, but the actual value null. null allows the fired event to be heard by all listeners that have registered for it. "preventable" is similar to null, but its propagation can be stopped by a registered listener. "unicast" can be used where one and only one listener should be registered for an event. For more information about what these different values represent, see the Infusion Event System.
Default events for Flutter
Try writing an example for Flutter. Download the Sample Code. Rename the file to FlutterInfused.js and place into the "flutter-infused" directory. Start adding the events options to the fluid.defaults function on line 162. The initial structure of the fluid.defaults is shown below. Don't worry about the view options here, they will be explained in the subcomponents section below.
fluid.defaults("fluid.flutter", { friendsView: { type: "fluid.flutter.friendsView" }, tweetsView: { type: "fluid.flutter.tweetsView" }, settingsView: { type: "fluid.flutter.settingsView" }, statusView: { type: "fluid.flutter.statusView" }, selectors: { mainPanel: ".flutter-main-panel", settingsPanel: ".flutter-settings-panel", statusPanel: ".flutter-status-panel", friendsList: ".flutter-friends", tweetsList: ".flutter-tweets", allTabs: ".flutter-panel-tabs li", friendsTab: ".flutter-friends-tab", settingsTab: ".flutter-settings-tab", errorDialog: ".flutter-error-dialog" }, styles: { hidden: "flutter-hidden", activeTab: "fl-activeTab" } });
Now add the events options.
fluid.defaults("fluid.flutter", { friendsView: { type: "fluid.flutter.friendsView" }, tweetsView: { type: "fluid.flutter.tweetsView" }, settingsView: { type: "fluid.flutter.settingsView" }, statusView: { type: "fluid.flutter.statusView" }, selectors: { mainPanel: ".flutter-main-panel", settingsPanel: ".flutter-settings-panel", statusPanel: ".flutter-status-panel", friendsList: ".flutter-friends", tweetsList: ".flutter-tweets", allTabs: ".flutter-panel-tabs li", friendsTab: ".flutter-friends-tab", settingsTab: ".flutter-settings-tab", errorDialog: ".flutter-error-dialog" }, styles: { hidden: "flutter-hidden", activeTab: "fl-activeTab" }, events: { // View-related events. afterFriendSelected: null, onSaveSettings: null, // Model change events. onFriendsFetchSuccess: null, onFriendsFetchError: null, onTweetsFetchSuccess: null, onTweetsFetchError: null, onStatusSaveSuccess: null, onStatusSaveError: null } });
Now that the events have been specified we can fire them or listen to them.
Try firing some events. On line 104, as part of the function to fetch friends, add two event firing functions. The first will fire on success, and the other will fire on error.
that.twitter.fetchFriends(that.events.onFriendsFetchSuccess.fire, that.events.onFriendsFetchError.fire);
Notice that both of these events have a null value, therefore all registered listeners will be notified when these events are fired.
Now listen to some events. On line 26 add a couple of event listeners as part of the bindEventHandlers function.
that.events.onSaveSettings.addListener(that.showMainPanel); that.events.onFriendsFetchError.addListener(that.friendsErrorDialog.open);
The function which is passed in (i.e. that.showMainPanel) is called when the event is fired and detected by the listener.
Alternatively listeners can be specified inside a listeners options object in defaults. In this case, the key is the name of the event to which the listener is bound, and the value is the function to call after the listener detects the event.
Adding a listener to fluid.defaults would look something like this:
fluid.defaults("fluid.flutter", {
listeners: {
eventName: function
}
});
When you are done, the finished code will look like this
Subcomponents
Depending on how you've planned and or refactored your code, you may find yourself needing to break off chunks of code into their own components. We refer to these chunks as subcompenents (although in reality they are just components used by another component). In Flutter, the individual views are components. Flutter, also a component, makes use of the individual views as subcomponents.
In fluid.defaults, you specify a subcomponent using a name and an object containing the key-value pair of type: "component name" as shown below. In this case the subcomponent's name is "subComponent".
fluid.defaults("fluid.flutter", { selectors: { mainPanel: ".flutter-main-panel", settingsPanel: ".flutter-settings-panel" }, styles: { hidden: "flutter-hidden" }, subComponent: { type: "nameSpace.componentName" } }};
Initialization is done using fluid.initSubcomponent. This function takes three arguments:
- the component you are attaching it too, which is represented by "that"
- the name of the subcomponent as specified in fluid.defaults
- an array containing the arguments for the subcomponent
that.subComponent = fluid.initSubcomponent(that, "subComponent", [arg1, arg2, ...]);
Subcomponents for Flutter
Try adding a subcomponent to Flutter. Download the Sample Code. Rename the file to FlutterInfused.js and place into the "flutter-infused" directory. To start we'll add the subcomponent options to the fluid.defaults function, on line 109. The initial structure of the fluid.defaults is shown below.
fluid.defaults("fluid.flutter", { selectors: { mainPanel: ".flutter-main-panel", settingsPanel: ".flutter-settings-panel", statusPanel: ".flutter-status-panel", friendsList: ".flutter-friends", tweetsList: ".flutter-tweets", allTabs: ".flutter-panel-tabs li", friendsTab: ".flutter-friends-tab", settingsTab: ".flutter-settings-tab", errorDialog: ".flutter-error-dialog" }, styles: { hidden: "flutter-hidden", activeTab: "fl-activeTab" }, events: { // View-related events. afterFriendSelected: null, onSaveSettings: null, // Model change events. onFriendsFetchSuccess: null, onFriendsFetchError: null, onTweetsFetchSuccess: null, onTweetsFetchError: null, onStatusSaveSuccess: null, onStatusSaveError: null } });
Now add the subcomponents friendsView, tweetsView, settingsVeiw and statusView.
fluid.defaults("fluid.flutter", { //subcomponent friendsView friendsView: { type: "fluid.flutter.friendsView" }, //subcomponent tweetsView tweetsView: { type: "fluid.flutter.tweetsView" }, //subcomponent settingsView settingsView: { type: "fluid.flutter.settingsView" }, statusView: { type: "fluid.flutter.statusView" }, selectors: { mainPanel: ".flutter-main-panel", settingsPanel: ".flutter-settings-panel", statusPanel: ".flutter-status-panel", friendsList: ".flutter-friends", tweetsList: ".flutter-tweets", allTabs: ".flutter-panel-tabs li", friendsTab: ".flutter-friends-tab", settingsTab: ".flutter-settings-tab", errorDialog: ".flutter-error-dialog" }, styles: { hidden: "flutter-hidden", activeTab: "fl-activeTab" }, events: { // View-related events. afterFriendSelected: null, onSaveSettings: null, // Model change events. onFriendsFetchSuccess: null, onFriendsFetchError: null, onTweetsFetchSuccess: null, onTweetsFetchError: null, onStatusSaveSuccess: null, onStatusSaveError: null } });
All of the subcomponents have been added to fluid.defaults and are ready to be initialized. On line 33 we'll add in the initialization code.
// Create the Friends View, responsible for showing and selecting the list of friends. that.friendsView = fluid.initSubComponent(that, "friendsView", [that.locate("friendsList"), that.twitter, that.events, fluid.COMPONENT_OPTIONS]); // Instantiate the Tweets View, which displays the list of tweets that.tweetsView = fluid.initSubComponent(that, "tweetsView", [that.locate("tweetsList"), that.twitter, that.events, fluid.COMPONENT_OPTIONS]); // The Settings View controls the panel allowing users to edit their username and password for Twitter. that.settingsView = fluid.initSubComponent(that, "settingsView", [that.locate("settingsPanel"), that.twitter, that.events, fluid.COMPONENT_OPTIONS]); // The Status View shows the user's icon, name, and allows them to update their status. that.statusView = fluid.initSubComponent(that, "statusView", [that.locate("statusPanel"), that.twitter, that.events, fluid.COMPONENT_OPTIONS]);
Their are two things to note in the initialization code above:
- the use of that.locate. Recall from the selectors section above that that.locate is used to find DOM elements.
- fluid.Components
- This tells fluid.initSubComponent to check if any options were specified for the subcomponent when the parent component was initialized. In this case the Fluid Framework will merge in those options. For example:
fluid.flutter(container, {friendsView: { selectors: { friends: ".liClass"}}});
- This tells fluid.initSubComponent to check if any options were specified for the subcomponent when the parent component was initialized. In this case the Fluid Framework will merge in those options. For example:
When you are done, the finished code will look like this
Custom Options
There may be times when there are options specific to a component you are writing which don't fit into any of the options already mentioned. fluid.defaults allows you to add custom options.
Here is an example of how you would specify custom options to a component called nameSpace.custom. This declaration follows the same pattern that the other options.
fluid.defaults("nameSpace.custom", {
customOptions: {
meaningfulName: value
}
});
To access this option use:
var getOptionValue = that.options.customOptions.meaningfulName;
To summarize, the following code is a basic template for a fluid component.
/*
Insert license here
*/
/*global jQuery*/
/*global fluid_1_1*/
fluid_1_1 = fluid_1_1 || {}; //use current released version number
(function($, fluid) {
var privateFunc = function() {
// some private function
};
//see http://wiki.fluidproject.org/display/fluid/The+creator+function
//start of creator function
nameSpace.componentName = function(container, options) {
var that = fluid.initView("nameSpace.componentName ", container, options);
that.publicFunc = function() {
// some public function
};
return that;
};
//end of creator function
//start of defaults
fluid.defaults("componentName", {
//default options can be selectors, styles, strings, or events
//also can create options based on subcomponents using subComponentName as the optionName
//finally, custom options are also possible
//see http://wiki.fluidproject.org/display/fluid/fluid.defaults
option1Name: {
key: value
},
option2Name: {
key1: value1,
key2: value2
}
});
// end of defaults
})(jQuery, fluid_1_1);
The World Without the Fluid Framework
Overview
The purpose of this section is to provide an example of how Flutter would be built, without the use of the Fluid Framework.
Setup
Working from the directory structure created above, we will add a couple more directories and files.
1) In the "webapp" directory, add a new sub-directory called "flutter-plain-jquery"
2) Inside the "flutter-plain-jquery" directory we'll add another sub-directory called "sample-data"
3) In the "shared/js" directory, add a new sub-directory called "jquery"
4) Next we'll add the files necessary to make Flutter work. Please copy the files below into the appropriate directories
| File | Directory |
|---|---|
| jquery.keyboard.a11y.js | webapp/shared/css |
| jquery | webapp/shared/js/jquery |
| ui.core.js | webapp/shared/js/jquery |
| ui.dialog.js | webapp/shared/js/jquery |
| ui.draggable.js | webapp/shared/js/jquery |
| Flutter.html | webapp/flutter-plain-jquery |
| Flutter.js | webapp/flutter-plain-jquery |
| Friends | webapp/flutter-infused/sample-data |
| user_timeline_12368532.json | webapp/flutter-plain-jquery/sample-data |
| user_timeline_14538601.json | webapp/flutter-plain-jquery/sample-data |
| user_timeline_14538636.json | webapp/flutter-plain-jquery/sample-data |
| user_timeline_14951188.json | webapp/flutter-plain-jquery/sample-data |
| user_timeline_17868497.json | webapp/flutter-plain-jquery/sample-data |
| user_timeline_19539154.json | webapp/flutter-plain-jquery/sample-data |
| user_timeline_5915782.json | webapp/flutter-plain-jquery/sample-data |
| user_timeline_752673.json | webapp/flutter-plain-jquery/sample-data |
Comparison
| Flutter-Plain-jQuery | Flutter-Infused |
|---|---|
| Relies on hard coded selectors to find elements in the DOM | The Fluid Framework allows selectors to be specified by the integrator. The component does come with defaults for the selectors, which the integrator is free to use or override. |
| Flutter.js is a set of behaviour governing the various aspects of Flutter-Plain-jQuery | The Fluid Framework's event system allows the Flutter-Infused to be partitioned into components/subcomponents, by providing a means of communication between them. The event system is constructed in such a way that individual components/subcomponents do not need to be aware of the existence of one another, but can just listen for events they are interested in. These events are also available to the integrator to listen to, thereby allowing Flutter to be extended |
| There aren't really options. You are able to pass in information about URL's | The options merging from the Fluid Framework provides a simple way to make your component customizable. Changing default options are as simple as specifying which options and values to override in an options object, passed to the Creator function |