There is a popular little JavaScript widget called the Hello Bar. You embed it in your website, and it displays a bar, with a line of text at the top of your webpages. Visitors are drawn to it, because of its prominent position.
What follows is a mini series of tutorials, where we’ll make ‘FooBar’ - our own interpretation of the Hello Bar.
Like the Hello Bar, our bar must be easy for site owners across the inter-webs to embed. With this in mind, FooBar should…
Start life as a copy and paste snippet.
Be free of dependencies. We won’t be using jQuery et al.
Load fast.
Be customisable, where possible.
Image free. In the name of leanness, no images will be used.
FooBar will be built with modern web technologies in mind. For this reason it’ll only work on modern browsers. If FooBar encounters IE9 or under it should respectfully do nothing.
Let’s Begin
We’ll start with a self invoking, anonymous function, to protect our code from the global namespace.
123
(function(){}());
Next, an init function that we’ll call when the window fires its onLoad event:
After applying some basic styling to the FooBar (see foobar.css) it’s time to consider how users can customise parts of the bar. Naturally, a user will want to adjust the content of the bar, but also being able to change the bar’s colours without touching the CSS would be handy. By defining a my_foobar object in their webpages, a user will be able to override default settings:
12345678
<script>my_foobar={content:"Hello from my FooBar",text_colour:'white',background_colour:'black',border_colour:'orange'};</script>
We can set our default settings within the FooBar script and then merge it with the user’s my_foobar object. Here’s a function for the job:
1234567891011121314
var extend_options = function(){
// Continue only if my_foobar has been defined as an object
if(typeof my_foobar === "object"){
for (var prop in my_foobar) {
// If the property from my_foobar exists in our default options object,
// then replace the value, with the value in my_foobar.
if (options[prop] !== undefined) {
options[prop] = my_foobar[prop];
}
}
}
};
To acquire these custom style settings, we’ll generate CSS on the fly, appending them to the DOM. It makes sense to do this before injecting FooBar’s markup. We’ll start with a styles function, which returns an object of style rules - it’s syntactical closeness to CSS doesn’t need mentioning. We pass each value to an as of yet undefined function called colour. colour will take three types of colour description and return something we can use in a CSS rule. Look at colour in applicatio.js to see what it does.
We need to now use the styles object as a template for generating the styles rules to be injected into the HTML document:
1234567891011121314151617181920
var injectStyles = function(){
var
// Create the style tag
tag = document.createElement('style'),
// Memo will hold our concatenated CSS string
memo = '';
// Iterate over each property in the styles object
for (element in styles()) {
memo += element + "{";
for( style in styles()[element]){
memo += style + ":" + styles()[element][style] + ';';
}
memo += "}";
}
tag.textContent = memo;
document.body.insertBefore(tag, document.body.childNodes[0]);
};
We’ve covered enough ground for a first part of what will be a series of posts. Next we can consider how best to minify and concatenate FooBar’s content (JavaScript and CSS) into a single file, thus limiting to one, the number of HTTP requests required to get FooBar working on a browser.
A look at Prototypal Inheritance in JavaScript and how we can implement it.
Here we have a JavaScript object, describing a single tweet…
12345678
vartweet={"created_at":"Sun, 05 Aug 2012 10:30:12 +0000","from_user":"NME","id":231185161608376321,"id_str":"231185161608376321","profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/1731598749\/twitter3_normal.jpg","text":"Arctic Monkeys set to score a Top 20 hit with their cover of The Beatles' 'Come Together' http:\/\/t.co\/1GvDZXoc"};
…from which we can extract information:
123456789
tweet.text;// Using dot notation.// Returns Artic Monkeys set to score...// Or...tweet['text'];// Using bracket notation.// Also returns Artic Monkeys set to score...
We can derive new information from the tweet, such as the tweet’s full HTTP address. Here’s a short function for the job:
1234567891011
// function 'where' takes a tweet object and returns a URL string,// constructed from the properties of the passed tweet.varwhere=function(tweet){return"https://twitter.com/"+tweet.from_user+"/status/"+tweet.id_str;};// Invoking the functionwhere(tweet);// Returns https://twitter.com/NME/status/231185161608376321
As it stands, we need to pass our object to the function where, but what if we could ask the object directly, like so:
1
tweet.where();
Not a problem. We just need to make the function a property of the object:
Notice above I’m using JavaScript’s this keyword for accessing a_tweets properties.
Working with Multiple Objects
What if we have an array of tweets and we want each object to respond to where?
123456789
// Loop through array of tweets, assigning copy of the// 'where' function to each tweet object.for(varx,length=tweets.length;x<length;x++){tweets[x].where=function(){return"https://twitter.com/"+this.user_name+"/status/"+this.tweet_id;}}tweets[0].where();
Assigning a copy of the ‘where’ function to every tweet isn’t a very efficient use of memory or code. What if we could have one instance of the where function and have all the tweet objects access that instance. This is where Prototypal Inheritance steps forward.
Prototypal Inheritance
Any JavaScript object can have a pointer to an object called its prototype. Multiple objects can all have the same prototype. Crucially, objects inherit properties from their prototypes:
1234567891011121314151617
// Here's an empty object created using Object constructor's 'create' method:varemptyObject=Object.create(null);// When creating new objects with this method, you can set the object's prototype // by passing the aspiring prototype object:// Elixir is a regular JavaScript object, created using object literal notation. It will act as our prototype.varelixir={'immortal':true};// Create a new object passing elixirvarmortalObject=Object.create(elixir);mortalObject.immortal;// Returns true!
mortalObject is inheriting immortality from its prototype object (the elixir object).
Other objects can drink from the elixir of life too:
12345678910111213141516
varordinaryObject=Object.create(elixir);ordinaryObject.immortal;// Returns true// What if the elixir runs dry?elixir.immortal=false;// Now all objects inheriting from elixir have lost their immortality:ordinaryObject.immortal;// Returns falsemortalObject.immortal;// Returns false
With that very brief tour of JavaScript prototypes, we can return to our tweets example. Using prototypal inheritance, we’ll endow the tweets with the ‘where’ method:
cortex will be the prototype object our tweet objects will inherit from:
12345678910111213141516171819202122
// Helper function takes a regular tweet objectfunctiongenerateSmartTweet(tweet){varobj=Object.create(cortex)obj.id=tweet.id_str;obj.text=tweet.text;obj.username=tweet.from_user;returnobj;}// Container for our smart tweetsvarsmart_tweets=[];// Iterate over our array of tweets passing each tweet to generateSmartTweet,// pushing the returned 'smart' tweet to our smart tweets array.for(varx,varlength=tweets.length;x<length;x++){smart_tweets.push(generateSmartTweet(tweets[x]));}smart_tweets[0].where();// Returns https://twitter.com/NME/status/231185161608376321
All tweets whose prototype object is the cortex will immediately have access to any new methods we assign to the cortex. Perhaps we’d like to know how long ago in words the tweet was tweeted? I won’t re-invent the wheel for this method - below is some code borrowed from John Resig (of jQuery fame):
// I'll pop this function into another, more generic object (`Helper`). // It has general appeal, and could be used by other code.varHelper={};Helper.timeAgoInWords=function(time){vardate=newDate((time||"").replace(/-/g,"/").replace(/[TZ]/g," ")),diff=(((newDate()).getTime()-date.getTime())/1000),day_diff=Math.floor(diff/86400);if(isNaN(day_diff)||day_diff<0||day_diff>=31)return;returnday_diff==0&&(diff<60&&"just now"||diff<120&&"1 minute ago"||diff<3600&&Math.floor(diff/60)+" minutes ago"||diff<7200&&"1 hour ago"||diff<86400&&Math.floor(diff/3600)+" hours ago")||day_diff==1&&"Yesterday"||day_diff<7&&day_diff+" days ago"||day_diff<31&&Math.ceil(day_diff/7)+" weeks ago";}}};// Back to the cortex...varcortex={where:function(){return"https://twitter.com/"+this.user_name+"/status/"+this.tweet_id;},when:function(){returnHelper.timeAgoInWords(this.created_at);}};
It would also be handy if we could have the tweet text ‘linkified’ for us:
And with that, we have an example (with some practical value) of how JavaScript’s prototypal inheritance allows objects to easily inherit from other, more general objects.
JSON is fantastic stuff. Learn to love it, if you want to reach Web Dev Nirvana.
As an aspiring Web App developer, it’s important you get down and dirty with JSON.
What is JSON?
Simply, JSON is a data format. It’s a standardised way of formatting structured information. When you query almost any API today, it’s going to format its response in JSON - copy and paste this query into your browser to see what I mean:
http://search.twitter.com/search.json?q=monkey
Twitter’s response is a screen full of JSON formatted tweets about monkeys. Notice how JSON is very readable - it is actually based on a subset of JavaScript. If you’re familiar with JavaScript objects, then you’re going to find JSON a breeze.
What follows is a simple demonstration of what JSON looks like and how we can can work with it in a web environment.
Fire up your JavaScript console, and follow along:
123
// Here's a JSON object of someone's name:'{"firstname": "Tim", "surname" : "Berners-Lee"}'
With JavaScript, let’s try and extract some information out of it.
12345678910111213141516171819202122232425
varobj='{"firstname": "Tim", "surname" : "Berners-Lee"}';typeofobj;>>"string"// We can't get much out of a string, without complicated RegExp pattern matching. // JSON.parse to the rescue...obj=JSON.parse(obj);typeofobj;>>"object"// Nice. We now have a JavaScript object!obj.firstname;>>"Tim"// OK. Let's write a simple function which takes the above object and returns a full name:functionfullName(person){returnperson.firstname+' '+person.surname;}fullName(obj);>>"Tim Berners-Lee"
Often, when working with JSON responses from APIs, you’ll get an array of objects. Here’s an example from Twitter’s search API (the response has been heavily trimmed, for brevity’s sake).
{"completed_in":0.125,"max_id_str":"229283820036304896","next_page":"?page=2&max_id=229283820036304896&q=blue%20angels&rpp=5&include_entities=1&result_type=mixed","page":1,"query":"blue+angels","results":[{"created_at":"Sat, 28 Jul 2012 18:34:57 +0000","from_user":"msinfoblog","from_user_name":"Kelley Wilson","profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/2287764130\/2tmsb7eyhs9v7qkvdbps_normal.png","text":"On our way to the blue Angels air museum http:\/\/t.co\/FJFDRUG1"},{"created_at":"Sat, 28 Jul 2012 18:29:22 +0000","from_user":"papawheelee","from_user_name":"Jeremy Wheelock","profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/2317606886\/1339975449024_normal.jpg","text":"They look fast parked. @Brad202b: Blue angels http:\/\/t.co\/aA4VCL2s"},{"created_at":"Sat, 28 Jul 2012 18:26:50 +0000","from_user":"preciousalexan2","from_user_name":"precious alexander","profile_image_url":"http:\/\/a0.twimg.com\/sticky\/default_profile_images\/default_profile_0_normal.png","text":"RT @ShamraStar: My Angels gave me pink purple & blue kisses! @starquality @pinkyinthebrain @my_dollhouse \ud83d\ude18 http:\/\/t.co\/oC5OVNh9"}]}
See just how readable JSON is? Notice the results property 6 lines down, this is where all the action is at. The property’s value is an array of tweets. Let’s programmatically do something with that data…
1234567891011121314151617181920212223242526272829
// JSON segment from above, duplicated below as a string, // on one line so you can copy and paste into your browser's JavaScript console.vardata='{"completed_in":0.125,"max_id_str":"229283820036304896","next_page":"?page=2&max_id=229283820036304896&q=blue%20angels&rpp=5&include_entities=1&result_type=mixed","page":1,"query":"blue+angels","results":[{"created_at":"Sat, 28 Jul 2012 18:34:57 +0000","from_user":"msinfoblog","from_user_name":"Kelley Wilson","profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/2287764130\/2tmsb7eyhs9v7qkvdbps_normal.png","text":"On our way to the blue Angels air museum http:\/\/t.co\/FJFDRUG1"},{"created_at":"Sat, 28 Jul 2012 18:29:22 +0000","from_user":"papawheelee","from_user_name":"Jeremy Wheelock","profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/2317606886\/1339975449024_normal.jpg","text":"They look fast parked. @Brad202b: Blue angels http:\/\/t.co\/aA4VCL2s"},{"created_at":"Sat, 28 Jul 2012 18:26:50 +0000","from_user":"preciousalexan2","from_user_name":"precious alexander","profile_image_url":"http:\/\/a0.twimg.com\/sticky\/default_profile_images\/default_profile_0_normal.png","text":"RT @ShamraStar: My Angels gave me pink purple & blue kisses! @starquality @pinkyinthebrain @my_dollhouse \ud83d\ude18 http:\/\/t.co\/oC5OVNh9"}]}'typeofdata;// "string"data=JSON.parse(data);typeofdata// "object"data.results;// [> Object, > Object, > Object]data.results[0].text;// "On our way to the blue Angels air museum http://t.co/FJFDRUG1"functionshowTweets(tweets){for(vari=0;i<tweets.length;i++){console.log(i+1+'.',tweets[i].text,'by',tweets[i].from_user_name);};}showTweets(data.results);// 1. On our way to the blue Angels air museum http://t.co/FJFDRUG1 by Kelley Wilson// 2. They look fast parked. @Brad202b: Blue angels http://t.co/aA4VCL2s by Jeremy Wheelock// 3. RT @ShamraStar: My Angels gave me pink purple & blue kisses! @starquality @pinkyinthebrain @my_dollhouse http://t.co/oC5OVNh9 by precious alexander
I’ll post some short tutorials over the coming weeks, exploring further the significance of JSON in Web App Development. In the mean time, have a peep at the following:
This post explains JSON-P, a widely used technique for circumnavigating the web browsers security block on cross domain Ajax requests.
Web browsers support XMLHttpRequest’s (Ajax) API for easily retrieving and posting data without reloading the document. The XMLHttpRequest JavaScript object, however, does not support cross-domain requests due to security restrictions imposed by the browser.
So, if I make an Ajax request to Twitter’s Search API, I’ll be communicating with another domain, and so can expect a security error. Try the following in a JavaScript console on your favourite browser. I’m using Chrome.
1234567
<scriptsrc='http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js'></script>// Using jQuery's Ajax APIvarresource='//search.twitter.com/search.json'$.get(resource,{q:'chimpanzee'})>XMLHttpRequestcannotloadhttps://search.twitter.com/search.json?q=chimpanzee. Origin https://twitter.com is not allowed by Access-Control-Allow-Origin.
We get an XMLHttpRequest cannot load error, so no surprises there.
I’m sure you’re familiar with Twitter’s JavaScript widget which one can embed directly into a website. The widget queries Twitter’s Search API and displays the results on the page the script is loaded from. But how? Why doesn’t the script get the XMLHttpRequest cannot load we saw above?
This is how: the widget dynamically injects a script tag into the web document. script tags are permitted by browsers to load scripts hosted on other domains. So, by dynamically injecting a script tag after your web page has loaded, Twitter’s widget can load data into your web document (originating from Twitter’s domain) without
having to reload your web page.
Let’s give it a try. In the same directory create two documents - index.html:
123456789101112131415161718192021222324
<!DOCTYPE html><html><head></head><body><script>functionget(url){// Create script tagvarscriptTag=document.createElement('script');scriptTag.src=url;// Inject script tag into the head of the web documentdocument.getElementsByTagName('head')[0].appendChild(scriptTag);}// Call getget('data.json');</script></body></html>
…and data.json:
123456
{animals:['cat','dog','mouse','koala']}
Notice the get function in index.html. This basic function, when called, dynamically generates and inserts a script tag into the loaded web document, setting src to the URL passed.
Load index.html in your browser. You’ll get a blank window, but reveal the Network panel and you’ll see that data.json was successfully loaded into our document, despite the script tag being inserted after the web page had loaded.
Browsers immediately run code delivered to the document via a script tag. In our example above, we’re just returning data - there are no statements to be executed, that’s why nothing happens.
The obvious question now is how then do we get our hands on that returned data - currently it’s vanishing into the ether…
Well, make the following edit to data.json, and refresh index.html with the console open:
12345678
process({animals:['cat','dog','mouse','koala']});
Notice the error displayed in the console:
1
UncaughtReferenceError:processisnotdefined
This is a good error to get. It means code is being executed - the function process is being invoked. Let’s define process:
12345678910111213141516171819202122
<!--index.html-->functionget(url){varscriptTag=document.createElement('script');scriptTag.src=url;document.getElementsByTagName('head')[0].appendChild(scriptTag);}functionprocess(data){varbody=document.getElementsByTagName('body')[0];varh1;// Iterate over the array of animals, appending each// as a h1 heading, to the document's bodyfor(vari=data.animals.length-1;i>=0;i--){h1=document.createElement('h1');h1.textContent=data.animals[i];body.appendChild(h1);}}get('data.json');
Refresh the browser and you should see a list of animals displayed to the window.
This technique of passing JSON encoded data (which has been requested via a script tag) as an argument to a function is know as JSON-P. How do we get this technique to work with JSON-P savvy API’s in the wild?
Returning to Twitter’s Search API, take this valid API request and paste it directly into your browser:
You’ll see, as a response, a page full of JSON encoded data. Twitter’s Search API is JSON-P enabled, so, with that in mind, add one additional query parameter (callback) to the URL and reload:
Notice how Twitter’s response has changed? It’s now doing exactly what we did in our data.json file. It’s calling process, passing as its only argument the JSON encoded data. Change the value of callback and the name of the invoked function changes accordingly.
Let’s update our process function to work with Twitter’s JSON response:
Twitter’s API response should now appear on the screen as a list of tweets. We have successfully made a cross-domain request, handling the API’s response.
Taking this knowledge with you on future projects, keep in mind not all APIs support JSON-P. Review the API’s documentation, or try adding a callback parameter and look at the APIs response.
I’ll leave you with an improved version of the get function from before:
varjsonpID=0;functionJSONP(url,callback){// create script tagvarscript=d.createElement('script');// Dynamically generate a callback function name.// Make the name unique so there's no chance we// override another function of the same name. By// attaching the counter jsonpID to the name we can make// invoke JSONP multiple times, each time with a different// function namevarcallbackName='_JSONP_callback__'+(++jsonpID)// replace question mark with callbackNamescript.src=url.replace(/=\?/,'='+callbackName)// append script to document's headdocument.getElementsByTagName('head')[0].appendChild(script)// create and assign the callback functionwindow[callbackName]=function(data){deletewindow[callbackName]// remove script element from documentscript.parentElement.removeChild(script)// call the passed function passing datacallback(data);}}
This post follows on from last week’s. I’ll be implementing pagination.
A working version of this app is located here, and you can view the code at github.
I quite like google-image’s solution to this problem, which is to have a single ‘Show more results’ button at the bottom of the page.
Instagram’s API makes pagination a doddle. There’s nothing in the API’s documentation about pagination, but in the API’s response there is an object property called pagination:
12345678
"pagination":{"next_max_tag_id":"1335465136530","deprecation_warning":"next_max_id and min_id are deprecated for this endpoint; use min_tag_id and max_tag_id instead","next_max_id":"1335465136530","next_min_id":"1335465556125","min_tag_id":"1335465556125","next_url":"https:\/\/api.instagram.com\/v1\/tags\/cats\/media\/recent?callback=jQuery17201362594123929739_1335440328560&_=133544032857&client_id=xxx&max_tag_id=1335465136530"}
Within the object there’s a property called next_url, which conveniently contains a prepared URL for accessing the next page of photos. Studying the URL you’ll notice there’s an additional parameter - max_tag_id, which can also be retrieved from the pagination object as next_max_id.
The plan then is to display the first page of photos, with a ‘View More’ button at the bottom of the page. The button will store the max_tag_id as a data attribute. When clicked, a handler will pass the max_tag_id as a query parameter in a request to Instagram’s API.
Calling generateResource with a search string like ‘cats’ would return another function, which when invoked would then return the URL with ‘cats’ passed in the query string.
12345
> var resource= generateResource('cats');
undefined
> resource();
"https://api.instagram.com/v1/tags/cats/media/recent?callback=?&client_id=xxx"
I can invoke resource at anytime. If and when the user wants to see the next page of photos I need only invoke resource, this time passing the max ID as a parameter. The returned string is the same URL as before, with ‘cats’ as the tag, but now also with the max_id query parameter slapped on to the end.
12345678
> var resource= generateResource('cats');
undefined
> resource();
"https://api.instagram.com/v1/tags/cats/media/recent?callback=?&client_id=xxx"> resource('12345');
"https://api.instagram.com/v1/tags/cats/media/recent?callback=?&client_id=xxx&max_id=12345"
The search function has changed to accommodate this new way of generating the query URL. search simply prepares the resource URL by invoking generateResource with the tag. The actual GET request to the API is delegated to a new function, fetchPhotos.
The toScreen function which previously appended photos to the screen, now also updates the ‘view more’ button with the max_id for the next page of photos:
12345678910111213
functiontoScreen(photos){varphotos_html='';// Update data-max-tag-id with the next_max_id.$('.paginate a').attr('data-max-tag-id',photos.pagination.next_max_id).fadeIn();$.each(photos.data,function(index,photo){photos_html+=toTemplate(photo);});$('div#photos-wrap').append(photos_html);}