11 Jul 2012
This is from Tilo Mitra’s blog post where he featured this comment below which was in response to another blog post. He calls it crazy world of code.
I just have to say I couldn’t agree more:
I agree, I can’t keep up. I just finished learning backbone.js and now I’m found out on that it’s old news, and I should use ember.js, cross that, it has opinions, I should use Meteor, no, AngularJS, no, Tower.js (on node.js), and for html templates I need handlebars, no mustache, wait, DoT.js is better, hang on, why do I need an HTML parser inside the browser? isn’t that what the browser for? so no HTML templates? ok, DOM snippets, fine, Web Components you say? W3C are in the game too? you mean write REGULAR JavaScript like the Google guys? yuck, oh, I just should write it with CofeeScript and it will look ok, not Coffee? Coco? LiveScript? DART? GWT? ok, let me just go back to Ruby on Rails, oh it doesn’t scale? Grails? Groovy? Roo? too “Springy?” ok, what about node.js? doesn’t scale either?? but I can write client side, server side and mongodb side code in the same language? (but does it have to be JavaScript?) ok, what about PHP, you say it’s not really thread safe? they lie?? ok, let me go back to server coding, it’s still Java right? no? Lisp? oh it’s called Clojure? well, it has a Bridge / protocol buffers / thrift implementation so we can be language agnostic, so we can support our Haskell developers. Or just go with Scala/Lift/Play it’s the BEST framework (Foresquare use it, so it has to be good). of course we won’t do SOAP and will use only JSON RESTful services cause it’s only for banks and Walmart, and god forbid to use a SQL database it will never scale
07 Jun 2012
Facebook killed their publicly available RSS feed for Facebook Pages couple of years back in favor of their walled garden approach (GraphAPI). For example, if you go to http://graph.facebook.com/Nike/feed it will ask you for a valid access token which you can get obtain via OAuth Authorization which entails quite a few steps. If you just wanted to display/parse any Facebook Page feed without going through those hoops you are out of luck!
I found myself in a similar situation recently and after some Googling I was able to figure out a workaround. Here’s how it goes:
1) Go to https://developers.facebook.com/tools/explorer
2) Put the username/vanity url of the Facebook Page in question in the request bar and hit Submit. For example, if you wanted to get a feed for: https://www.facebook.com/nike you would put “nike” without quotes in the request bar and click on Submit. (Make sure GET request is selected in the drop-down)
3) Grab the id from the results below and paste it in this URL: https://www.facebook.com/feeds/page.php?id=[FACEBOOK-ID-GOES-HERE]&format=[rss20|json]
So for example, if we wanted JSON feed for Nike’s Facebook page, we’d go to: https://www.facebook.com/feeds/page.php?id=15087023444&format=json
BONUS:
You can do the same with Twitter (officially supported):
http://api.twitter.com/1/statuses/user_timeline.[rss|json]?screen_name=[USERNAME-GOES-HERE]&include_rts=[1|0]
So for example, if you wanted to get JSON feed for Nike’s Twitter, you’d go to:
http://api.twitter.com/1/statuses/user_timeline.json?screen_name=nike&include_rts=1
Happy Hacking!
13 Jul 2011
Recently I was faced with the task of validating a model with a HABTM relationship. I tried looking around for various solutions but all of them seemed quite complicated and time consuming to implement until I came across this nifty solution. Lets say you have two models - posts and tags with a HABTM relationship.
You probably already have the relationship defined in your model, if not, it goes something like this:
models/post.php:
var $hasAndBelongsToMany = array(
'Tag' => array(
'className' => 'Tag',
'joinTable' => 'tags_posts',
'foreignKey' => 'post_id',
'associationForeignKey' => 'tag_id',
'with' => 'TagsPost',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
Now in addition to that, just go ahead and add multiple validation rule in your post model:
var $validate = array(
'Tag' => array(
'multiple' => array(
'rule' => array('multiple',array('min' => 2)),
'message' => 'Please select at least 2 tags'),
),
);
And last but not least add a beforeValidate filter …
function beforeValidate() {
foreach($this->hasAndBelongsToMany as $k=>$v) {
if(isset($this->data[$k][$k]))
{
$this->data[$this->alias][$k] = $this->data[$k][$k];
}
}
}
All you have to do now is add the following snippet in your main model’s controller:
controllers/posts_controller.php:
<?php
class PostsController extends Controller {
function beforeRender()
{
$model = Inflector::singularize($this->name);
foreach($this->{$model}->hasAndBelongsToMany as $k=>$v) {
if(isset($this->{$model}->validationErrors[$k]))
{
$this->{$model}->{$k}->validationErrors[$k] = $this->{$model}->validationErrors[$k];
}
}
}
}
?>
That will check for the errors raised in the main model’s validationErrors and copies them back to the HABTM’s model so that the view can correctly capture and print that validation error.
That’s it!
20 Jan 2011
This post simply demonstrates how to parse Twitter and Facebook feeds using jQuery. I’m utilizing a JavaScript equivalent of C# prototype methods which I had talked about in one of my earlier posts. Also, you’ll note that I’m using two different date/time jQuery plugins to make Twitter and Facebook date parsing a little easier. One of them is timeago and the other one is dateFormat. Although I had to put a condition to check if its IE as the dateFormat function was not playing nice with IE. Rest everything is pretty straight forward.
$(function () {
$.ajax({
url: 'http://api.twitter.com/1/statuses/user_timeline.json?screen_name=jesalg&trim_user=1&count=5&include_rts=1&callback=?',
dataType: 'json',
success: displayTwitterFeed
});
$.ajax({
url: 'https://graph.facebook.com/calistolabs/feed?limit=5&access_token=XXXXXXXXXXXX|XXXXXXXXXXXXX|XXXXXXXXXXXXXXX&callback=?', //Replace with your own access token
dataType: 'json',
success: displayFacebookFeed
});
});
function displayTwitterFeed(result) {
var outputTemplate = "<p>{0}<br /> <small><a href=\"{1}\">{2} via {3}</a></small></p>";
$.each(result, function (i, item) {
var tweet = item.text.parseURL().parseUsername().parseHashtag();
var createdAt = $.browser.msie ? item.created_at : $.timeago(dateFormat(item.created_at, "isoUtcDateTime"));
var source = item.source;
var tweetURL = "http://twitter.com/CalistoLabs/status/" + item.id_str;
$("#TwitterFeed").append(outputTemplate.format(tweet, tweetURL, createdAt, source));
});
}
function displayFacebookFeed(result) {
var outputTemplate = "<p><strong><a href=\"{0}\">{1}</a></strong> {2}<br /><small>{3}</small></p>";
$.each(result.data, function (i, item) {
var username = item.from.name;
var pageURL = "http://www.facebook.com/" + item.from.name.replace(/ /g, '');
var date = $.browser.msie ? item.created_time : dateFormat(item.created_time.replace("+0000", ""), "dddd, mmmm dS, yyyy 'at' h:MM TT");
var body = item.message;
if (!body) {
body = "<a href='" + item.link + "'>" + item.name + "</a><br/>" + item.description;
}
$("#FacebookFeed").append(outputTemplate.format(pageURL, username, body, date));
});
}
String.prototype.parseURL = function () {
return this.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+/g, function (url) {
return url.link(url);
});
};
String.prototype.parseUsername = function () {
return this.replace(/[@]+[A-Za-z0-9-_]+/g, function (u) {
var username = u.replace("@", "")
return u.link("http://twitter.com/" + username);
});
};
String.prototype.parseHashtag = function () {
return this.replace(/[#]+[A-Za-z0-9-_]+/g, function (t) {
var tag = t.replace("#", "%23")
return t.link("http://search.twitter.com/search?q=" + tag);
});
};
String.prototype.format = function () {
var s = this,
i = arguments.length;
while (i--) {
s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
}
return s;
};
Edit:
As of Friday June 3rd Facebook Graph API now requires a valid app or user access_token to access feed data: https://developers.facebook.com/blog/post/509/ You can get your access_token from from the Graph API Explorer and append it to your URL as a query string parameter as shown above.
Edit:
Found a workaround which would let us bypass the GraphAPI. See more details in my latest post. Basically instead of using a URL like this:
https://graph.facebook.com/calistolabs/feed?limit=5&access_token=XXXXXXXXXXXX|XXXXXXXXXXXXX|XXXXXXXXXXXXXXX&callback=?
we’ll switch it out for:
https://www.facebook.com/feeds/page.php?id=161821885215&format=json
21 Dec 2010
Recently I encountered a situation where I had to communicate between an iframe located on a different domain and its parent. Due to the “same origin policy”, a security concept for browsers, which only permits scripts running on pages originating from the same site, this was not possible to do right out of the box. After some Googling, I learned there were a couple of workarounds for it. You could use dynamic script tags included from external domains aka JSONP or use postMessage or the IFrame URL technique. All of these solutions are quite hackish or complicated and not 100% secure. Luckily I found this nice little library called easyXDM.
At the core easyXDM provides a transport stack capable of passing string based messages between two windows, a consumer (the main document) and a provider (a document included using an iframe). It does this by using one of several available techniques, always selecting the most efficient one for the current browser. For all implementations the transport stack offers bi-directionality, reliability, queueing and sender-verification.
Here’s a small test I created. It calls a function to scroll the page up from the remote site. Local and remote directories represent the servers where the files will be residing on.
local/methods.html
<!doctype html>
<html>
<head>
<title>easyXDM</title>
<script type="text/javascript" src="easyXDM.js">
</script>
<script type="text/javascript">
var REMOTE = (function(){
var remote = location.href;
switch (location.host) {
case "jesal.us":
location.href = remote.replace("provider", "consumer");
break;
case "calistolabs.com":
remote = remote.replace("calistolabs.com", "jesal.us");
break;
}
return remote.substring(0, remote.lastIndexOf("/"));
}());
var remote = new easyXDM.Rpc(/** The channel configuration */{
/**
* Register the url to hash.html, this must be an absolute path
* or a path relative to the root.
* @field
*/
local: "name.html",
/**
* Register the url to the remote interface
* @field
*/
remote: REMOTE + "/../remote/remotemethods.html",
remoteHelper: REMOTE + "/../remote/name.html",
/**
* Register the DOMElement that the generated IFrame should be inserted into
*/
container: "embedded",
props: {
style: {
border: "2px dotted red",
height: "1200px"
}
}
}, /** The interface configuration */ {
local: {
triggerScrollUp: function(){
scroll(0,0);
}
}
});
</script>
<style type="text/css">
#embedded iframe {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<script type="text/javascript">
document.write("Domain: " + location.host);
</script>
<br/>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div id="embedded">
</div>
</body>
</html>
remote/remotemethods.html
<!doctype html>
<html>
<head>
<title>easyXDM</title>
<script type="text/javascript" src="easyXDM.js">
</script>
<script type="text/javascript">
var remote = new easyXDM.Rpc(/** The channel configuration*/{
local: "name.html",
onReady: function(){
/**
* Call a method on the other side
*/
remote.triggerScrollUp();
}
}, /** The configuration */ {
remote: {
triggerScrollUp: {}
}
});
</script>
</head>
<body>
<script type="text/javascript">
document.write("Domain: " + location.host);
</script>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<input type="button" onclick="remote.triggerScrollUp();" value="Call to triggerScrollUp on mother ship"/>
</body>
</html>
remote/name.html
<!doctype html>
<html>
<head>
<title></title>
<meta http-equiv="CACHE-CONTROL" content="PUBLIC"/>
<meta http-equiv="EXPIRES" content="Sat, 01 Jan 2050 00:00:00 GMT"/>
</head>
<body>
<script type="text/javascript">
function sendMessage(message, url){
window.setTimeout(function(){
window.name = message;
location.href = url + "," + encodeURIComponent(location.protocol + "//" + location.host + location.pathname);
}, 0);
}
if (location.hash) {
if (location.hash.substring(1, 2) === "_") {
var channel, url, hash = location.href.substring(location.href.indexOf("#") + 3), indexOf = hash.indexOf(",");
if (indexOf == -1) {
channel = hash;
}
else {
channel = hash.substring(0, indexOf);
url = decodeURIComponent(hash.substring(indexOf + 1));
}
switch (location.hash.substring(2, 3)) {
case "2":
// NameTransport local
window.parent.parent.easyXDM.Fn.get(channel)(window.name);
location.href = url + "#_4" + channel + ",";
break;
case "3":
// NameTransport remote
var guest = window.parent.frames["easyXDM_" + channel + "_provider"];
if (!guest) {
throw new Error("unable to reference window");
}
guest.easyXDM.Fn.get(channel)(window.name);
location.href = url + "#_4" + channel + ",";
break;
case "4":
// NameTransport idle
var fn = window.parent.easyXDM.Fn.get(channel + "_load");
if (fn) {
fn();
}
break;
}
}
}
</script>
</body>
</html>
You can find lot more examples and documentation on easyXDM’s website.