This section describes how to perform some common tasks and provides information on troubleshooting and debugging flakes.
A flake may appear more than once on a page. As the Pageflakes framework loads each flake onto the page, it must manage each flake instance separately.
For example, consider the following flake:
<html>
<head>
<title>HelloWorld</title>
<script type="text/javascript">
function onDoSomething()
{
var foo = getElementById("bodytext");
foo.manipulate(); // imaginary method
}
</script>
</head>
<body>
<div id="bodytext">
Hello World
</div>
</body>
</html>
If we tried to load this flake twice, we would get two elements with the id of bodytext. The function onDoSomething() would not work correctly for each flake, because getElementById would always return the <div> element of the first flake.
So we have to add a bit more code to handle this scenario:
<html>
<head>
<title>HelloWorld</title>
<script id="com.pageflakes.HelloWorld2"
type="text/javascript">
function com_pageflakes_HelloWorld2(id)
{
this.load
= function(flakeInstance)
{
}
}
</script>
<script id="_PAGEFLAKES_Instance"
type="text/javascript">
var
_PAGEFLAKES_ = new HelloWorld('_PAGEFLAKES_');
</script>
</head>
<body>
<div id="_PAGEFLAKE_bodytext">
Hello World
</div>
</body>
</html>
When the Pageflakes framework loads each flake for a given page, it replaces _PAGEFLAKES_ with an instance identifier, such as m123. Each flake has a different identifier, such that no two flakes on a given page conflict.
The resulting flake that gets added to the page looks like this:
<html>
<head>
<title>HelloWorld</title>
<script id="com.pageflakes.HelloWorld2"
type="text/javascript">
function com_pageflakes_HelloWorld2(id)
{
this.load = function(flakeInstance)
{
}
}
</script>
<script id="m123Instance"
type="text/javascript">
var m123 = new HelloWorld('m123');
</script>
</head>
<body>
<div id="m123bodytext">
Hello World
</div>
</body>
</html>
If the same flake is loaded again with a different identifier, there is no id attribute collision.
There is one exception to this where id is com.pageflakes.HelloWorld2. In this instance the framework is clever enough to load that script only once, since it is not needed multiple times.
Flakes do not necessarily have to be hand-coded using script and XML. They can be written in any web scripting language, such as PHP, Perl, JSP, ASP, ASP.NET, and so forth.
The Pageflakes framework passes a variety of parameters in HTTP headers, such that the server-side scripts have all the necessary information to build a flake. These are described in Server-Side Scripting.
When using server-side script, it is occasionally useful for the server script to pass parameters to the client script. This can be done in several ways.
Using hidden <input> elements, the server script can set values in those elements for the client script to retrieve later.
<div id="_PAGEFLAKES_Results" class="RssReaderResults">
Loading...
</div>
<%
string rssUrl = "http://www.alistapart.com/rss.xml";
string[] parameters = Request.Url.Query.Split('=');
// Get the rss url
if (parameters.Length > 1)
{
rssUrl = parameters[1];
}
rssUrl = HttpUtility.UrlDecode(rssUrl);
%>
<input id="_PAGEFLAKES_RssDefaultUrl" type="hidden"
value="<%=HttpUtility.HtmlEncode(rssUrl)%>" />
Alternatively, the server script can generate the necessary client script:
<script id="_PAGEFLAKES_InstanceSpecificVariables">
var _PAGEFLAKES_array = {
"<%= arrayItem1 %>", "<%= arrayItem2 %>",
"<%= arrayItem3 %>" };
var _PAGEFLAKES_myURL = '<%= someServerSideVariable %>';
</script>
function com_pageflakes_devdocs_WelcomeFlake(id)
{
this.load = function(instance)
{
this.array = eval(id + 'array');
this.myURL = eval(id + 'myURL');
}
}This example demonstrates how to create a function that automatically saves the settings for a given flake. The function detects differences between the settings form and the profile data; if a difference is found, it calls the save() method.
function demoFlake(id)
{
var _self=this;
var _instance = null;
this.autoSave=function ()
{
var savedDoAlert=false;
if( _instance.Profiles["DOALERT"] )
savedDoAlert = _instance.Profiles["DOALERT"];
var savedText="";
if( _instance.Profiles["TEXT"] )
savedText = _instance.Profiles["TEXT"];
if(document.getElementById(_id+"doAlert").checked!=savedDoAlert
||
document.getElementById(_id+"text").value!=savedText)
_self.saveProfile();
setTimeout(_self.autoSave, 8000);
}
this.load=function(instance)
{
_instance = instance;
_self.autoSave();
}
}
This example:
this during initialization, called _self._self.autoSave() function during load to start the auto-save process.autoSave() function:_self.saveProfile()JavaScript arrays cannot be stored directly into an instance; they must be converted into strings prior to storing. This example clarifies this procedure:
var myarray = new Array("sample1", "sample2", "sample2", "sample4");
var arrayToString = myarray.join("|");
fso.profiles['data'] = arrayToString;
fso.save();
To retrieve the array from the instance and use its previously-saved state, use the following code:
var arrayToString = fso.Profiles['data'];
var myarray = arrayToString.split("|");
Developers can use CSS when developing flakes. When writing CSS, you should use rules that apply only to your flake without affecting the appearance of other flakes or pages. To do this, use CSS classes and avoid global selectors.
Here is an example of a CSS rule that can interfere with the appearance of other flakes:
td
{
padding-top: 5px;
}
This style affects all <td> elements in the current page. Although this style may work correctly, other flakes using table cells may be affected by this style.
Instead of using a global selector, use classes with selectors:
td.class_name
{
padding-top: 5px;
}
This style applies to your flake without affecting other flakes.
You can create instance-specific styles for flakes. These apply only to elements which are specific to the instance and do not affect any other flakes or any other instances of the same flake. Using instance-specific styles can be done by adding _PAGEFLAKES_ before all selectors.
#_PAGEFLAKES_data
{
/*Styles here*/
}
The following code uses both class names and instance-specific
CSS for a very simple "HelloWorld" flake.
<html>
<head>
<title>HelloWorld</title>
<style id="_PAGEFLAKES_MyFlakeStyles">
#_PAGEFLAKES_container
{
font-family: Verdana;
font-size: 18px;
}
h1._PAGEFLAKES_header
{
color: green;
}
</style>
<script id="HelloWorld_flake" type="text/javascript"><![CDATA[
function HelloWorld(id) {
this.load = function(flakeInstance) {
}
}
]]></script>
<script id="_PAGEFLAKES_Instance"
type="text/javascript"><![CDATA[
var _PAGEFLAKES_ = new HelloWorld('_PAGEFLAKES_');
]]></script>
</head>
<body>
<div id="_PAGEFLAKES_container">
<h1 class="_PAGEFLAKES_header">Hello World</h1>
</div>
</body>
</html>
Any flakes hosted on the Pageflakes server can access typed web services provided by ASP.NET Ajax. This code example shows a script tag in JavaScript that makes a web service available:
<script language="JavaScript" src="SimpleService.asmx/js">
This example generates a JavaScript proxy to a web service developed in ASP.NET 2.0. Now web-services can be called anytime directly from JavaScript.
requestSimpleService = SimpleService.EchoString (
"SomeText", //params
OnComplete, //On Complete callback
OnError //OnError callback
);
If a particular service accepts more than one parameter, the call should be performed as follows:
requestSimpleService = SimpleService.EchoString (
"SomeText", //params
"another param",
"another param",
OnComplete, //On Complete callback
OnError //OnError callback
);
There are a number of tools to help Pageflakes developers get started and make the most of their flake. These tools can increase productivity while streamlining the develop/debug process.
For debugging flakes in FireFox, Firebug's advanced debugging capabilities quickly identify any problems within a flake's inner workings.
For IE developers, WebDevHelper is a great tool for debugging. Although it’s not as powerful as Firebug, but it helps debug Javascript errors by showing a complete call stack.
This toolbar is a FireFox extension designed to dissect a flake's output and can collect useful information about the page.
Similar to FireBug, DebugBar works for debugging in Internet Explorer.
This toolbar is created and distributed by Microsoft for easy debugging and analysis of page or flake output.
http://www.microsoft.com/downloads/details.aspx?FamilyID=e59c3964-672d-4...
Aptana is a handy IDE designed for making JavaScript and CSS development more flexible than ever. IDE offers a world of flexibility and can be downloaded at no charge.
This section contains some common mistakes that developers make when building their own flakes.
Markup elements fall into two categories: instance-specific elements and Page-Set specific elements. (This is somewhat analogous to class versus instance members, for those familiar with object orientation).
If a given flake exists many times in a Page-Set, an instance-specific element is created multiple times. The id attribute of such elements needs to be pre-pended with _PAGEFLAKES_, such that each element has a unique id attribute.
Page-Set specific elements are created once, regardless of how many times a flake exists on a Page-Set. The id attribute of such elements must be unique, such that it does not conflict with other Page-Set specific elements. For example, <script> elements containing FUA code, or <link> elements referencing style-sheets are Page-Set specific.
Most browsers, as a security precaution, allow the XMLHttpRequest object to only fetch data from the domain where the script itself was loaded. For flakes, this restriction effectively means that Flakes can only fetch data from *.pageflakes.com using XMLHttpRequest.
To work around this, Pageflakes provides the ContentProxy object.
document.getElementById to get HTML elements. Internet Explorer supports accessing elements using the element ID directly, but Firefox and Opera do not. You can also use the shortcut function 'PF.$' instead of document.getElementById.innerText in Firefox. So, use PF.T(element, text) to set text content inside HTML elements.Using < or > signs inside <script> tags is problematic, even if it is enclosed in double quotes. For example:
<script> body.innerHTML = "<a href='test.html'>Test</a>"; </script>
will fail because the < sign is invalid inside a script tag when it is parsed. So, use CDATA sections for such script blocks. For example:
<script><![CDATA[ body.innerHTML = "<a href='test.html'>Test</a>"; ]]></script>
If you see that your script blocks are not getting processed at all and the functions you are trying to access are completely unavailable, then this might be the reason.
This can also be a problem with for loops. If the script is not enclosed in CDATA, then the following example will fail:
for(var i = 0;i<10;i++)
We need some space before and after the "less than" (<) sign. Otherwise when we parse the flake file, "<10;" appears as a tag in the flake HTML file. Putting a space after the "<" characters prevents it from becoming a tag.
for(var i = 0; i < 10; i ++)
However, the preferred mechanism is to include the script in a CDATA element, and the whitespace is no longer an issue.
Normally we know this refers to the current instance of the class. So, we write scripts like:
this.load = function()
{
this.a = 10;
}
However, if the load function is called from an event or a timer, then this actually means the control from which the event is fired or the timer, not the instance of the class. The same applies to web service callbacks or any asynchronous JavaScript callbacks. In that case, this.a will no longer be a variable inside the class.
This code shows another problem:
function what_is_this()
{
this.load = function(instance)
{
this.resultDIV = $('result');
SomeWebService.DoSomething( 10, 20, 30, onComplete );
}
function onComplete(result)
{
this.resultDIV.innerHTML = result; // FAILS
this.done(); // FAILS
}
this.done = function()
{
alert("I never get called");
}
}
This code will fail because this is no longer the instance of the class; instead it's some object from the Atlas framework which called the onComplete function.
One easy way to work around this problem is to save the value of this in a variable (self is commonly used):
function what_is_this()
{
var _resultDIV;
var self = this;
this.load = function(instance)
{
_resultDIV = $('result');
SomeWebService.DoSomething( 10, 20, 30, this.onComplete );
}
this.onComplete = function(result)
{
_resultDIV.innerHTML = result;
self.done();
}
this.done = function()
{
alert("I do get called");
}
}
While this works, it will leak memory because a closure is formed. Here’s how:
Once load() has been called and returned, onComplete cannot be removed because the global context has a reference to it (to call it back). In addition, the onComplete() function has a reference to the global context because it refers to the self variable, contained in the what_is_this object, which is created in the global context. Because of the circular reference, the memory is never freed, which quickly causes significant performance degradation.
To solve this issue, eliminate such closures:
function what_is_this()
{
this._resultDIV = undefined;
this.load = function(instance)
{
this._resultDIV = $('result');
this.SomeWebService.DoSomething( 10, 20, 30, PF.F(this, this.onComplete));
}
this.onComplete = function(result)
{
this._resultDIV.innerHTML = result;
this.done();
}
this.done = function()
{
alert("I do get called");
}
}
In the example above, the self variable is eliminated, thus removing the closure. In addition, the PF.F function is used when setting the callback, to ensure that the value of this is correct when onComplete is invoked.
The code shown below improves the example even further by using prototypes. The advantage of prototypes is that if we have multiple instances of the what_is_this class created, the functions would be instantiated only once, resulting in performance savings.
function what_is_this()
{
this._resultDIV =
undefined;
}
what_is_this.prototype.load = function(instance)
{
this._resultDIV = $('result');
this.SomeWebService.DoSomething( 10, 20, 30, PF.F(this, this.onComplete));
}
what_is_this.prototype.onComplete = function(result)
{
this._resultDIV.innerHTML = result;
this.done();
}
what_is_this.prototype.done = function()
{
alert("I do get called");
}
Similarly, if an event handler needs to call one of the prototype functions, it must use the PF.F shortcut to ensure that the event handler is called with the correct value of this.
When a page is loaded, the load() function of all the embedded flakes must be called. This causes a large amount of operations, and thus to ensure that pages load in a timely fashion it is important to minimize the processing that occurs in the load() call.
More specifically it is recommended that:
Javascript alerts are very intrusive and interfere with the the presentation of a Pageflakes page. As such, alerts should not be used. As an alternative, any messages should be displayed in a suitable <div> element.
Submitting a <form> using either POST or GET results in a refresh of the whole Pageflakes page. As this is inconsistent with the AJAX model that Pageflakes uses, any <form> elements are removed by the Pageflakes framework when the page is being loaded.
Developers should instead make usage of the Content Proxy object to communicate with server-side implementations.
Since the <form> tag is removed, it is not possible to use ASP.NET Controls such as DataGrid, Repeater, Calendar, Validators, etc. However, intrinsic HTML controls are allowed.