I checked out the latest version of JSLint and found that regardless of setting the passfail to be true/false, JSLint would always abort after the 1st line. This naturally caused issues if one wanted to use JSLint with Hudson and be able to dump out all the results from JSLint so that they could be corrected all at once.
I dug through the code and noticed that this line seemed to be causing the issue:
https://github.com/rogerhu/JSLint/commit/ac9e88a1aadcc26eb7f5d8b76abdb0fe2854c71b
The problem appears to have persisted at least since the 03/06/10 version of fulljslint.js.
Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts
Wednesday, February 2, 2011
JSLint and Rhino support...
Official support for JSLint and Rhino has been dropped according to this post by Douglas Crockford:
http://tech.groups.yahoo.com/group/jslint_com/message/1636
https://github.com/douglascrockford/JSLint/commit/ca120a731db548c0014320fa0c196edc613536ae#diff-3
The actual GitHub that removed this file is located here:
https://github.com/douglascrockford/JSLint/commit/523956b6a2a6771ecfdd138934ed0611fb25bc6c
The Rhino'd version on the jslint-utils is basically concatenating the fulljslint.js with a small wrapper program for the Rhino:
https://github.com/mikewest/jslint-utils/commit/6e854f564b517c696a35b72f93a69635bfc66ab3 has the following diff:
In addition, it changes the JSLint output to be the following:
If you intend to use Flymake with Emacs, you have to check your flymake-jslint.el to change the regexp accordingly.
Page 362 of Learning GNU Emacs from the O'Reilly series and Stack Overflow article explains the reason for the double backslashes. Apparently Emacs needs one backslash when decoding/parsing the Lisp program, and another when creating the regular expression character. Basically the regular expression extracts the file displayed with the \\[ and \\] groupings, and then we extract the filename within it.
http://tech.groups.yahoo.com/group/jslint_com/message/1636
I am dropping support for rhino.js and wsh.js because others have improved on them. If you have such an improvement, please add a record describing it. Thank you.Here is what the original rhino.js file looks like:
https://github.com/douglascrockford/JSLint/commit/ca120a731db548c0014320fa0c196edc613536ae#diff-3
The actual GitHub that removed this file is located here:
https://github.com/douglascrockford/JSLint/commit/523956b6a2a6771ecfdd138934ed0611fb25bc6c
The Rhino'd version on the jslint-utils is basically concatenating the fulljslint.js with a small wrapper program for the Rhino:
cat ./fulljslint.js ./rhino.js > ../rhinoed_jslint.jsThe version posted at
https://github.com/mikewest/jslint-utils/commit/6e854f564b517c696a35b72f93a69635bfc66ab3 has the following diff:
Adjust rhino.js
- accept a 'realfilename' argument for the CSS munging that I'll put
in later on.
- change the defaults to what I consider a good baseline (documented
inline)
var e, i, input, fileToParse, fileToDisplay, defaults;
15c15
< print("Usage: jslint.js file.js");
---
> print("Usage: jslint.js file.js [realfilename.js]");
In addition, it changes the JSLint output to be the following:
print('Lint at line ' + e.line + ' character ' +
47
+ print('[' + fileToDisplay + '] Lint at line ' + e.line + ' character ' +
If you intend to use Flymake with Emacs, you have to check your flymake-jslint.el to change the regexp accordingly.
(setq flymake-err-line-patterns
; (cons '("^Lint at line \\([[:digit:]]+\\) character \\([[:digit:]]+\\): \\(.+\\)$"
; nil 1 2 3)
(cons '("\\[\\(.*\\)\\] Lint at line \\([[:digit:]]+\\) character \\([[:digit:]]+\\): \\(.+\\)$"
1 2 3 4)
flymake-err-line-patterns))
(provide 'flymake-jslint)
Page 362 of Learning GNU Emacs from the O'Reilly series and Stack Overflow article explains the reason for the double backslashes. Apparently Emacs needs one backslash when decoding/parsing the Lisp program, and another when creating the regular expression character. Basically the regular expression extracts the file displayed with the \\[ and \\] groupings, and then we extract the filename within it.
JSLint differences and empty block warning messages...
This Rhino JavaScript version appears to avoid issuing "Empty block" warnings. Further examination reveals that the block() checker for JSLint versions out there are all slightly different.
http://www.microidc.com/usr/tools/jslint/rhino/index.html
http://www.microidc.com/usr/tools/jslint/rhino/index.html
function block(f) {
var a, b = inblock, old_indent = indent, s = scope, t;
inblock = f;
scope = Object.create(scope);
nonadjacent(token, nexttoken);
t = nexttoken;
funct['(verb)'] = null;
scope = s;
inblock = b;
return a;
}
The Rhino'd version at https://github.com/mikewest/jslint-utils/blob/master/lib/rhinoed_jslint.js:function block(f) {
var a, b = inblock, old_indent = indent, s = scope, t;
inblock = f;
scope = Object.create(scope);
nonadjacent(token, nexttoken);
t = nexttoken;
funct['(verb)'] = null;
scope = s;
inblock = b;
if (f && (!a || a.length === 0)) {
warning("Empty block.");
}
return a;
}
Douglas Crockford's official block() checker (http://www.jslint.com/webjslint.js) or at https://github.com/douglascrockford/JSLint:function block(ordinary) {
var a, b = inblock,
m = strict_mode,
s = scope,
t;
inblock = ordinary;
scope = Object.create(scope);
spaces();
t = nexttoken;
if (nexttoken.id === '{') {
advance('{');
step_in();
if (!ordinary && !use_strict() && !m && option.strict && funct['(context)']['(global)']) {
warning(bundle.missing_use_strict);
}
a = statements();
funct['(verb)'] = null;
scope = s;
inblock = b;
if (ordinary && a.length === 0) {
warning(bundle.empty_block);
}
return a;
Friday, January 28, 2011
is: visible in jQuery resolves to true if visibility:hidden....
http://api.jquery.com/visible-selector/
Elements with visibility: hidden or opacity: 0 are considered to be visible, since they still consume space in the layout. During animations that hide an element, the element is considered to be visible until the end of the animation. During animations to show an element, the element is considered to be visible at the start at the animation.
Friday, December 17, 2010
The trouble with contenteditable and WYSWYG editors..
If you want to implement some type of WYSWYG editors, there are several options. You can try out CkEditor (http://ckeditor.com/), which seems to be a re-write of the popular FCkEditor. It's still 300kB gzip'd, and it has a lot of plug-ins. You can also TinyMCE and WymEditor, and neither seem optimal since they add so many extra HTML tags and add to the code base. There's WysiHat, which is used in Basecamp and developed by 37signals.com, but primarily leverages Prototype so JQuery/MooTools frameworks are out.
Enter the 'contenteditable' attribute, which lets you turn any DOM element into an editable field. You can bold, highlight, italicize and the browser automatically adds the appropriate HTML tag. The challenge of course is that different browsers add different tags when the Enter button is pressed. The approach listed here is to intercept the Enter key and insert <br> tags to avoid cross-browser issues:
http://stackoverflow.com/questions/2735672/how-to-change-behavior-of-contenteditable-blocks-after-on-enter-pressed-in-variou
There isn't an answer on Stack Overflow threads about what to do with the browser to get the cursor position to be move to be situated after "<br>" tags are inserted. I looked through the CKEditor source code and there's a specific plug-in called _source/plugins/enterkey/plugin.js that tries to deal with the issue. It looks like what ends up happening is that not only is a <br><br/> is added but also an extra bogus <br> is added, in addition to a <span> </span> gets inserted. The code gets even more convoluted when you start working with IE and Opera browsers, so this general approach just seems a bit difficult in geneal.
The solution may be to post-replace these tags instead:
http://stackoverflow.com/questions/3455931/extracting-text-from-a-contenteditable-div
Enter the 'contenteditable' attribute, which lets you turn any DOM element into an editable field. You can bold, highlight, italicize and the browser automatically adds the appropriate HTML tag. The challenge of course is that different browsers add different tags when the Enter button is pressed. The approach listed here is to intercept the Enter key and insert <br> tags to avoid cross-browser issues:
http://stackoverflow.com/questions/2735672/how-to-change-behavior-of-contenteditable-blocks-after-on-enter-pressed-in-variou
There isn't an answer on Stack Overflow threads about what to do with the browser to get the cursor position to be move to be situated after "<br>" tags are inserted. I looked through the CKEditor source code and there's a specific plug-in called _source/plugins/enterkey/plugin.js that tries to deal with the issue. It looks like what ends up happening is that not only is a <br><br/> is added but also an extra bogus <br> is added, in addition to a <span> </span> gets inserted. The code gets even more convoluted when you start working with IE and Opera browsers, so this general approach just seems a bit difficult in geneal.
The solution may be to post-replace these tags instead:
http://stackoverflow.com/questions/3455931/extracting-text-from-a-contenteditable-div
collapse() in Ran
A good explanation of JavaScript DOM ranges...
http://www.wrox.com/WileyCDA/Section/JavaScript-DOM-Ranges-Page-3.id-292303.html
Collapsing a DOM Range
To empty a range, (that is, to have it select no part of the document), you collapse it. Collapsing a range resembles the behavior of a text box. When you have text in a text box, you can highlight an entire word using the mouse. However, if you left-click the mouse again, the selection is removed and the cursor is located between two letters. When you collapse a range, you are setting its locations between parts of a document, either at the beginning of the range selection or at the end. Figure 6 illustrates what happens when a range is collapsed.Figure 6You can collapse a range by using the
collapse() method, which accepts a single argument: a Boolean value indicating which end of the range to collapse to. If the argument is true, then the range is collapsed to its starting point; if false, the range is collapsed to its ending point. To determine if a range is collapsed, you can use the collapsed property:How to do range highlighting too:oRange.collapse(true); //collapse to the starting point alert(oRange.collapsed); //outputs "true"
http://efreedom.com/Question/1-3771824/Select-Range-Contenteditable-Div
Sunday, November 7, 2010
WymEditor strips out <embed> tags
WymEditor seems to strip out <embed>/<object> tags so does not seem to have the ability to embed YouTube clips. The way to fix it is to patch the latest WymEditor release, or download the GitHub files. Here are two blog posts that talk about the issue:
http://simonwoodside.com/weblog/2009/2/8/how_to_make_wym_editor/
http://meridimus.com/post/167515648/wymeditor-flash
I took the diff of these changes and patched the 0.5rc2 release accordingly:
This issue has been filed as a ticket on the trac.wymeditor.org. If you want to create an account, you have to visit http://trac.wymeditor/trac/register. The link that's made available on the web site doesn't quite work. You can download the patched file here:
http://trac.wymeditor.org/trac/ticket/221
Update: There is already a plug-in that adds these tags into the code:
https://github.com/wymeditor/wymeditor/blob/master/src/wymeditor/plugins/embed/jquery.wymeditor.embed.js
http://simonwoodside.com/weblog/2009/2/8/how_to_make_wym_editor/
http://meridimus.com/post/167515648/wymeditor-flash
I took the diff of these changes and patched the 0.5rc2 release accordingly:
1757d1756
< "param",
2124a2124,2150
> "param":
> {
> "attributes":
> [
> "type",
> "value",
> "name"
> ],
> "required":[
> "name"
> ],
> "inside":"object"
> },
> "embed":
> {
> "attributes":
> [
> "width",
> "height",
> "allowfullscreen",
> "allowscriptaccess",
> "wmode",
> "type",
> "src"
> ],
> "inside":"object"
> },
2166,2178d2191
< "param":
< {
< "attributes":
< {
< "0":"type",
< "valuetype":/^(data|ref|object)$/,
< "1":"valuetype",
< "2":"value"
< },
< "required":[
< "name"
< ]
< },
3381c3394
< "object", "ol", "optgroup", "option", "p", "param", "pre", "q",
---
> "ol", "optgroup", "option", "p", "pre", "q",
3384c3397
< "thead", "title", "tr", "tt", "ul", "var", "extends"];
---
> "thead", "title", "tr", "tt", "ul", "var", "extends", "object"];
3387c3400
< this.inline_tags = ["br", "hr", "img", "input"];
---
> this.inline_tags = ["br", "hr", "img", "input", "param", "embed"];
This issue has been filed as a ticket on the trac.wymeditor.org. If you want to create an account, you have to visit http://trac.wymeditor/trac/register. The link that's made available on the web site doesn't quite work. You can download the patched file here:
http://trac.wymeditor.org/trac/ticket/221
Update: There is already a plug-in that adds these tags into the code:
https://github.com/wymeditor/wymeditor/blob/master/src/wymeditor/plugins/embed/jquery.wymeditor.embed.js
Monday, October 25, 2010
Using jQuery's pagination
Try out the demo of this jQuery pagination code:
http://d-scribe.de/webtools/jquery-pagination/demo/demo_options.htm
It works pretty well for static/fixed content, but how about for Ajax-based content? What about situations when you want to add elements to the document so that the total number of entries can be changed?
First, the great part of this plug-in is that you can use the pageSelectCallBack() to do your Ajax query. The demo_ajax.htm that comes with this plug-in shows you the basic approach. The challenge of course comes if you have a situation when you want to add/update existing entries and have the changes reflected in the pagination.
In order to accomplish this last part, several changes had to be made to the jquery.pagination.js code. There are actually several helper functions that get attached to the HTML DIV element, including selectPage(), prevPage(), nextPage().
1) I noticed first that the selectPage() was originally:
But this line appears to be a bug since the PageSelected() takes in an event object, and sets the current page using the event target and retrieves the page ID from there.
The way to fix it would be to invoke the callback function directly, similar to how it's done in other parts of the code:
2) The other aspect is that we'll add helper functions that will enable us to update the rendering links (updateLinks()), as well as set and retrieve the total number of entries. Since the 'renderer' vars is a variable local to the scope, this addition was one way to get access to the values without modifying other parts of the existing code:
3) Finally, inside our callback function, we invoke the getTotalPages(), setTotalPages(), and updateLinks() after the Ajax response, assuming that the JSON response returns back a 'total_entries' variable that denotes the total # of entries. Note that since jq is a jQuery object and we need to access the HTML DIV element (and not the jQuery object), we have to use j[0] to get access. (This approach of course limits us to have one pagination plug-in per page, but it can be modified to use each() if necessary).
So our diff for the jQuery pagination plug-in looks like:
http://d-scribe.de/webtools/jquery-pagination/demo/demo_options.htm
It works pretty well for static/fixed content, but how about for Ajax-based content? What about situations when you want to add elements to the document so that the total number of entries can be changed?
First, the great part of this plug-in is that you can use the pageSelectCallBack() to do your Ajax query. The demo_ajax.htm that comes with this plug-in shows you the basic approach. The challenge of course comes if you have a situation when you want to add/update existing entries and have the changes reflected in the pagination.
In order to accomplish this last part, several changes had to be made to the jquery.pagination.js code. There are actually several helper functions that get attached to the HTML DIV element, including selectPage(), prevPage(), nextPage().
1) I noticed first that the selectPage() was originally:
containers.each(function () {
// Attach control functions to the DOM element
this.selectPage = function (page_id) { pageSelected(page_id);}
But this line appears to be a bug since the PageSelected() takes in an event object, and sets the current page using the event target and retrieves the page ID from there.
function pageSelected(evt) {
var links, current_page = $(evt.target).data('page_id');
containers.data('current_page', current_page);
links = renderer.getLinks(current_page, pageSelected);
containers.empty();
The way to fix it would be to invoke the callback function directly, similar to how it's done in other parts of the code:
this.selectPage = function (page_id) { opts.callback(page_id, containers); }
2) The other aspect is that we'll add helper functions that will enable us to update the rendering links (updateLinks()), as well as set and retrieve the total number of entries. Since the 'renderer' vars is a variable local to the scope, this addition was one way to get access to the values without modifying other parts of the existing code:
this.updateLinks = function() {
var current_page = containers.data('current_page');
links = renderer.getLinks(current_page, pageSelected);
containers.empty();
links.appendTo(containers);
}
this.setTotalPages = function(num) {
renderer.pc.maxentries = num;
}
this.getTotalPages = function(num) {
return renderer.pc.maxentries;
}
3) Finally, inside our callback function, we invoke the getTotalPages(), setTotalPages(), and updateLinks() after the Ajax response, assuming that the JSON response returns back a 'total_entries' variable that denotes the total # of entries. Note that since jq is a jQuery object and we need to access the HTML DIV element (and not the jQuery object), we have to use j[0] to get access. (This approach of course limits us to have one pagination plug-in per page, but it can be modified to use each() if necessary).
function pageSelectCallback(page_index, jq) {
var url = "/get_data";
if (init === true) {
$.ajax({
url: url,
dataType: 'json',
data: {
'start_page': page_index
},
success: function (response) {
if (jq[0].getTotalPages() !== response.total_entries) { // Update the total count
jq[0].setTotalPages(response.total_entries);
jq[0].updateLinks();
So our diff for the jQuery pagination plug-in looks like:
containers.each(function () {
// Attach control functions to the DOM element
- this.selectPage = function (page_id) { pageSelected(page_id);}
- this.prevPage = function () {
- var current_page = containers.data('current_page');
- if (current_page > 0) {
- pageSelected(current_page - 1);
- return true;
- }
- else {
- return false;
- }
- }
- this.nextPage = function () {
- var current_page = containers.data('current_page');
- if(current_page < numPages()-1) {
- pageSelected(current_page+1);
- return true;
- }
- else {
- return false;
- }
- }
+ this.selectPage = function (page_id) { opts.callback(page_id, containers); }
+ this.prevPage = function () {
+ var current_page = containers.data('current_page');
+ if (current_page > 0) {
+ pageSelected(current_page - 1);
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ this.nextPage = function () {
+ var current_page = containers.data('current_page');
+ if(current_page < numPages()-1) {
+ pageSelected(current_page+1);
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ this.updateLinks = function() {
+ var current_page = containers.data('current_page');
+ links = renderer.getLinks(current_page, pageSelected);
+ containers.empty();
+ links.appendTo(containers);
+ }
+ this.setTotalPages = function(num) {
+ renderer.pc.maxentries = num;
+ }
+
+ this.getTotalPages = function(num) {
+ return renderer.pc.maxentries;
+ }
});
// When all initialisation is done, draw the links
- links = renderer.getLinks(current_page, pageSelected);
- containers.empty();
- links.appendTo(containers);
+ containers.each(function() { this.updateLinks(); });
+
// call callback function
opts.callback(current_page, containers);
-
+
}
Saturday, October 23, 2010
Inside Facebook's JavaScript SDK code...
Over the past 2 weeks, I've been curious to figure out how Facebook's JavaScript SDK is implemented. The documentation and source code is discussed at http://github.com/facebook/connect-js/. The JavaScript library that you are supposed to add is located at: http://connect.facebook.net/en_US/all.js.
1. First, you can use Rhino and JS Beautifier (http://jsbeautifier.org/) to de-minify the http://connect.facebook.net/en_US/all.js file. Using the uncompressed version allows you not only review the source code but also enables you to set breakpoints and add alert statements to get a better understanding how the JavaScript code works.
2. Once you de-minify the code, you'll notice that Facebook's JavaScript SDK attempts to modularize the different aspects of its code. There are several different modules included with the all.js file. Among the notable modules and their accompanying utils inside this file are:
Inside all.js, you'll notice inside the source code the extensive use of FB.provide(), which is essentially a fancy classical object-oriented way to attach these modules to the FB object. For instance, FB.provide('Array', { ... }) is simply a way to add the Array object and its accompanying functions (i.e. FB.Array.indexOf()). (You can convince yourself by reviewing the provide() and create() functions inside the file to see how things work):
a. The code verifies that you have provide an API key parameter (i.e. FB.init ( {apiKey : '12345'});).
b. The function init() tries to load the fbs_ cookie that relates to this API key, setting FB.Cookie._enabled to be true, attempts to find a cookie that matches fbs_ + API_KEY for the domain, and sets the FB._session according to the cookie.
c. The code then invokes getLoginStatus(), which attempts to run the auth.status function.
1. A cross-domain handler is used (FB.Auth.xdHandler). These handlers will be used later to deal with the results rom invoking extern/login_status.php.
2. Three different callbacks are added: no_session, no_user, ok_session. Each of these callbacks translate to results of notConnected, unknown, and connected, respectively.
3. The callbacks each return a function called xdResponseWrapper() that is used to parse the JSON response from the result and used to update the FB._session object.
" -O /tmp/bla, you would see that the code returned would have an XD Proxy code that will execute doFragmentSend() when loaded. In other words, the IFrame will load this JavaScript code from www.facebook.com. What actually happens is that this iframe will redirect to xd_proxy.php, which will provide the frame of the callback to invoke in the cb= code. The redirect URL looks similar to the following:
1. First, you can use Rhino and JS Beautifier (http://jsbeautifier.org/) to de-minify the http://connect.facebook.net/en_US/all.js file. Using the uncompressed version allows you not only review the source code but also enables you to set breakpoints and add alert statements to get a better understanding how the JavaScript code works.
2. Once you de-minify the code, you'll notice that Facebook's JavaScript SDK attempts to modularize the different aspects of its code. There are several different modules included with the all.js file. Among the notable modules and their accompanying utils inside this file are:
FB - getLoginStatus(), getSession(), login(), logout() Array (array utils) - indexOf, merge, filter, keys, map, forEach QS (query string utils) - encode, decode Content (DOM-related utils) - append, appendHidden, insertIFrame, postTarget Flash (Flash-related utils) - init, hasMinVersion, onReady JSON - stringify, parse, flatten ApiServer - graph, rest, oauthRequest, jsonp, flash EventProvider - subscribers, subscribe, unsubscribe, monitor, clear, fire Intl (international utils) - _endsInPunct, _tx, tx String - trim, format, escapeHTML, quote Dom - containsCss, addCss, removeCss, getStyle, setStyle, addScript, addCssRulse, getBrowserType, getViewPortInfo, ready Dialog - _findRoot, _showLoader, _hideLoader, _makeActive, _lowerActive, _removeStacked, create, show, remove XD (cross-domain utils) - init, resolveRelation, handler, recv, PostMessage.init, PostMessage.onMessage, Flash.init, Flash.onMessage, Fragment.checkandDispatch Arbiter - inform UiServer - genericTransform, prepareCall, getDisplayMode, getXdRelation, popup, hidden, iframe, async, _insertIframe, _triggerDefault, _popupMonitor, _xdChannelHandler, _xdNextHandler, _xdRecv, _xdResult Auth - setSession, xdHandler, xdResponseWrapper UIServer.Methods - permissions.request, auth.logout, auth.status Canvas - setSize, setAutoResize
Inside all.js, you'll notice inside the source code the extensive use of FB.provide(), which is essentially a fancy classical object-oriented way to attach these modules to the FB object. For instance, FB.provide('Array', { ... }) is simply a way to add the Array object and its accompanying functions (i.e. FB.Array.indexOf()). (You can convince yourself by reviewing the provide() and create() functions inside the file to see how things work):
create: function(c, h) {
var e = window.FB,
d = c ? c.split('.') : [],
a = d.length;
for (var b = 0; b < a; b++) {
var g = d[b];
var f = e[g];
if (!f) {
f = (h && b + 1 == a) ? h : {};
e[g] = f;
}
e = f;
}
return e;
},
provide: function(c, b, a) {
return FB.copy(typeof c == 'string' ? FB.create(c) : c, b, a);
},
3. When you call FB.init(), several things happen:a. The code verifies that you have provide an API key parameter (i.e. FB.init ( {apiKey : '12345'});).
b. The function init() tries to load the fbs_ cookie that relates to this API key, setting FB.Cookie._enabled to be true, attempts to find a cookie that matches fbs_ + API_KEY for the domain, and sets the FB._session according to the cookie.
c. The code then invokes getLoginStatus(), which attempts to run the auth.status function.
FB.ui({
method: 'auth.status',
display: 'hidden'
}, c);
d. Inside the auth.status function, you will see a few notable things:1. A cross-domain handler is used (FB.Auth.xdHandler). These handlers will be used later to deal with the results rom invoking extern/login_status.php.
2. Three different callbacks are added: no_session, no_user, ok_session. Each of these callbacks translate to results of notConnected, unknown, and connected, respectively.
3. The callbacks each return a function called xdResponseWrapper() that is used to parse the JSON response from the result and used to update the FB._session object.
'auth.status': {
url: 'extern/login_status.php',
transform: function(a) {
var b = a.cb,
c = a.id,
d = FB.Auth.xdHandler;
delete a.cb;
FB.copy(a.params, {
no_session: d(b, c, 'parent', false, 'notConnected'),
no_user: d(b, c, 'parent', false, 'unknown'),
ok_session: d(b, c, 'parent', false, 'connected'),
session_version: 3,
extern: FB._inCanvas ? 0 : 2
});
return a;
In fact, if you were to step through this code, you could see that there are three callbacks: no_user="http://static.ak.fbcdn.net/connect/xd_proxy.php#cb=f146913dd972a&origin=http%3A%2F%2Fdev.myhost.com%2Ff14444845a8136&relation=parent&transport=postmessage&frame=f87b5165d6fd26" no_session="http://static.ak.fbcdn.net/connect/xd_proxy.php#cb=f3424882dbe5a&origin=http%3A%2F%2Fdev.myhost.com%2Ff14444845a8136&relation=parent&transport=postmessage&frame=f87b5165d6fd26" no_user="http://static.ak.fbcdn.net/connect/xd_proxy.php#cb=f3424882dbe5c&origin=http%3A%2F%2Fdev.myhost.com%2Ff14444845a8136&relation=parent&transport=postmessage&frame=f87b5165d6fd26"Each of these callbacks (cb=xxxxxxx) refer to a separate JavaScript function that will be invoked depending on the result returned by the cross-domain auth.status request. The all.js will insert a hidden iframe with a pointer to these callbacks. If we use the Firebug console and search for $('iframe') objects, you would see something similar:
<iframe scrolling="no" id="f1970fe755e1e58" name="f1a7f022a28bd14" style="border: medium none; overflow: hidden;" class="FB_UI_Hidden" src="http://www.facebook.com/extern/login_status.php?access_token=false&api_key=022046fa222ebeac8bdc99ec4ebdf8b2&display=hidden&extern=2&locale=en_US&method=auth.status&next=http%3A%2F%2Fstatic.ak.fbcdn.net%2Fconnect%2Fxd_proxy.php%23cb%3Df191b56da21a52a%26origin%3Dhttp%253A%252F%252Fdev.myhost.com%252Ff14444845a8136%26relation%3Dopener%26transport%3Dpostmessage%26frame%3Df87b5165d6fd26%26result%3D%2522xxRESULTTOKENxx%2522&no_session=http%3A%2F%2Fstatic.ak.fbcdn.net%2Fconnect%2Fxd_proxy.php%23cb%3Df3424882dbe5a%26origin%3Dhttp%253A%252F%252Fdev.myhost.com%252Ff14444845a8136%26relation%3Dparent%26transport%3Dpostmessage%26frame%3Df87b5165d6fd26&no_user=http%3A%2F%2Fstatic.ak.fbcdn.net%2Fconnect%2Fxd_proxy.php%23cb%3Df146913dd972a%26origin%3Dhttp%253A%252F%252Fdev.myhost.com%252Ff14444845a8136%26relation%3Dparent%26transport%3Dpostmessage%26frame%3Df87b5165d6fd26&ok_session=http%3A%2F%2Fstatic.ak.fbcdn.net%2Fconnect%2Fxd_proxy.php%23cb%3Df1ce4645681670e%26origin%3Dhttp%253A%252F%252Fdev.myhost.com%252Ff14444845a8136%26relation%3Dparent%26transport%3Dpostmessage%26frame%3Df87b5165d6fd26&sdk=joey&session_version=3"></iframe>If you were to copy the entire src tag, do a wget "
http://static.ak.fbcdn.net/connect/xd_proxy.php#cb=f28ec7bcadf45c&origin=http%3A%2F%2Fdev.myhost.com%2Ffeb2a54d47bbae&relation=parent&transport=postmessage&frame=f2b29b433ebc468Inside the xd_proxy.php code, these are the critical lines for the cross-domain request. Since params.relation is equal to 'parent' (relation=parent in the URL string), the resolveRelation() will return back window.parent and invoke the window.parent.postMessage() function that was added by the all.js file.
// either send the message via postMessage, or via Flash
if (params.transport == 'postmessage') {
resolveRelation(params.relation).postMessage(fragment, params.origin);
When the postMessage() gets invoked, an event listener is created for receiving the related event.data, which is then used to decode the URL query string and extract the cb= callback. The source code. This callback function is then execute (assuming the callback exists): recv: function(b) {
if (typeof b == 'string') b = FB.QS.decode(b);
var a = FB.XD._callbacks[b.cb];
if (!FB.XD._forever[b.cb]) delete FB.XD._callbacks[b.cb];
a && a(b);
},
PostMessage: {
init: function() {
var a = FB.XD.PostMessage.onMessage;
window.addEventListener ? window.addEventListener('message', a, false) : window.attachEvent('onmessage', a);
},
onMessage: function(event) {
FB.XD.recv(event.data);
}
},
One side effect is that if the no_user or no_session callback is invoked, then the function that tries to parse the response and invoke the FB.Auth.setSession() will fail. xdResponseWrapper: function(a, c, b) {
return function(d) {
try {
b = FB.JSON.parse(d.session);
} catch (f) {}
if (b) c = 'connected';
var e = FB.Auth.setSession(b || null, c);
e.perms = d && d.perms || null;
a && a(e);
};
Wednesday, October 20, 2010
How the jQuery.active code works..
If you look online to find the best way to check if there are any active jQuery Ajax requests for Selenium, you may find the following line that can be invoked by your code:
Here is the documentation describes the global variable in http://api.jquery.com/jQuery.ajax/
browser.wait_for_condition("selenium.browserbot.getCurrentWindow().jQuery.active === 0;", '30000')
If you look inside the jQuery code, here is how the active flag works. Basically the global: flag is a default setting to enable global event handlers for ajaxStart and ajaxStop. If the jQuery.active counter is 0, then we fire ajaxStart. If the jQuery.active is equal to 0 after decrementing, then we fire the ajaxStop event:ajaxSettings: {
url: location.href,
global: true,
type: "GET",
contentType: "application/x-www-form-urlencoded",
processData: true,
async: true,
etag: {},
ajax: function( origSettings ) {
var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings);
// Watch for a new set of requests
if ( s.global && ! jQuery.active++ ) {
jQuery.event.trigger( "ajaxStart" );
}
// Handle the global AJAX counter
if ( s.global && ! --jQuery.active ) {
jQuery.event.trigger( "ajaxStop" );
}
Here is the documentation describes the global variable in http://api.jquery.com/jQuery.ajax/
globalBoolean Default: true Whether to trigger global Ajax event handlers for this request. The default is true. Set to false to prevent the global handlers like ajaxStart or ajaxStop from being triggered. This can be used to control various Ajax Events.In other words, you still need to put JavaScript-based wait_for_condition() calls if you are doing things like waiting for dialog boxes to render. For instance, if you are using a JQuery UI dialog box, you could do something like the following:
selenium.wait_for_condition("selenium.browserbot.getCurrentWindow().document.getElementsByClassName('ui-dialog')[0].style.display != 'none'", 10000)
This function will wait for the presence of the dialog box. We rely on the display: style property of the overlay to determine if the dialog is rendered properly. We assume in the example for now there is only one ui-dialog class. Using the overlay that gets rendered is how JQuery hides/shows the dialog box.
Tuesday, October 12, 2010
Chrome and JQuery UI min-height issue
The JQuery UI library attempts to determine whether the browser can support the min-height propery by injecting a <div> tag into the body and checking the offsetHeight property, which represents the total height of the element (including the padding & margin). If the offsetHeight is equal to 100, then the $.support.minHeight is also set to true.
jQuery UI (v.1.8.5)
The problem with this approach is that if you use Chrome and zoom-out (57%, 69%, 83%), the offsetHeight will be 98, 99, and 99 instead, therefore causing the $.support.minHeight to not equal to 100.
This issue can ultimately affect the use of the dialog box. If the browser cannot support the minimum height, it will resort to Math.max() operations to determine what the height of the dialog content should be. Since nonContentHeight can easily equal to 100px, you end up needing to set a content height of a minimum of 150px.
The problem of course is that your dialog box cannot grow/shrink according to the size of your content using the height: auto property.
I checked through the Chromium source code (http://dev.chromium.org/Home) and can confirm that HTML elements are scaled according to some type of zoom factor:
WebKit/WebCore/dom/Element.cpp
The PageZoom class (found in src/chrome/common/page_zoom.h) seems to trigger different page zoom levels:
Inside browser.cc, the ZOOM commands are declared:
WebKit seems to adjust the zoom level here:
chromium/src/chrome/renderer/render_view.cc
So far I've noticed that the height of the <div> scales properly, but the offsetHeight still reports 1-2px less than the desired minimum height. Oh, here's the first set of patches that implemented full page zoom on WebKit:
https://bug-14998-attachments.webkit.org/attachment.cgi?id=19878
https://bugs.webkit.org/show_bug.cgi?id=14998
Update: The problem possibly related to using integers for zooming/rendering?
https://bugs.webkit.org/show_bug.cgi?id=60318
jQuery UI (v.1.8.5)
// support
$(function() {
var div = document.createElement( "div" ),
body = document.body;
$.extend( div.style, {
minHeight: "100px",
height: "auto",
padding: 0,
borderWidth: 0
});
$.support.minHeight = body.appendChild( div ).offsetHeight === 100;
// set display to none to avoid a layout bug in IE
// http://dev.jquery.com/ticket/4014
body.removeChild( div ).style.display = "none";
});
The problem with this approach is that if you use Chrome and zoom-out (57%, 69%, 83%), the offsetHeight will be 98, 99, and 99 instead, therefore causing the $.support.minHeight to not equal to 100.
This issue can ultimately affect the use of the dialog box. If the browser cannot support the minimum height, it will resort to Math.max() operations to determine what the height of the dialog content should be. Since nonContentHeight can easily equal to 100px, you end up needing to set a content height of a minimum of 150px.
The problem of course is that your dialog box cannot grow/shrink according to the size of your content using the height: auto property.
this.element
.css(options.height === 'auto' ? {
minHeight: Math.max(options.minHeight - nonContentHeight, 0),
height: $.support.minHeight ? 'auto' :
Math.max(options.minHeight - nonContentHeight, 0)
} : {
minHeight: 0,
height: Math.max(options.height - nonContentHeight, 0)
})
.show();
I checked through the Chromium source code (http://dev.chromium.org/Home) and can confirm that HTML elements are scaled according to some type of zoom factor:
WebKit/WebCore/dom/Element.cpp
int Element::offsetHeight()
{
document()->updateLayoutIgnorePendingStylesheets();
if (RenderBoxModelObject* rend = renderBoxModelObject())
return adjustForAbsoluteZoom(rend->offsetHeight(), rend);
return 0;
}
.
.
inline int adjustForAbsoluteZoom(int value, const RenderStyle* style)
{
double zoomFactor = style->effectiveZoom();
if (zoomFactor == 1)
return value;
// Needed because computeLengthInt truncates (rather than rounds) when scaling up.
if (zoomFactor > 1)
value++;
return roundForImpreciseConversion(value / zoomFactor);
}
template inline T roundForImpreciseConversion(double value)
{
// Dimension calculations are imprecise, often resulting in values of e.g.
// 44.99998. We need to go ahead and round if we're really close to the
// next integer value.
value += (value < 0) ? -0.01 : +0.01;
return ((value > max) || (value < min)) ? 0 : static_cast(value);
}
src/third_party/WebKit/WebCore/rendering/style/RenderStyle.h:
static float initialZoom() { return 1.0f; }
m_style->setEffectiveZoom(m_parentStyle ? m_parentStyle->effectiveZoom() : RenderStyle::initialZoom());
float m_effectiveZoom;
m_effectiveZoom(RenderStyle::initialZoom())
class RenderStyle: public RefCounted {
.
.
float effectiveZoom() const { return rareInheritedData->m_effectiveZoom; }
float zoom() const { return visual->m_zoom; }
void setZoom(float f) { SET_VAR(visual, m_zoom, f); setEffectiveZoom(effectiveZoom() * zoom()); }
}
The PageZoom class (found in src/chrome/common/page_zoom.h) seems to trigger different page zoom levels:
class PageZoom {
public:
// This enum is the parameter to various text/page zoom commands so we know
// what the specific zoom command is.
enum Function {
ZOOM_OUT = -1,
RESET = 0,
ZOOM_IN = 1,
};
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(PageZoom);
};
#endif // CHROME_COMMON_PAGE_ZOOM_H_
Inside browser.cc, the ZOOM commands are declared:
void Browser::Zoom(PageZoom::Function zoom_function) {
// Zoom
command_updater_.UpdateCommandEnabled(IDC_ZOOM_MENU, true);
command_updater_.UpdateCommandEnabled(IDC_ZOOM_PLUS, true);
command_updater_.UpdateCommandEnabled(IDC_ZOOM_NORMAL, true);
command_updater_.UpdateCommandEnabled(IDC_ZOOM_MINUS, true);
}
WebKit seems to adjust the zoom level here:
chromium/src/chrome/renderer/render_view.cc
void RenderView::OnZoom(PageZoom::Function function) {
if (!webview()) // Not sure if this can happen, but no harm in being safe.
return;
webview()->hidePopups();
double old_zoom_level = webview()->zoomLevel();
double zoom_level;
if (function == PageZoom::RESET) {
zoom_level = 0;
} else if (static_cast(old_zoom_level) == old_zoom_level) {
// Previous zoom level is a whole number, so just increment/decrement.
zoom_level = old_zoom_level + function;
} else {
// Either the user hit the zoom factor limit and thus the zoom level is now
// not a whole number, or a plugin changed it to a custom value. We want
// to go to the next whole number so that the user can always get back to
// 100% with the keyboard/menu.
if ((old_zoom_level > 1 && function > 0) ||
(old_zoom_level < 1 && function < 0)) {
zoom_level = static_cast(old_zoom_level + function);
} else {
// We're going towards 100%, so first go to the next whole number.
zoom_level = static_cast(old_zoom_level);
}
}
So far I've noticed that the height of the <div> scales properly, but the offsetHeight still reports 1-2px less than the desired minimum height. Oh, here's the first set of patches that implemented full page zoom on WebKit:
https://bug-14998-attachments.webkit.org/attachment.cgi?id=19878
https://bugs.webkit.org/show_bug.cgi?id=14998
Update: The problem possibly related to using integers for zooming/rendering?
https://bugs.webkit.org/show_bug.cgi?id=60318
Array.prototype.slice.call(arguments)
The Array.prototype.slice.call() function is used to "coerce" JavaScript arguments (which are not really arrays) into JavaScript arrays..
I've seen examples such as:
[].slice.call(arguments, 1);
http://www.danwebb.net/2006/11/7/a-low-down-dirty-goblin-of-a-hack
The '1' argument means to slice the arguments list from 1 to the end.
The criticism about this approach is that [] creates a new Array object in memory. It is therefore more preferable to do:
Array.prototype.slice.call(arguments, 1);
Remember that all functions have a call and apply method(). So we're invoking the slice method by using call() in this case:
http://ryanmorr.com/archives/scope-context-in-javascript
I've seen examples such as:
[].slice.call(arguments, 1);
http://www.danwebb.net/2006/11/7/a-low-down-dirty-goblin-of-a-hack
The '1' argument means to slice the arguments list from 1 to the end.
The criticism about this approach is that [] creates a new Array object in memory. It is therefore more preferable to do:
Array.prototype.slice.call(arguments, 1);
Remember that all functions have a call and apply method(). So we're invoking the slice method by using call() in this case:
http://ryanmorr.com/archives/scope-context-in-javascript
call and apply If you want to know how all the mainstream libraries do it, well it’s simple; call and apply. These two very simple methods inherent to all functions allow you to execute any function in any desired context. Before I continue, it is important to know that everything in JavaScript inherently has its own context, including DOM nodes, objects, arrays, strings, functions, etc. Meaning, any function can be executed in the context of any object using call and apply. This may seem insignificant, but understand it is this implementation that serves as an underlying bridge between the object-oriented and functional methodologies in the JavaScript language.
Making the Array-like Objects become Arrays
Well, that heading is a misnomer. If we want those array-like objects to behave like arrays, we are going to need a new array.
view plaincopy to clipboardprint?
var testFunction = function() {
// Create a new array from the contents of arguments
var args = Array.prototype.slice.call(arguments);
var a = args.shift();
console.log("The first argument is: %s", a);
// send the remaining arguments to some other function
someOtherFunction(args);
};
Clearly, the magic is all in Array.prototype.slice.call(arguments). So let me break it down per piece.
Array - this is the name of the base object that we want
prototype -this can be thought of as the namespace for the instance methods of an array
slice - this extracts a section of an array and returns a new array, and without a beginning and ending index, it simply returns a copy of the array
call - this is a very useful function, it allows you to call a function from one object and use it in the context of another
Wednesday, October 6, 2010
Ubuntu Lucid and Thinkpad Sleep
I noticed that when trying to shut off my Thinkpad, the moon light continued to blink but the machine never entered into suspend state.
The problem appears to be related to an SD card mounted inside the card slot. If there is an SD card, then the Thinkpad will not be able to sleep.
The first solution is not to have the SD card. Another solution (untested) is to create a script to umount the SD card when the laptop enters Suspend or Hibernate mode:
http://www.thinkwiki.org/wiki/Installing_Ubuntu_9.10_(Karmic_Koala)_on_a_ThinkPad_T61
The problem appears to be related to an SD card mounted inside the card slot. If there is an SD card, then the Thinkpad will not be able to sleep.
The first solution is not to have the SD card. Another solution (untested) is to create a script to umount the SD card when the laptop enters Suspend or Hibernate mode:
http://www.thinkwiki.org/wiki/Installing_Ubuntu_9.10_(Karmic_Koala)_on_a_ThinkPad_T61
#!/bin/sh
PATH=/sbin:/usr/sbin:/bin:/usr/bin
case "${1}" in
suspend|hibernate)
for i in `ls /media`; do
/usr/bin/gvfs-mount -u "/media/$i"
done
;;
resume|thaw)
# nothing
;;
esac
Sunday, October 3, 2010
Squid/proxy server and .htaccess
sudo apt-get install htpasswd -c /etc/squid/squid_passwd
vi /etc/squid/squid.conf: auth_param basic program /usr/lib/squid/ncsa_auth /etc/squid/squid_passwd acl ncsa_users proxy_auth REQUIRED http_access allow ncsa_users
Go into your web browser and change your proxy settings now. You'll also want to supply the username and password to sign-in to the Squid server.
Wednesday, September 22, 2010
JSONP and cross-domain Ajax calls
I was looking at this demo and trying to figure out how the cross-domain Ajax works:
http://www.prettyklicks.com/demo/fbjson.php
Some background info is here:
http://www.ibm.com/developerworks/library/wa-aj-jsonp1/?ca=dgr-jw64JSONP jQuery&S_TACT=105AGY46&S_CMP=grsitejw64
Here's the nitty/gritty - it boils down to avoid using the XMLHttpRequest object, injecting a <script src> tag that retrieves a JSON-encoded response but wrapped in a callback function, and then creating the callback function in the native browser that then gets invoked.
Both client and server need to be equipped to handle this technique. On the client side, JQuery does all of this magic under the covers with its JSONP implementation. On the server side, the server needs to wrap a JSON data if a callback= parameter is specified.
The JavaScript code has this line:
http://code.google.com/p/django-rest-interface/issues/detail?id=39
So the server will return a result such as the following (note the jsonp1285197073066() function):
This section then creates a function based on the callback name (jsonp):
http://www.prettyklicks.com/demo/fbjson.php
Some background info is here:
http://www.ibm.com/developerworks/library/wa-aj-jsonp1/?ca=dgr-jw64JSONP jQuery&S_TACT=105AGY46&S_CMP=grsitejw64
Here's the nitty/gritty - it boils down to avoid using the XMLHttpRequest object, injecting a <script src> tag that retrieves a JSON-encoded response but wrapped in a callback function, and then creating the callback function in the native browser that then gets invoked.
Both client and server need to be equipped to handle this technique. On the client side, JQuery does all of this magic under the covers with its JSONP implementation. On the server side, the server needs to wrap a JSON data if a callback= parameter is specified.
The JavaScript code has this line:
var url = "http://graph.facebook.com/prettyklicks/feed?limit=5&callback=?";But the actual URL call translates to:
http://graph.facebook.com/prettyklicks/feed?limit=5&callback=jsonp1285193963567It turns out there's a bit of jQuery and server-side magic going on. On the server side, It seems that when this callback= function will wrap a function that can then be executed by the DOM tree, similar to what's being done here:
http://code.google.com/p/django-rest-interface/issues/detail?id=39
So the server will return a result such as the following (note the jsonp1285197073066() function):
jsonp1285197073066({
"data": [
{
"id": "45075726743_434059296743",
"from": {
"name": "Jason John Adams",
"id": "1205845313"
},
"to": {
"data": [
{
"name": "Pretty Klicks",
"category": "Artist",
"id": "45075726743"
}
]
},
.
.
.
});
On the client-side, the JQuery's ajax() function has some checking for whether the Ajax() call actually seems to have a regexp check to see if there is a & or ? appended at the end of the URL call for JSON Ajax calls. It uses this regexp to replace the ? with a generated function name to call.jsre = /=\?(&|$)/,This section seems to rename the callback to ?jsonp=jsc (i.e. jsonp1285193963567).
var jsc = now(),
.
.
// Build temporary JSONP function
if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
jsonp = s.jsonpCallback || ("jsonp" + jsc++);
// Replace the =? sequence both in the query string and the data
if ( s.data ) {
s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
}
This section then creates a function based on the callback name (jsonp):
s.url = s.url.replace(jsre, "=" + jsonp + "$1");
// We need to make sure
// that a JSONP style response is executed properly
s.dataType = "script";
// Handle JSONP-style loading
window[ jsonp ] = window[ jsonp ] || function( tmp ) {
data = tmp;
success();
complete();
// Garbage collect
window[ jsonp ] = undefined;
try {
delete window[ jsonp ];
} catch(e) {}
if ( head ) {
head.removeChild( script );
}
};
}
So what's happening is that jQuery creates a function that will take as an argument tmp, which corresponds to the data from the JSON response. So when the script finishes loading, it will be executed and the Ajax callback is called.function success() {
// If a local callback was specified, fire it and pass it the data
if ( s.success ) {
s.success.call( callbackContext, data, status, xhr );
}
// Fire the global callback
if ( s.global ) {
trigger( "ajaxSuccess", [xhr, s] );
}
}
The last part of the equation is that script tags are inserted in the HEAD document with the src= set to the external URL. By doing is this way, the result will be evaluated by the function that we previously added to the window object.// If we're requesting a remote document
// and trying to load JSON or Script with a GET
if ( s.dataType === "script" && type === "GET" && remote ) {
var head = document.getElementsByTagName("head")[0] || document.documentElement;
var script = document.createElement("script");
script.src = s.url;
if ( s.scriptCharset ) {
script.charset = s.scriptCharset;
}
// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (#2709 and #4378).
head.insertBefore( script, head.firstChild );
// We handle everything using the script element injection
return undefined;
}
This success function can then be used by the $.getJSON call from http://www.prettyklicks.com/demo/fbjson.php to retrieve and process the data:function(json){
var html = "- "; //loop through and within data array's retrieve the message variable. $.each(json.data,function(i,fb){ html += "
- " + fb.message + " "; }); html += "
Friday, September 17, 2010
Finding the encoding of a MySQL table
Inside a MySQL database, you can type the following:
Thanks to https://wincent.com/wiki/Finding_out_the_encoding_of_a_MySQL_database
http://blog.awarelabs.com/2008/django-tips-utf-8-ascii-encoding-errors-urllib2-and-mysql/
Django uses a default character set of UTF-8, so when it connects to the MySQL database, it uses UTF-8. If you use mysql to interface, you will notice that the character_set_results are set to latin1 (otherwise known as ASCII).
sql> show variables like "character_set%"
-> ;
+--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | latin1 | | character_set_connection | latin1 | | character_set_database | utf8 | | character_set_filesystem | binary | | character_set_results | latin1 | | character_set_server | latin1 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | +--------------------------+----------------------------+ 8 rows in set (0.00 sec)
Thanks to https://wincent.com/wiki/Finding_out_the_encoding_of_a_MySQL_database
http://blog.awarelabs.com/2008/django-tips-utf-8-ascii-encoding-errors-urllib2-and-mysql/
Django uses a default character set of UTF-8, so when it connects to the MySQL database, it uses UTF-8. If you use mysql to interface, you will notice that the character_set_results are set to latin1 (otherwise known as ASCII).
Subscribe to:
Comments (Atom)