Monday, March 26, 2012

Pinning a website - Part 1 - Internet Explorer 9

While attending a Webcamp around HTML 5, the instructor pointed out the "Pin to Taskbar" feature that is available as from Internet Explorer 9. Pinning a site to the taskbar makes your website behave more like a desktop application, allowing users to select from fixed tasks and/or a dynamic jumplist, receive notifications, see thumbnail previews... all available from a prominent location on screen showing your website's logo.

My blog pinned to my Taskbar

You can find all information and guidance on the 'Build my Pinned Site' website. Obviously I had to try it out for myself so if you are on Internet Explorer 9 or higher, you should be able to pin my blog to your taskbar. The result of doing this should look like this at the moment:

  • My brand new logo always present on the desktop
  • The right-click menu containing:
    • A link to my homepage
    • A fixed 'Tasks' section showing a link to my LinkedIn profile
    • A dynamic jumplist with my 10 most recent posts
As long as you don't pin this blog to your taskbar you will see a teaser bar at the bottom of my page informing you that this site can take advantage of the 'Pin to taskbar' feature

Generating the script

Microsoft provides a quick way of generating the script that is needed to enable the feature. When you start their wizard you'll need to provide some basic information, a list of fixed tasks, upload a 64x64 icon file, indicate if you want a teaser bar and give the URL to your RSS or Atom feed to generate the dynamic jump list. This generates a single JavaScript file that is hosted on the 'BuildMyPinnedSite' data servers, along with the icon you uploaded. So all you need to do is insert a reference to this script file on your page and you are good to go.

Unfortunately when I first inserted that script in my blog there were some issues:

  • The teaser bar was inserted on a fixed position on the top of the page where it collided with the Google navigation bar.
  • The URLs in my dynamic jumplist pointed to the 'comments' pages instead of the posts themselves.
  • I linked to the JavaScript file from within my blogspot template file, meaning that I would lose it if I were to choose a different template.

Making it work on Blogspot

Blogspot allows inserting custom HTML and JavaScript via their 'HTML/JavaScript' gadget. You can find it when you put your blog in design mode, choose layout and click 'Add Gadget'. I choose a wide gadget at the bottom of my screen because the generated teaser bar is quite wide.

Since the script has to do everything by itself, the entire construction of the teaser bar is done via script. I don't need that kind of dynamic behavior on this blog so I extracted the teaser bar <div id="___ie9sitepinning__bar_container"> from JavaScript and converted it to HTML. I also don't want the teaser bar to decide its own position. It should display in the area that is reserved for the gadget as defined by the template and chosen by the user.

These are the changes I made to the inline style of the root div:

  • Changed the style so it gets positioned inside the gadget's area
    • from "position: absolute; top: 0; left: 20px;width: 95%;"
    • into "position: relative; top: 0; left: 0px;width: 100%;"
  • Add the "display:none;" style because I don't want this div to appear by default
The HTML that I put in 'HTML/JavaScript' gadget looks like this:

<div id="___ie9sitepinning__bar_container" style="display:none; position: relative; top: 0; left: 0px;width: 100%; margin: 0; padding: 0; border: 0 none; border-bottom: 1px solid #707070;color: #1c1f26; background: transparent none no-repeat scroll 0 0; font-family: 'segoe ui', Arial, tahoma, sans-serif;line-height: 18px; box-shadow: 0 1px 5px rgba(140,140,140,0.7);">
 <div style="border: 1px solid #E1E1E1; padding: 5px 9px 2px 9px; background: #fff url(http://www.buildmypinnedsite.com/PinImages/Bar/bar-background.png) repeat-x scroll 0 100%;">
  <table cellspacing="0" cellpadding="0" style="width: 100%; border: 0 none; border-collapse: collapse;">
   <tbody>
    <tr>
     <td>
      <div style="background: transparent url(http://ie9pinning.blob.core.windows.net/files/JumpList/634679315872229742/Main-c:/users/bart.jolling/pictures/bscb-logo.png) no-repeat scroll 0 3px;background-size: 20px 20px; width: 260px; padding-left: 26px; min-height: 30px;font-weight: bold; font-size: 14px;">
       Experience my Software Cookbook as a Pinned Site
      </div>
     </td>
     <td>
      <div style="display: inline-block; padding-right: 40px; position: relative; text-align: right;">
       <strong style="font-weight: bold; font-size: 18px;">Drag this icon to your taskbar
        <img src="http://www.buildmypinnedsite.com/PinImages/Bar/arrow-icon.png" alt="Arrow" />
       </strong>
       <div style="color: #797c85; font-size: 12px;">
        or, <a href="#" onclick="window.external.msAddSiteMode(); return false" style="color: #6CABBA;text-decoration: underline;">click here</a> to add this site to your start menu
       </div>
       <div style="position: absolute; right: -120px; top: -13px; width: 164px; height: 143px;background: transparent url(http://www.buildmypinnedsite.com/PinImages/Bar/drag-icon-placeholder.png) no-repeat scroll 0 0;">
        <img class="msPinSite" style="position: absolute; top: 17px; left: 16px; cursor: move;width: 32px; height: 32px;" src="http://ie9pinning.blob.core.windows.net/files/JumpList/634679315872229742/Main-c:/users/bart.jolling/pictures/bscb-logo.png" alt="Drag this icon to pin this site" />
       </div>
      </div>
     </td>
     <td>
      <div style="position: relative; float: right; width: 80px; min-height: 30px; padding-left: 23px;padding-right: 30px; background: transparent url(http://www.buildmypinnedsite.com/PinImages/Bar/info-icon.png) no-repeat scroll 0 6px;">
       <a style="font-size: 12px; color: #6CABBA; text-decoration: underline;" href="http://windows.microsoft.com/en-US/internet-explorer/products/ie-9/features/pinned-sites" target="_blank">Learn about Site Pinning</a>
       <div onclick="document.getElementById('___ie9sitepinning__bar_container').style.display='none';window.sessionStorage.setItem('hideie9sitepinningbar', '1')"
        style="background: transparent url(http://www.buildmypinnedsite.com/PinImages/Bar/close-button.png) no-repeat scroll 0 0;position: absolute; top: 0; right: 0; display: block; width: 18px; height: 18px;cursor: pointer; float: right;">
       </div>
      </div>
     </td>
    </tr>
   </tbody>
  </table>
 </div>
</div>

Notice that the image on the highlighted line 20 has a class attribute set to "msPinSite". This class is recognized by Internet Explorer 9 and higher and enables the 'Drag this icon to your taskbar' functionality

The rest of the JavaScript needs to be encapsulated in a CDATA block and appended below the above div. Notice the two changes (highlighted) I had to make:

Line 87-99

The original code for generating the jumplist downloads the list of links per post and then just takes the first available link. This happens to be the link to the comments page of each blog. According to the information I found on the structure of an Atom feed (as used by blogspot) the perma-link to a post is called the 'alternate' link. So I execute a 'filter' function to the list of links to find the alternate link. If it can't find it, I take the first link just like the original code did

Line 171-172

Instead of dynamically generating the div here, I just find it back using its 'id' and I set the 'display' style to 'block'

<script type='text/javascript'>
//<![CDATA[
    var ____prototype_ae_IE9JumpList = ____prototype_ae_IE9JumpList || {};

    (function (jumplist) {
        if (!navigator.userAgent.toLowerCase().match(/msie (9|10)(\.?[0-9]*)*/)) {
            return;
        }

        var options = {

            // Basic site information 
            siteName: 'Bart\'s Software Cookbook', // Site Name
            applicationName: 'Bart\'s Software Cookbook', // Site Name 
            startURL: 'http://bartjolling.blogspot.com/', // Homepage URL 
            shortcutIcon: 'http://ie9pinning.blob.core.windows.net/files/JumpList/634679315872229742/Main-c:/users/bart.jolling/pictures/bscb-logo.ico', // Main Site Icon
            tooltip: '',

            // Dynamic jumplist tasks & notifications
            rssFeedURL: 'http://www.buildmypinnedsite.com/RSSFeed?feed=http%3a%2f%2fbartjolling.blogspot.com%2ffeeds%2fposts%2fdefault',
            categoryTitle: 'Posts', // Task group name
            defaultTaskIcon: 'http://ie9pinning.blob.core.windows.net/files/JumpList/634679315872229742/GenericTask-c:/users/bart.jolling/pictures/feed.ico', // Generic task icon

            navButtonColor: false,

            // Jumplist tasks { name: Task Label, action: Task URL, icon: Task Icon }
            staticTasks: [{ name: 'Linked-In Profile', action: 'http://www.linkedin.com/in/bartjolling', icon: 'http://ie9pinning.blob.core.windows.net/files/JumpList/634679315872229742/Task0-c:/users/bart.jolling/pictures/linkedin.ico', target: 'tab'}],

            // Drag and drop site pinning bar  
            prompt: true, // Add a site pinning bar on top of my site pages
            barSiteName: 'Bart\'s Software Cookbook' // Site name as it should appear on the pinning bar
        };

        var lib = {
            dom: {
                meta: function (name, content) {
                    var meta = document.createElement('meta');
                    meta.setAttribute('name', name);
                    meta.setAttribute('content', content);
                    return meta;
                },
                link: function (rel, href) {
                    var link = document.createElement('link');
                    link.setAttribute('rel', rel);
                    link.setAttribute('href', href);
                    return link;
                },
                div: function () {
                    return document.createElement('div');
                }
            },
            net: {
                getJSONP: function (URL) {
                    var script = document.createElement('script');
                    script.type = 'text/javascript';
                    script.src = URL + (URL.indexOf('?') != -1 ? '&' : '?') + Date.now();
                    var head = document.getElementsByTagName('head')[0];
                    head.insertBefore(script, head.firstChild);
                }
            }
        };

        jumplist.parseRSSFeed = function parseRSSFeed(news) {
            try {
                if (window.external.msIsSiteMode()) {
                    window.external.msSiteModeClearJumpList();
                    window.external.msSiteModeCreateJumpList(options.categoryTitle);

                    try {
                        // RSS feeds
                        if (news.rss && news.rss.channel && news.rss.channel.item) {
                            for (var items = news.rss.channel.item.slice(0, 10), numItems = items.length, i = numItems - 1, task, pubDate, taskTitle = ''; i >= 0; i--) {
                                task = items[i];
                                pubDate = Date.parse(task.pubDate);
                                taskTitle = task.title ? (typeof task.title == 'string' ? task.title : task.title['#cdata-section'] || '') : '';
                                window.external.msSiteModeAddJumpListItem(taskTitle, task.link, options.defaultTaskIcon);
                            }
                        } else if (news.feed && news.feed.entry) { // Atom feeds
                            for (var items = news.feed.entry.slice(0, 10), numItems = items.length, i = numItems - 1, task, pubDate, taskTitle = '', link = {}; i >= 0; i--) {
                                task = items[i];
                                pubDate = Date.parse(task.published);
                                taskTitle = task.title ? (typeof task.title == 'string' ? task.title : (task.title['#cdata-section'] ? task.title['#cdata-section'] : task.title['#text'] || '')) : '';

                                if (task.link) {
                                    if (typeof task.link == 'string') {
                                        link['@href'] = task.link || '#';
                                    } else if (Object.prototype.toString.call(task.link) === '[object Array]') {
                                         //filter to get the perma link called 'alternate'
                                        links = task.link.filter( function (item) {
                                            return(item['@rel'] == 'alternate');
                                        });
                                        if(links[0]) {
                                          link = links[0];
                                        } else {
                                          link = task.link[0];
                                        }
                                    } else {
                                        link = task.link;
                                    }
                                }

                                window.external.msSiteModeAddJumpListItem(taskTitle, link['@href'] || '#', options.defaultTaskIcon);
                            }
                        }
                    } catch (ex) {
                    }

                    window.external.msSiteModeShowJumpList();
                } else {
                }
            }
            catch (ex) {
            }
        }

        // Init code
        document.addEventListener('DOMContentLoaded', function () {

            try {
                document.getElementsByTagName('body')[0].onfocus = function () {
                    window.external.msSiteModeClearIconOverlay();
                };
            } catch (err) {
            }

            var head = document.getElementsByTagName('head');

            if (!head) {
                return;
            }

            head = head[0];

            var links = document.getElementsByTagName('link'), remove = [];

            for (var i = 0, rel; i < links.length; i++) {
                rel = links[i].getAttribute('rel');
                if (!rel) {
                    continue;
                }
                rel = rel.toLowerCase().replace(/^\s+|\s+$/g, '').replace(/\s+/g, ' ');
                if (rel == 'icon' || rel == 'shortcut icon') {
                    remove.push(links[i]);
                }
            }

            for (i = 0; i < remove.length; i++) {
                head.removeChild(remove[i]);
            }

            if (options.shortcutIcon) {
                head.appendChild(lib.dom.link('shortcut icon', options.shortcutIcon));
            }

            head.appendChild(lib.dom.meta('application-name', options.applicationName));
            head.appendChild(lib.dom.meta('msapplication-tooltip', options.tooltip));

            if (options.navButtonColor) {
                head.appendChild(lib.dom.meta('msapplication-navbutton-color', options.navButtonColor));
            }

            if (options.startURL) {
                head.appendChild(lib.dom.meta('msapplication-starturl', options.startURL));
            }

            for (var i = 0, task; i < options.staticTasks.length; i++) {
                task = options.staticTasks[i];
                head.appendChild(lib.dom.meta('msapplication-task', 'name=' + task.name + ';action-uri=' + task.action + ';icon-uri=' + task.icon + ';window-type=' + task.target));
            }
            if (options.prompt && !window.external.msIsSiteMode() && sessionStorage.getItem('hideIE9SitePinningBar') != '1') {
                var bar = document.getElementById('___ie9sitepinning__bar_container');
                bar.style.display='block';
            }

            jumplist.poll = function () {
                lib.net.getJSONP(options.rssFeedURL, jumplist.parseRSSFeed);
            };

            window.setTimeout(jumplist.poll, 30);
        });
    })(____prototype_ae_IE9JumpList);
//]]>
</script>

Pinning my blog site seems a bit over the top but I can easily imagine business cases for this feature. Web applications are starting to behave more and more like desktop applications and this is one of the things that makes the end-user experience richer and provides the content owner with a means to grab the users' attention and get them to visit their site more frequently.

2 comments:

SATiN said...

Hi , This is a very useful tutorial , thanks . I just have one thing to ask , that if we use use the rss feed url for dynamic jump list , does this updates the jumplist only when site is opened or it just keeps updating it async even if the website is not opened . i hope the question was clear .

Bart Jolling said...

Hi SATiN, the dynamic jumplist gets updated when the page loads. There is no 'offline' polling in Internet Explorer 9 on Windows 7.

That feature appears to be added as from Internet Explorer 10 on Windows 8