Miguel A. Castro's Blog

# Tuesday, March 16, 2010

In the never-ending quest to keep my site up-to-date, I wanted to add a “latest tweet” area to the top of my blog.  All these tweakings I’ve done (the syntax highlighter, licensing, branding, etc) were actually inspired by a talk that Scott Hanselman made in Cairo Code Camp on making your blog suck less.  The talk is based on this posting he made some time ago.  I’ll post on all the tweakings I’ve done later; let’s get back to the Twitter feed.

The Twitter site self has a section where you can step through creating an embedded Twitter feed, either in Flash or HTML.  In fact if you Google or Bing the phrase “embed twitter feed on website”, you’ll see many different techniques.  The one I used is the one right off the Twitter site.

After walking through the wizard, the site gives you this code:

<div id="twitter_div">
    <h2 class="sidebar-title">Twitter Updates</h2>
    <ul id="twitter_update_list"></ul>
    <a href="http://twitter.com/miguelcastro67" id="twitter-link" style="display:block;text-align:right;">follow me on Twitter</a>
</div>
<script type="text/javascript" src="http://twitter.com/javascripts/blogger.js"></script>
<script type="text/javascript" src="http://twitter.com/statuses/user_timeline/miguelcastro67.json?callback=twitterCallback2&count=5"></script>

I first inserted this code as-is, just to get things working (my usual approach to things).  The div tag was inserted into my homeTemplate.blogTemplate in the area where I wanted to display my Twitter feed.  In my case, this was just below the area that shows the admin bar (shown only when logged in).  The two script tags need to be placed at the bottom of the same file, just above the closing body tag.  This displayed my last five tweets in a unordered list.  The first script tag includes the function that the Twitter site will call back to after retrieving my feed information.  The second script tag makes the call to the Twitter API and defines the name of the callback function defined in the first script.  This worked fine but I wanted a different look.

The first thing I did to modify the display is to browse to the http://twitter.com/javascripts/blogger.js link and place the function in a javascript file in my site; then I replaced the src attribute in the first script tag to point at my function.  The results of this of course were no different than before – good.

Next I modified the div section for my display to look like this:

<div class="twitter-div">
    <a class="twitter-header" href="http://twitter.com/miguelcastro67" alt="Follow me on Twitter">Last Tweet:</a>&nbsp;
    <span id="twitter-post" class="twitter-post"><i>retrieving last tweet...</i></span>
</div>
<br/>

 

As you can see, I eliminated the unordered list and replaced it with something more simple, that’s one line only, and that expects only one tweet.  Then I took the function I had obtained from the blogger.js file and modified to look like this:

function twitterCallback(twitters) 
{
    var statusHTML = [];
    var username = twitters[0].user.screen_name;
    var status = twitters[0].text.replace(/((https?|s?ftp|ssh)\:\/\/[^"\s\<\>]*[^.,;'">\:\s\<\>\)\]\!])/g, function(url) {
        return '<a href="' + url + '">' + url + '</a>';
    }).replace(/\B@([_a-z0-9]+)/ig, function(reply) {
        return reply.charAt(0) + '<a href="http://twitter.com/' + reply.substring(1) + '">' + reply.substring(1) + '</a>';
    });
    statusHTML.push('<span>' + status + '</span> - <a style="font-size:85%" href="http://twitter.com/' + username + '/statuses/' + twitters[0].id + '">' + relative_time(twitters[0].created_at) + '</a>');
    document.getElementById('twitter-post').innerHTML = statusHTML.join('');
}

function relative_time(time_value)
{ var values = time_value.split(" "); time_value = values[1] + " " + values[2] + ", " + values[5] + " " + values[3]; var parsed_date = Date.parse(time_value); var relative_to = (arguments.length > 1) ? arguments[1] : new Date(); var delta = parseInt((relative_to.getTime() - parsed_date) / 1000); delta = delta + (relative_to.getTimezoneOffset() * 60); if (delta < 60) { return 'less than a minute ago'; } else if (delta < 120) { return 'about a minute ago'; } else if (delta < (60 * 60)) { return (parseInt(delta / 60)).toString() + ' minutes ago'; } else if (delta < (120 * 60)) { return 'about an hour ago'; } else if (delta < (24 * 60 * 60)) { return 'about ' + (parseInt(delta / 3600)).toString() + ' hours ago'; } else if (delta < (48 * 60 * 60)) { return '1 day ago'; } else { return (parseInt(delta / 86400)).toString() + ' days ago'; } }

Now I only worry about one tweet and display information about that tweet into the inner section of the span tag called twitter-post.  Incidentally, the original callback function looked like this:

function twitterCallback2(twitters) {
    var statusHTML = [];
    for (var i = 0; i < twitters.length; i++) {
        var username = twitters[i].user.screen_name;
        var status = twitters[i].text.replace(/((https?|s?ftp|ssh)\:\/\/[^"\s\<\>]*[^.,;'">\:\s\<\>\)\]\!])/g, function(url) {
            return '<a href="' + url + '">' + url + '</a>';
        }).replace(/\B@([_a-z0-9]+)/ig, function(reply) {
            return reply.charAt(0) + '<a href="http://twitter.com/' + reply.substring(1) + '">' + reply.substring(1) + '</a>';
        });
        statusHTML.push('<li><span>' + status + '</span> - <a style="font-size:85%" href="http://twitter.com/' + username + '/statuses/' + twitters[i].id + '">' + relative_time(twitters[i].created_at) + '</a></li>');
    }
    document.getElementById('twitter_update_list').innerHTML = statusHTML.join('');
}

 

Obviously I changed the name in the call to the Twitter API to use the callback function twitterCallback instead of the original twitterCallback2, but of course this can be anything so long as the two match.

The last change was to change the count attribute in the call to the Twitter API to 1 instead of 5.  This wasn’t crucial since I was only dealing with array item 0 in my callback function, but in the interest of getting as much performance as possible I thought it was a good idea.

I added the style sheet tags to make the display look the way I wanted and voila.  Because of the callback-nature of this technique, the blog page will render first while the call to the Twitter API is made and in the meantime I display a “retrieving…” message.

Until next time.

Tuesday, March 16, 2010 4:54:16 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] - - Follow me on Twitter
Dev Stuff

This technorati tag – TKHAYZRNKMP3 – is for their verification purposes only.

This bloglines tag – <!-- ckey="4595A1E8" –> – is for their verification purposes only.

Tuesday, March 16, 2010 12:59:05 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] - - Follow me on Twitter

# Monday, March 15, 2010

One of the reasons I took my blog off GeeksWithBlogs.com, despite the fact that I liked running it there for as long as I did, is so I can have source control over the site and be able to customize it as much as I want.  As before, I want people to be able to visit this site to learn about speaking engagements I do and to download session material, but rather than spread out the session material on individual blog postings (though I’ll do that as well), I wanted a single page where folks can go and download any material I’ve made available; hence www.dotnetdude.com/downloads.

So here’s where I tell you what I did and how I did it.  Now as usual, I anticipate criticism on what I did wrong and how better I could have done it and blah blah blah.  So while I really don’t care what people say about the way I do certain things, I will tell you what my goal was so you understand the methods I undertook.  I wanted a very, very simple downloads page.  Let me repeat that s-word, “SIMPLE”.  I didn’t want to have a database driven page or something with layers of categories.  I simply wanted a single page where anyone can go to get session, or magazine material.  Now that being said, I did want some amount organization while having ease of maintenance.  I simply ended up writing a page that read off a folder structure and manifested that structure in a tree.  In fact, in the interest of keeping it as simple as possible, I used Microsoft’s TreeView control.

The code is pretty generic (as you’ll soon see) and simply looks for a folder starting point, which I store in the config file.  From there it recursively iterates through the folders and files and builds the nodes of the tree.  This design let me manage the folder structure in whatever manner I want and allows for as much or as little hierarchy as I want.  I thought this was a good mix of simplicity and flexibility.

This was the easy part.  The harder part was digging around the dasBlog source code to figure out what I had to do in order to add custom pages to the site.  The dasBlog engine is very nice but does things a bit different from what conventional web forms application developers may be used to.  The site uses template files to determine the ASP.NET code for each section of a page.  For example, the main layout of every page in your blog is determined by a file called homeTemplate.blogTemplate.  This file contains what looks like conventional HTML code, and in fact it is.  However there are some keywords surrounded by <% %> tags (note, no equal sign) which will get replaced later when the template is processed and rendered.  This design actually made it pretty easy to customize the theme I chose for my blog because it is in this particular template file where I went to make alterations to the page layout.  When I wanted to customize what the blog posting area looked like, I went to the itemTemplate.blogTemplate file.  Here I could make changes like put the permalink on top instead of the bottom, or add an image to the left of my post header, etc.  Back to the homeTemplate.blogTemplate file; it is here where I added additional link tags and meta tags I wanted every page to use.  I also added the Syntax Highlighter here that you’ll see in practice below on code.  Rather than tell you a lot about this must-have utility, just check out Scott Hanselman’s post here.  Another customization I’ll save for another post is the Bing search box on the site.

So now that I knew how the templates worked, I wanted to add a custom page that appeared within the context of the main layout defined in the homeTemplate.blogTemplate file.  It didn’t take much digging into the existing pages of the site to figure out the repeating pattern they all have.

The page I wanted was to be called Downloads.aspx so I started by creating that page right off the root of my site.  Every page in dasBlog contains an empty PlaceHolder control which is later filled with a corresponding user control.

<%@ Page Language="C#" AutoEventWireup="False" CodeBehind="Downloads.aspx.cs" Inherits="newtelligence.DasBlog.Web.Downloads" %>
<asp:placeholder id="contentPlaceHolder" runat="server"></asp:placeholder>

 

The code-behind for this page follows the same pattern as other pages in the site.  The main thing is to inherit from the SharedBasePage class and implement the IPageFormatInfo interface.  The only required implementation for this interface is the BodyText property which returns a type of Control.  Here is where I would return an instance of a user control that will be the actual heart of my new page.  By instance, I mean a usage of the LoadControl command.  Another standard on the page is to expose the PlaceHolder by providing a property for it. 

Here’s the code-behind for the Downloads.aspx page:

using newtelligence.DasBlog.Runtime;
using newtelligence.DasBlog.Web.Core; 

namespace newtelligence.DasBlog.Web
{
    public partial class Downloads : SharedBasePage, IPageFormatInfo
    {
        protected override PlaceHolder ContentPlaceHolder
        {
            get { return contentPlaceHolder; }
        } 

        public Control Bodytext
        {
            get { return LoadControl("DownloadsList.ascx"); }
        }
    }
}

 

Now I had to write the user control that this page was to load and display.  This user control was simply going to have an ASP.NET TreeView control on it.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="DownloadsList.ascx.cs" Inherits="newtelligence.DasBlog.Web.DownloadsList" %>
<%@ Register Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
    Namespace="System.Web.UI" TagPrefix="asp" %>
<div class="downloadHeading">File Download Section</div>
<div class="bodyContentStyle" id="content" runat="server">
    <div class="downloadArea">
        <asp:TreeView ID="tvwFiles" runat="server" NodeIndent="40">
            <NodeStyle CssClass="fileTree" />
        </asp:TreeView>
    </div>
</div>

 

The div-tag hierarchy is just standard div styling with which I’m sure most of you as more familiar with than I am.

The real meat of this page is in the building of the tree, but even this was not overly complex.  I simply loaded a folder starting point from my config file and started my recursive iteration.  Each time I went down a level, I sent the previously built tree node so that each subsequent child tree node can be added to its collection.  The ASP.NET TreeView control is a bit more limited than third party controls and only seems to allow node-searching through one level within the tree.  If I’m wrong on this, please let me know.

Before I show you the code for this user control, let me tell you about one more thing I came up with.  Even though a folder and file hierarchy is easy enough to follow for any of us, I wanted a tad more polishing to this page.  I wanted the option (not the requirement) to display friendly titles for both folders and files within the folders should I desire that (and I do).  To accomplish this, I knew I would need some kind of mappings file but I didn’t want one massive file to manage so what the code does is look for a file called index.xml in each folder during the recursive iteration.  This file, if it exists, will let me rename the folder itself as well as any of the contained files.

Here’s an example of the index.xml file which resides in a folder called Cairo2010:

<titles folder="Cairo Code Camp - Feb 2010">
    <title file="DynamicStateStorage.zip" name="Dynamic State Storage: An ASP.NET Provider-Based Feature" />
    <title file="Extensibility.zip" name="Extensibility: Software That Survives" />
    <title file="FunWithHandlers.zip" name="Fun With HTTP Handlers" />
    <title file="UnderstandingASPNET.zip" name="Understanding ASP.NET Under The Covers" />
</titles>

 

As you can see, based on the information in this file, the folder will display as Cairo Code Camp – Feb 2010 and each of the contained files will display as their corresponding title as show in each name attribute.  Should I forget to create one of these files for a particular folder or forget to map a particular file within a folder, or simply decide to not do it by choice, the tree will display the original folder or file name instead.

    public partial class DownloadsList : System.Web.UI.UserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                string filesSubFolder = ConfigurationManager.AppSettings["files"];
                string filesFolder = Server.MapPath(filesSubFolder);
                string filesUrl = Request.ApplicationPath + "/" + filesSubFolder;

                string[] folders = ProcessFolder(filesFolder, null);
            }
        }

        private string[] ProcessFolder(string path, TreeNode parentNode)
        {
            string[] folders = Directory.GetDirectories(path);
            if (folders.Length > 0)
            {
                foreach (string folder in folders)
                {
                    DirectoryInfo dirInfo = new DirectoryInfo(folder);
                    string folderName = dirInfo.Name;

                    XDocument xmlDoc = null;

                    string indexFile = folder + "\\index.xml";
                    if (File.Exists(indexFile))
                    {
                        xmlDoc = XDocument.Load(indexFile);
                        var attrFolder = from node in xmlDoc.Descendants("titles")
                                 where node.Attribute("folder") != null && node.Attribute("folder").Value != ""
                                 select node.Attribute("folder").Value;

                        if (attrFolder != null)
                            folderName = attrFolder.FirstOrDefault<string>();
                    }

                    TreeNode folderNode = new TreeNode("&nbsp;&nbsp;" + folderName, folder);
                    folderNode.SelectAction = TreeNodeSelectAction.None;
                    folderNode.ImageUrl = "Images/Folder24.png";
                    
                    if (parentNode != null)
                        parentNode.ChildNodes.Add(folderNode);
                    else
                        tvwFiles.Nodes.Add(folderNode);

                    // retrieve files
                    string[] files = Directory.GetFiles(folder);
                    if (files.Length > 0)
                    {
                        string appPath = Request.PhysicalApplicationPath;

                        foreach (string file in files)
                        {
                            FileInfo fileInfo = new FileInfo(file);
                            string fileName = fileInfo.Name;

                            if (fileName.ToLower() != "index.xml")
                            {
                                if (xmlDoc != null)
                                {
                                    var attrName = from node in xmlDoc.Element("titles").Descendants("title")
                                                    where node.Attribute("file").Value == fileName
                                                    select node.Attribute("name").Value;

                                    if (attrName != null)
                                        fileName = attrName.FirstOrDefault<string>();
                                }

                                string fileUrl = file.Replace(appPath, "");
                                string fileAlt = fileInfo.Name;
                                string fileLink = string.Format("<a href=\"{0}\" alt=\"{1}\">{2}</a>", fileUrl, fileAlt, fileName);

                                TreeNode treeNode = new TreeNode(fileName, fileUrl);
                                treeNode.NavigateUrl = fileUrl;
                                folderNode.ChildNodes.Add(treeNode);
                            }
                        }
                    }

                    string[] subFolders = ProcessFolder(folder, folderNode);
                }
            }

            return folders;
        }
    }

 

As you can see, the code uses Linq-to-XML to access the index.xml files.

The last thing I wanted to do is to give a user the easiest URL possible so although you can navigate to www.dotnetdude.com/downloads.aspx to get to this page, I want you to be able to simply type www.dotnetdude.com/downloads.

IE7 allows for extension-less URL, my current hosting server still uses IE6 so I needed a sure-fire technique to accomplish this.  The easiest thing to do was to create a sub-folder in my site called Downloads and place an empty Default.aspx page in it.  In fact, I eliminated the code-behind for this page so it is truly empty.  I eliminated the code-behind because I didn’t need to put a code-driven redirect to my actual Downloads.aspx page.  This is thanks to a URL mapper that comes with dasBlog.  To take advantage of this, I went to the web.config file and fouind the <newtelligence.DasBlog.UrlMapper> sesion.  Following the pattern I saw in there already, I added an entry that looked like this:

<add matchExpression="(?&lt;basedir&gt;.*?)/downloads" mapTo="{basedir}/Downloads.aspx"/>

 

This should be easy enough to follow.  Suffice it to say that navigating to www.dotnetdude.com/downloads now takes you to the actual Downloads.aspx page.  In fact, I added another couple of lines to shortcut to the email page on the site and to an bio posting as well.

<add matchExpression="(?&lt;basedir&gt;.*?)/aboutme" mapTo="{basedir}/2010/03/02/AboutMeShamelessBioPost.aspx"/>
<add matchExpression="(?&lt;basedir&gt;.*?)/contact" mapTo="{basedir}/Email.aspx"/>

 

Now you can simply type www.dotnetdude.com/email to contact me, or if you’re really bored you can type www.dotnedude.com/aboutme .

The last thing I want to point out here is that the TreeView control is not using JavaScript to expand and collapse itself.  I haven’t yet figured out why as this is supposed to be a built-in feature of the ASP.NET TreeView control.  I’m suspecting that it has to do with the way dasBlog turns templates into code and renders pages.  I need to put some time into it cause I don’t really like the fact that it posts each time you collapse or expand a node.  Clicking on a file doesn’t post, since the display text for each file is literally an HTML anchor tag to the actual file to download.  I tried simply putting the control in an ASP.NET Ajax UpdatePanel control and it was ignored.  This is what tells me it’s something within dasBlog rendering since the Ajax UpdatePanel functionality is also not working.  If anyone has any ideas on this, please let me know.

I hope this has been useful to anyone who uses dasBlog and wants to customize it to their liking.  Now I just gotta fill the download page with all my session material from past event.

Until next time.

Monday, March 15, 2010 7:48:06 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] - - Follow me on Twitter

# Tuesday, March 09, 2010

In the process of readying my new blog site, I wanted to place a couple of badges that point to my various profiles around the web and I found a site that showed me this link:

http://www.linkedin.com/profile?promoteProfile

It’s weird but trying to find this page within the LinkedIn site itself seems impossible.  In any case, each badge comes with the script that you can include in your own site so there’s not even the need to copy the image to your local store.

Here are some examples of what the page has:

View Miguel Castro's profile on LinkedIn

View Miguel Castro's profile on LinkedIn

View Miguel Castro's profile on LinkedIn

View Miguel Castro's profile on LinkedIn

Tuesday, March 09, 2010 3:59:12 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] - - Follow me on Twitter

# Monday, March 08, 2010

Well, Code Camp in NYC is over and I have to say it was really a lot of fun.  Kudos to Steve, Andrew, and the rest of the organization staff for a great job.  The only thing that really holds that code camp back is the fire code in the Microsoft building on 6th Avenue.  There always seems to be a waiting list because the attendance registration fills up real fast, but be that as it may it was still great.

I always submit one or two intro talks and behold, they still get chosen.  I did an intro talk on WCF and the room was so packed that they asked me if I’d repeat it in the afternoon (packed too).  It seems that WCF is still an intimidating technology so I’m always glad to clear it up for people and prove that at its basics, it really is easy to hit the ground running with it.

You can get the code and slides in the downloads section of this site.  I didn’t go through all the slides because after all it is Code Camp and not Slide Camp.  However, the steps I took to write all the code from scratch and the order in which I took them is thoroughly highlighted in the slide deck amongst the slides I did not go through.

All in all, I think everyone seemed to have enjoyed the presentation and I’m always glad to add to the numbers of people getting up to speed on this technology.

Monday, March 08, 2010 12:58:31 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] - - Follow me on Twitter
Speaking Events
Search
Me & My Flair

Read all about me here.
Download my Resume here.

Check out where I am here.
 
Click on logos above for profiles.
Archive
<March 2010>
SunMonTueWedThuFriSat
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910
Statistics
Total Posts: 40
This Year: 0
This Month: 0
This Week: 0
Comments: 93