{"rowid": 139, "title": "Flickr Photos On Demand with getFlickr", "contents": "In case you don\u2019t know it yet, Flickr is great. It is a lot of fun to upload, tag and caption photos and it is really handy to get a vast network of contacts through it. \n\nUsing Flickr photos outside of it is a bit of a problem though. There is a Flickr API, and you can get almost every page as an RSS feed, but in general it is a bit tricky to use Flickr photos inside your blog posts or web sites. You might not want to get into the whole API game or use a server side proxy script as you cannot retrieve RSS with Ajax because of the cross-domain security settings.\n\nHowever, Flickr also provides an undocumented JSON output, that can be used to hack your own solutions in JavaScript without having to use a server side script.\n\n\n\tIf you enter the URL http://flickr.com/photos/tags/panda you get to the flickr page with photos tagged \u201cpanda\u201d.\n\tIf you enter the URL http://api.flickr.com/services/feeds/photos_public.gne?tags=panda&format=rss_200 you get the same page as an RSS feed.\n\tIf you enter the URL http://api.flickr.com/services/feeds/photos_public.gne?tags=panda&format=json you get a JavaScript function called jsonFlickrFeed with a parameter that contains the same data in JSON format\n\n\nYou can use this to easily hack together your own output by just providing a function with the same name. I wanted to make it easier for you, which is why I created the helper getFlickr for you to download and use.\n\ngetFlickr for Non-Scripters\n\nSimply include the javascript file getflickr.js and the style getflickr.css in the head of your document:\n\n\n\n\nOnce this is done you can add links to Flickr pages anywhere in your document, and when you give them the CSS class getflickrphotos they get turned into gallery links. When a visitor clicks these links they turn into loading messages and show a \u201cpopup\u201d gallery with the connected photos once they were loaded. As the JSON returned is very small it won\u2019t take long. You can close the gallery, or click any of the thumbnails to view a photo. Clicking the photo makes it disappear and go back to the thumbnails.\n\nCheck out the example page and click the different gallery links to see the results.\n\nNotice that getFlickr works with Unobtrusive JavaScript as when scripting is disabled the links still get to the photos on Flickr.\n\ngetFlickr for JavaScript Hackers\n\nIf you want to use getFlickr with your own JavaScripts you can use its main method leech():\n\ngetFlickr.leech(sTag, sCallback);\n\n \n\tsTag\n\tthe tag you are looking for\n\tsCallback\n\tan optional function to call when the data was retrieved.\n \n\nAfter you called the leech() method you have two strings to use:\n\n \n\tgetFlickr.html[sTag]\n\tcontains an HTML list (without the outer UL element) of all the images linked to the correct pages at flickr. The images are the medium size, you can easily change that by replacing _m.jpg with _s.jpg for thumbnails.\n\tgetFlickr.tags[sTag]\n\tcontains a string of all the other tags flickr users added with the tag you searched for(space separated)\n \n\nYou can call getFlickr.leech() several times when the page has loaded to cache several result feeds before the page gets loaded. This\u2019ll make the photos quicker for the end user to show up. If you want to offer a form for people to search for flickr photos and display them immediately you can use the following HTML:\n\n
\n\nAll the JavaScript you\u2019ll need (for a basic display) is this:\n\nfunction populate(){\n var tag = document.getElementById('tag').value;\n document.getElementById('photos').innerHTML = getFlickr.html[tag].replace(/_m\\.jpg/g,'_s.jpg');\n document.getElementById('tags').innerHTML = getFlickr.tags[tag];\n return false;\n}\n\nEasy as pie, enjoy!\n\nCheck out the example page and try the form to see the results.", "year": "2006", "author": "Christian Heilmann", "author_slug": "chrisheilmann", "published": "2006-12-03T00:00:00+00:00", "url": "https://24ways.org/2006/flickr-photos-on-demand/", "topic": "code"} {"rowid": 161, "title": "Keeping JavaScript Dependencies At Bay", "contents": "As we are writing more and more complex JavaScript applications we run into issues that have hitherto (god I love that word) not been an issue. The first decision we have to make is what to do when planning our app: one big massive JS file or a lot of smaller, specialised files separated by task. \n\nPersonally, I tend to favour the latter, mainly because it allows you to work on components in parallel with other developers without lots of clashes in your version control. It also means that your application will be more lightweight as you only include components on demand.\n\nStarting with a global object\n\nThis is why it is a good plan to start your app with one single object that also becomes the namespace for the whole application, say for example myAwesomeApp:\n\nvar myAwesomeApp = {};\n\nYou can nest any necessary components into this one and also make sure that you check for dependencies like DOM support right up front.\n\nAdding the components\n\nThe other thing to add to this main object is a components object, which defines all the components that are there and their file names.\n\nvar myAwesomeApp = {\n\tcomponents :{\n\t\tformcheck:{\n\t\t\turl:'formcheck.js',\n\t\t\tloaded:false\n\t\t},\n\t\tdynamicnav:{\n\t\t\turl:'dynamicnav.js',\n\t\t\tloaded:false\n\t\t},\n\t\tgallery:{\n\t\t\turl:'gallery.js',\n\t\t\tloaded:false\n\t\t},\n\t\tlightbox:{\n\t\t\turl:'lightbox.js',\n\t\t\tloaded:false\n\t\t}\n\t}\n};\n\nTechnically you can also omit the loaded properties, but it is cleaner this way. The next thing to add is an addComponent function that can load your components on demand by adding new SCRIPT elements to the head of the documents when they are needed.\n\nvar myAwesomeApp = {\n\tcomponents :{\n\t\tformcheck:{\n\t\t\turl:'formcheck.js',\n\t\t\tloaded:false\n\t\t},\n\t\tdynamicnav:{\n\t\t\turl:'dynamicnav.js',\n\t\t\tloaded:false\n\t\t},\n\t\tgallery:{\n\t\t\turl:'gallery.js',\n\t\t\tloaded:false\n\t\t},\n\t\tlightbox:{\n\t\t\turl:'lightbox.js',\n\t\t\tloaded:false\n\t\t}\n\t},\n\taddComponent:function(component){\n\t\tvar c = this.components[component];\n\t\tif(c && c.loaded === false){\n\t\t\tvar s = document.createElement('script');\n\t\t\ts.setAttribute('type', 'text/javascript');\n\t\t\ts.setAttribute('src',c.url);\n\t\t\tdocument.getElementsByTagName('head')[0].appendChild(s);\n\t\t}\n\t}\n};\n\nThis allows you to add new components on the fly when they are not defined:\n\nif(!myAwesomeApp.components.gallery.loaded){\n\tmyAwesomeApp.addComponent('gallery');\n};\n\nVerifying that components have been loaded\n\nHowever, this is not safe as the file might not be available. To make the dynamic adding of components safer each of the components should have a callback at the end of them that notifies the main object that they indeed have been loaded:\n\nvar myAwesomeApp = {\n\tcomponents :{\n\t\tformcheck:{\n\t\t\turl:'formcheck.js',\n\t\t\tloaded:false\n\t\t},\n\t\tdynamicnav:{\n\t\t\turl:'dynamicnav.js',\n\t\t\tloaded:false\n\t\t},\n\t\tgallery:{\n\t\t\turl:'gallery.js',\n\t\t\tloaded:false\n\t\t},\n\t\tlightbox:{\n\t\t\turl:'lightbox.js',\n\t\t\tloaded:false\n\t\t}\n\t},\n\taddComponent:function(component){\n\t\tvar c = this.components[component];\n\t\tif(c && c.loaded === false){\n\t\t\tvar s = document.createElement('script');\n\t\t\ts.setAttribute('type', 'text/javascript');\n\t\t\ts.setAttribute('src',c.url);\n\t\t\tdocument.getElementsByTagName('head')[0].appendChild(s);\n\t\t}\n\t},\n\tcomponentAvailable:function(component){\n\t\tthis.components[component].loaded = true;\n\t}\n}\n\nFor example the gallery.js file should call this notification as a last line:\n\nmyAwesomeApp.gallery = function(){\n\t// [... other code ...]\n}();\nmyAwesomeApp.componentAvailable('gallery');\n\nTelling the implementers when components are available\n\nThe last thing to add (actually as a courtesy measure for debugging and implementers) is to offer a listener function that gets notified when the component has been loaded:\n\nvar myAwesomeApp = {\n\tcomponents :{\n\t\tformcheck:{\n\t\t\turl:'formcheck.js',\n\t\t\tloaded:false\n\t\t},\n\t\tdynamicnav:{\n\t\t\turl:'dynamicnav.js',\n\t\t\tloaded:false\n\t\t},\n\t\tgallery:{\n\t\t\turl:'gallery.js',\n\t\t\tloaded:false\n\t\t},\n\t\tlightbox:{\n\t\t\turl:'lightbox.js',\n\t\t\tloaded:false\n\t\t}\n\t},\n\taddComponent:function(component){\n\t\tvar c = this.components[component];\n\t\tif(c && c.loaded === false){\n\t\t\tvar s = document.createElement('script');\n\t\t\ts.setAttribute('type', 'text/javascript');\n\t\t\ts.setAttribute('src',c.url);\n\t\t\tdocument.getElementsByTagName('head')[0].appendChild(s);\n\t\t}\n\t},\n\tcomponentAvailable:function(component){\n\t\tthis.components[component].loaded = true;\n\t\tif(this.listener){\n\t\t\tthis.listener(component);\n\t\t};\n\t}\n};\n\nThis allows you to write a main listener function that acts when certain components have been loaded, for example:\n\nmyAwesomeApp.listener = function(component){\n\tif(component === 'gallery'){\n\t showGallery();\n\t}\n};\n\nExtending with other components\n\nAs the main object is public, other developers can extend the components object with own components and use the listener function to load dependent components. Say you have a bespoke component with data and labels in extra files:\n\nmyAwesomeApp.listener = function(component){\n\tif(component === 'bespokecomponent'){\n\t\tmyAwesomeApp.addComponent('bespokelabels');\n\t};\n\tif(component === 'bespokelabels'){\n\t\tmyAwesomeApp.addComponent('bespokedata');\n\t};\n\tif(component === 'bespokedata'){\n\t\tmyAwesomeApp,bespokecomponent.init();\n\t};\n};\nmyAwesomeApp.components.bespokecomponent = {\n\turl:'bespoke.js',\n\tloaded:false\n};\nmyAwesomeApp.components.bespokelabels = {\n\turl:'bespokelabels.js',\n\tloaded:false\n};\nmyAwesomeApp.components.bespokedata = {\n\turl:'bespokedata.js',\n\tloaded:false\n};\nmyAwesomeApp.addComponent('bespokecomponent');\n\nFollowing this practice you can write pretty complex apps and still have full control over what is available when. You can also extend this to allow for CSS files to be added on demand.\n\nInfluences\n\nIf you like this idea and wondered if someone already uses it, take a look at the Yahoo! User Interface library, and especially at the YAHOO_config option of the global YAHOO.js object.", "year": "2007", "author": "Christian Heilmann", "author_slug": "chrisheilmann", "published": "2007-12-18T00:00:00+00:00", "url": "https://24ways.org/2007/keeping-javascript-dependencies-at-bay/", "topic": "code"} {"rowid": 104, "title": "Sitewide Search On A Shoe String", "contents": "One of the questions I got a lot when I was building web sites for smaller businesses was if I could create a search engine for their site. Visitors should be able to search only this site and find things without the maintainer having to put \u201crelated articles\u201d or \u201cfeatured content\u201d links on every page by hand. \n\nBack when this was all fields this wasn\u2019t easy as you either had to write your own scraping tool, use ht://dig or a paid service from providers like Yahoo, Altavista or later on Google. In the former case you had to swallow the bitter pill of computing and indexing all your content and storing it in a database for quick access and in the latter it hurt your wallet.\n\nTimes have moved on and nowadays you can have the same functionality for free using Yahoo\u2019s \u201cBuild your own search service\u201d \u2013 BOSS. The cool thing about BOSS is that it allows for a massive amount of hits a day and you can mash up the returned data in any format you want. Another good feature of it is that it comes with JSON-P as an output format which makes it possible to use it without any server-side component!\n\nStarting with a working HTML form\n\nIn order to add a search to your site, you start with a simple HTML form which you can use without JavaScript. Most search engines will allow you to filter results by domain. In this case we will search \u201cbbc.co.uk\u201d. If you use Yahoo as your standard search, this could be: \n\n\n\nThe Google equivalent is:\n\n\n\nIn any case make sure to use the ID term for the search term and site for the site, as this is what we are going to use for the script. To make things easier, also have an ID called customsearch on the form.\n\nTo use BOSS, you should get your own developer API for BOSS and replace the one in the demo code. There is click tracking on the search results to see how successful your app is, so you should make it your own.\n\nAdding the BOSS magic\n\nBOSS is a REST API, meaning you can use it in any HTTP request or in a browser by simply adding the right parameters to a URL. Say for example you want to search \u201cbbc.co.uk\u201d for \u201cchristmas\u201d all you need to do is open the following URL:\n\nhttp://boss.yahooapis.com/ysearch/web/v1/christmas?sites=bbc.co.uk&format=xml&appid=YOUR-APPLICATION-ID\n\nTry it out and click it to see the results in XML. We don\u2019t want XML though, which is why we get rid of the format=xml parameter which gives us the same information in JSON:\n\nhttp://boss.yahooapis.com/ysearch/web/v1/christmas?sites=bbc.co.uk&appid=YOUR-APPLICATION-ID\n\nJSON makes most sense when you can send the output to a function and immediately use it. For this to happen all you need is to add a callback parameter and the JSON will be wrapped in a function call. Say for example we want to call SITESEARCH.found() when the data was retrieved we can do it this way:\n\nhttp://boss.yahooapis.com/ysearch/web/v1/christmas?sites=bbc.co.uk&callback=SITESEARCH.found&appid=YOUR-APPLICATION-ID\n\nYou can use this immediately in a script node if you want to. The following code would display the total amount of search results for the term christmas on bbc.co.uk as an alert:\n\n\n\n\nHowever, for our example, we need to be a bit more clever with this.\n\nEnhancing the search form\n\n\n\n\nHere\u2019s the script that enhances a search form to show results below it.\n\nSITESEARCH = function(){\n\tvar config = {\n\t\tIDs:{\n\t\t\tsearchForm:'customsearch',\n\t\t\tterm:'term',\n\t\t\tsite:'site'\n\t\t},\n\t\tloading:'Loading results...',\n\t\tnoresults:'No results found.',\n\t\tappID:'YOUR-APP-ID',\n\t\tresults:20\n\t};\n\tvar form;\n\tvar out;\n\tfunction init(){\n\t\tif(config.appID === 'YOUR-APP-ID'){\n\t\t\talert('Please get a real application ID!');\n\t\t} else {\n\t\t\tform = document.getElementById(config.IDs.searchForm);\n\t\t\tif(form){\n\t\t\t\tform.onsubmit = function(){\n\t\t\t\t\tvar site = document.getElementById(config.IDs.site).value;\n\t\t\t\t\tvar term = document.getElementById(config.IDs.term).value;\n\t\t\t\t\tif(typeof site === 'string' && typeof term === 'string'){\n\t\t\t\t\t\tif(typeof out !== 'undefined'){\n\t\t\t\t\t\t\tout.parentNode.removeChild(out);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tout = document.createElement('p');\n\t\t\t\t\t\tout.appendChild(document.createTextNode(config.loading));\n\t\t\t\t\t\tform.appendChild(out);\n\t\t\t\t\t\tvar APIurl = 'http://boss.yahooapis.com/ysearch/web/v1/' + \n\t\t\t\t\t\t\t\t\t\t\t\t\tterm + '?callback=SITESEARCH.found&sites=' + \n\t\t\t\t\t\t\t\t\t\t\t\t\tsite + '&count=' + config.results + \n\t\t\t\t\t\t\t\t\t\t\t\t\t'&appid=' + config.appID;\n\t\t\t\t\t\tvar s = document.createElement('script');\n\t\t\t\t\t\ts.setAttribute('src',APIurl);\n\t\t\t\t\t\ts.setAttribute('type','text/javascript');\n\t\t\t\t\t\tdocument.getElementsByTagName('head')[0].appendChild(s);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t};\n\tfunction found(o){\n\t\tvar list = document.createElement('ul');\n\t\tvar results = o.ysearchresponse.resultset_web;\n\t\tif(results){\n\t\t\tvar item,link,description;\n\t\t\tfor(var i=0,j=results.length;i