27 October 2011

SharePoint: Get parent site title

In SharePoint, we can use subsites to handle navigation beyond the 2nd level; let's assume we have a structure such as this:
  • Site Collection Root
    • Site 1: site title - "Products & Software"
      • Subsite A: site title - "Widgets"
        • Subsite AA: site title - "Widget X"
        • Subsite BB: site title - "Widget Y"
    • Site 2
    • Site 3
It often makes sense to show Site 1's title "Products & Software" down that branch of subsites, regardless of how deep you are. This helps users know they're still under "Products & Software". To fetch the root parent's title (Site1, not the Site Collection Root), you have to use a recursive function, thanks to W0ut and help from Stack Exchange:
protected override void OnLoad(EventArgs e)
{
  // To ensure page behaves correctly, must call base.OnLoad(e).
  base.OnLoad(e);

  GetRootTitle();
}

private void GetRootTitle()
{
  string title = string.Empty;

  /* 
     On admin pages, this label object doesn't exist and will throw
     a NullReferenceException if we don't check it before trying
     to use it.
  */
  if (sectionTitle == null) return;

  using (SPSite site = new SPSite(SPContext.Current.Site.ID))
  {
    using (SPWeb web = site.OpenWeb(SPContext.Current.Web.ID))
    {
      title = IterateThroughParentsAndStoreInfo(web, title);
    }
  }

  sectionTitle.Text = title; // Set a label's value to the title.
}

private string IterateThroughParentsAndStoreInfo(SPWeb web, string title)
{
  if (web.ParentWeb != null)
  {
    title = web.Title;
    return IterateThroughParentsAndStoreInfo(web.ParentWeb, title);
  }
  return title;
}
Note the using statements; these ensure there are no memory leaks by disposing of the objects properly and also implicitly handling the try/catch/finally block. The change to W0ut's code is the addition of the OnLoad() handler, which can be either in your master page or page layout. Also, we've added the title variable, which gets set in the recursive function; note that its value is always of the title for the site one before the last. This is because when the web variable's ParentWeb becomes null, we've reached the site collection. This is exactly what we need.

One last thing to note: We do a null check for the label object which will hold the title's value. On some administrative (system) pages, the label isn't rendered so we'll throw a NullReferenceException. Another way to do this is to always add the title label as a new Label(); this way it's always present, regardless of which page we're on.

19 October 2011

IE7 and "Error: Expected identifier, string or number"

IE7 (and earlier versions) have trouble with errant commas in array declarations. Take this code, provided by James Messinger on Stack Overflow:
<script type="text/javascript">
// Replace the normal jQuery getScript function with one that supports
// debugging and which references the script files as external resources
// rather than inline.
jQuery.extend({
   getScript: function(url, callback) {
      var head = document.getElementsByTagName("head")[0];
      var script = document.createElement("script");
      script.src = url;

      // Handle Script loading
      {
         var done = false;

         // Attach handlers for all browsers
         script.onload = script.onreadystatechange = function(){
            if ( !done && (!this.readyState ||
                  this.readyState == "loaded" || this.readyState == "complete") ) {
               done = true;
               if (callback)
                  callback();

               // Handle memory leak in IE
               script.onload = script.onreadystatechange = null;
            }
         };
      }

      head.appendChild(script);

      // We handle everything using the script element injection
      return undefined;
   }, // <-- Unneeded comma which blows up IE7.
});
</script>
IE7 blows up with this error:
Error: Expected identifier, string or number
It took quite a bit of debugging and Google searches to finally understand where the problem was. Note the last comma, after the next-to-last closing curly brace "}," -- this is unnecessary. Modern browsers can handle this, but IE7 chokes. Simply remove the comma and you're good to go. More on this problem on Stack Exchange.

How to hide/show table rows in IE7

If you need to hide a table row (TR), you'd usually use the following:
.hide-tr { display:none; }
The problem develops when you later wish to show the row; simply setting the display to block is insufficient, as the individual cells in the row lose their positions and often get bunched next to each other. Using display:table-row works on most modern browsers, but IE7 ignores that and prefers display:inline-block. So the solution? There are a few ways to fix the problem, as indicated on Stack Exchange. The problem with using visibility:hidden is that the row still takes up space on the page and shrinking its height is a non-trivial task. Another solution involves a method less recommended, using browser and version detection via jQuery. However, it does fix the issue:
if ($.browser.msie && jQuery.browser.version == '7.0'){
  $('.hide-tr').css('display','inline-block');
}
else {
  $('.hide-tr').css('display','table-row');
}
Not a clean solution but it gets the job done :)

Update: An even easier solution: Simply call the jQuery show() method on the object and it's smart enough to apply the appropriate CSS to the object.

02 October 2011

SharePoint: Create an easy "Share page with friend" button

In SharePoint, it's useful to have an email icon that allows users to email the page to friends. Though we could build a popup box, we can also go with a simpler solution that uses the person's email client to do the heavy lifting. In another post, I covered how to fetch some of the server-side variables into a JavaScript array needed for generating this email. Now, let's use the variables and see how we send the email. One key to this feature is to keep it simple; this means, we'll leave the To field of the email blank and use the commonplace "mailto:" HREF attribute. So, let's get to the code:
function generateEmailTo(){
  var body = currentElements.currentUserName + ' has shared a page with you on the intranet.%0A%0APage Title: %22' +
    currentElements.currentTitle  + '%22%0A' + $(location).attr('href').replace('#',''); 
  var subject = currentElements.currentUserName + ' has shared an intranet page with you';
  var mailto = 'mailto: ?body=' + body + '&subject=' + subject;
  var anchor = '<a href="' + mailto + '"></a>';

  $("#send-email").wrap(anchor);
}
We can pass the body, subject, and mailto for the mail message. The ?body= and the &subject= allow the main message and the subject to be passed in the querystring. To provide line breaks, we use %0A hex values; so for two line breaks, we use %0A%0A in the querystring value. To pass quotes, we use %22. To pass a blank mailto:, we leave a space in front of it, before the ?body=. More notes on mailto, setting its cc, bcc, and special characters can be found here.

Once we've concatenated the various elements, we pass the anchor variable to the jQuery wrap() function, called on the email icon img tag (with ID of send-email). We call the above generateEmailTo() in the document.ready function. When you click the link, it produces something like this in the generated email (Outlook, Thunderbird, etc.):
Alex C has shared a page with you on the intranet. 

Page Title: "My Page Title" 
http://mysite.org/Pages/mypage.aspx
The subject, not shown, will be "Alex C has shared an intranet page with you". Note that the URL will appear as a clickable link in most email clients. Also note that we can't pass HTML to the body of the message, only plain text.

Hope you find this useful.

Update: Here's the IMG tag that is being referenced in the code above:
<img id="infoweb-email" title="Send this page to a friend." 
  src="<%= baseUrl %>/SiteAssets/media/icons/email-icon.gif"/>
The baseUrl server-side variable is set in the page layout's OnLoad event handler:
<script type="text/c#" runat="server">
protected override void OnLoad(EventArgs e)
{
  base.OnLoad(e); // Required for the SharePoint ribbon bar, etc., to work correctly.
  Microsoft.SharePoint.SPContext context = Microsoft.SharePoint.SPContext.GetContext(HttpContext.Current);
  baseUrl = context.Site.Url;
  // Other code...
}
</script>
For the page layout to allow code blocks, we must make the following change to the Web.config file:
<PageParserPaths>
   <PageParserPath VirtualPath="/_catalogs/masterpage/*" 
       CompilationMode="Always" 
       AllowServerSideScript="true" IncludeSubFolders="true"/>
</PageParserPaths>
Note that this allows all files in the masterpage folder to contain code blocks. If we use Firebug to examine the element in Firefox, we'll see this is the HTML generated by our server- and client-side code:
<a href="mailto: ?body=Alex%20C has shared a page with you on the intranet.%0A%0APage Title: %22My%20Page%20Title%22%http://mysite.org/Pages/mypage.aspx&subject=Alex%20C has shared a page with you on the intranet.">
<img id="send-email" src="http://mysite.org/SiteAssets/media/icons/email-icon.gif" title="Send this page to a friend.">
</a>
So the IMG element has been wrapped by an anchor tag which has the properties we set in JavaScript.

SharePoint: Fetch environment variables and store in JavaScript

For a header text, we needed the current site's name. In addition, to generate a "Share this page with a friend" button, we needed the current user's login name and full name. We could use SharePoint's Client Object Model, which does an asynchronous call from JavaScript to get the information.

However, we needed the site's name to be available with the DOM, not when the page loads; we tried the AJAX call and it took a second or two to fetch the data. During this time, the page had no header text saying which site the user was on.

What to do? We could use jQuery to get the site name from the breadcrumbs generated via the asp:SiteMapPath control, but there could be times when we wouldn't be using this control. Else we could fetch it from the top-left navigation tree dropdown; even that was clunky. So we decided to put the following code in the Page Layout's PlaceHolderAdditionalPageHead control; it can also be placed in the master page:
<asp:Content ContentPlaceholderID="PlaceHolderAdditionalPageHead" runat="server">
<%
String currentUserName = Microsoft.SharePoint.SPContext.Current.Web.CurrentUser.Name.ToString();
String currentUserEmail = Microsoft.SharePoint.SPContext.Current.Web.CurrentUser.LoginName.ToString().Replace("MYDOMAIN\\","");
String currentSite = Microsoft.SharePoint.SPContext.Current.Web.ToString();
String currentTitle = (Microsoft.SharePoint.SPContext.Current.Item["Title"] == null) ?
 "" : Microsoft.SharePoint.SPContext.Current.Item["Title"].ToString();

StringBuilder sb = new StringBuilder();
sb.Append("<script type='text/javascript'>var currentElements = { currentUserName: '");
sb.Append(currentUserName);
sb.Append("', currentUserEmail: '");
sb.Append(currentUserEmail);
sb.Append("', currentSite: '");
sb.Append(currentSite);
sb.Append("', currentTitle: '");
sb.Append(currentTitle);
sb.Append("'}</script>");
Response.Write(sb.ToString());
%>

....

</asp:Content>
We fetch the current user's name using the SPContext.Current property, which gives us a handle to the current HTTP request in SharePoint. Then we get the various items we need, such as the user's name, login name, web (current site name), and the publishing page's title.

Once we have the values, we plug them into a JavaScript array that we build server-side. When the page loads in the browser, the array will be instantiated with the values we set using C#. We use a StringBuilder object for optimal performance because of the string concatenations.

So what does it look like when the page loads? Here it is:
<script type='text/javascript'>var currentElements = { currentUserName: 'Alex C', currentUserEmail: 'myemailid', currentSite: 'My Site Name', currentTitle: 'Page Title'}</script>
To use any of the array elements in jQuery or elsewhere, just call it via something like this: currentElements.currentSite

A final note: If any of the values will have a single quote in them, just escape the value using a .Replace("'","\'")on the string in C#.

21 September 2011

SharePoint: Programmatically add JS/CSS to pages

John Chapman has a great article on creating a feature for your SharePoint installation that adds JavaScript and CSS files to all pages without touching the site's master page. The good folks on SharePoint.StackExchange.com helped me implement it and also modify the code to better utilize SharePoint's native control libraries.

First, follow the code provided by John Chapman. Then modify the CustomPageHead.ascx.cs to take advantage of the built-in SharePoint controls:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;

namespace CustomPageHead.CONTROLTEMPLATES.CustomPageHead
{
public partial class CustomPageHead : UserControl
{
protected override void CreateChildControls()
{
base.CreateChildControls();

this.Controls.Add(new ScriptLink()
{
Name = "/_layouts/CustomPageHead/jquery-1.6.2.min.js",
Language = "javascript",
Localizable = false
});

this.Controls.AddAt(0,new ScriptLink()
{
Name = "/_layouts/CustomPageHead/some-custom-code.js",
Language = "javascript",
Localizable = false
});

this.Controls.AddAt(1,new CssRegistration()
{
Name = "/_layouts/CustomPageHead/some-stylesheet.css"
});
}
}
}
Note that using the native controls, you don't have to worry about paths to where the files will be; SharePoint will place these items in the following location on your server:
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\CustomPageHead
Also, the advantage of using the this.Controls.AddAt() method is that you can specify where in the object hierarchy to add the specified object.

In addition, the solution can be generated into a *.WSP by selecting "Package" from Visual Studio's Build menu. The file can then be copied from the bin/Release folder and run on the command line to install the feature.

Special thanks goes to omlin and James Love for their help.

14 September 2011

SharePoint tabs UI for web parts

Christophe Humbert's beautiful Easy Tabs for SharePoint works wonderfully well; it automatically generates tabs for the web parts on the page. You simply drop the code Christophe provides into a content editor web part (CEWP) and off you go.

In a current project, we needed the ability to have all pages show tabs without dropping a CEWP on every page. The code needed to be on a page layout for a publishing site. The challenge? Christophe's code traverses up the DOM from the current CEWP to find the parent container of the web parts; unless you use a CEWP, it won't work.

Some serious trial-and-error, as well as posting to the SharePoint group on StackExchange.com, provided the clues; we need to provide a direct reference to the parent container of the web parts. Here's the part of the code that was moving up the DOM tree:
var el=document.getElementsByTagName("SCRIPT"),p=el[el.length-1],sT,a,sep,tabRow;
do {p=p.parentNode;sT=p.innerHTML.split("MSOZoneCell_WebPart");}while (sT.length<4 && p.parentNode.id!="MSO_ContentTable")
In the page layout, the web parts are in a Page Content control (PublishingWebControls:richhtmlfield), so here's the change to the JavaScript to reference that parent container:
var p,a,sep,tabRow;
p = document.getElementById('ctl00_PlaceHolderMain_ctl01__ControlWrapper_RichHtmlField');
Note that there's no need for the el variable or the do-while loop. You probably need to do some trial-and-error, using alert() statements to view the p.innerHtml and/or p.parentNode.innerHtml; this helped me discover what p was pointing to after the do-while and simply reference it directly.

07 September 2011

SharePoint: If your stylesheet isn't added to the page...

For a current project, I'm using a custom page layout within a publishing site. I had the following code in the my_page_layout.aspx file:
<asp:Content ContentPlaceholderID="PlaceHolderAdditionalPageHead" runat="server">
<link type="text/css" href="../../SiteAssets/js/jquery/jquery-ui-1.8.16/css/cupertino/jquery-ui-1.8.16.custom.css"/>
....
</asp:Content>
No matter what I did, the stylesheet wouldn't be available to the SharePoint page. However, another stylesheet was showing up. The difference? I was missing the rel="stylesheet" attribute on the bad link tag. Added it and everything worked.

28 August 2011

SharePoint: Remove link from list view title

Links can't be easily removed from list view titles (or web part titles, for that matter). That's where jQuery comes in handy:
$(document).ready(function () {

// Remove the link from the web part title and set its cursor to normal.
$('.ms-WPTitle > a').contents().unwrap().css('cursor','default');
$('.ms-WPTitle').css('cursor','default');
});
This is thanks to a tip from this post on StackOverflow.com. Note also that the code sets the cursor to default (instead of "hand").

23 August 2011

SharePoint: Automatic table of contents for a page's sections

If you have really long pages in SharePoint with lots of sections, you can use the nifty jQuery plugin from Rochester Oliveira to automate the creation of the table of contents (TOC). Note that this is different from the table of contents web part built into SharePoint; that tool only handles pages and sites, not content within pages.
  1. Ensure you've uploaded jQuery to your SharePoint site (or site collection); usually, these files will go into the Site Assets area so they're accessible to all sites. For example, http://mydomain.org/SiteAssets/js/jquery/jquery-1.6.2.min.js.

  2. Also place the jquery.stoc.js file necessary for this task in a similar location.

  3. Reference these two JS files from your master page, page layout, or content editor web part.

  4. Add some JavaScript to trigger the TOC creation (see code below). You'll also need an empty DIV tag that will have the TOC injected into it.

  5. Add the necessary CSS to style the TOC and the UL tags. Rochester Oliveira's plugin page has some great examples to get you started.

One other change is necessary for it to work correctly: For some reason, on SharePoint 2010, it was appending #undefined to all the table of contents items. So I modified the JS in the jquery.stoc.js to this:
if (id == "" || typeof id === "undefined") { //if it doesn't have only, of course

id = "h" + tagNumber + "_" + i;
cacheHN.attr('id', id);
}
Now if id is undefined, it will generate one. So you're ready to test. If you wish to get fancier, you can add a True/False dropdown to the page layout for editors to choose whether a page will have a TOC (for instructions on conditionally displaying the dropdown, please see this post). This code can generate the necessary JavaScript based on the page layout's custom content column EnableTOC value:
<%= (Microsoft.SharePoint.SPContext.Current.Item["EnableTOC"] == null ||

Microsoft.SharePoint.SPContext.Current.Item["EnableTOC"].ToString() == "False")? "" :
" "
%>
In the above code, if EnableTOC is set to True, we trigger the TOC plugin on the element with ID toc; the plugin will search the element main-content for tags of type H2 and H3 ("start: 2"), and generate the TOC based on that structure using UL tags. Also, the toc DIV tag is hidden by default; the code above changes its display property to block.

If you always want the TOC to be generated, take it out of the server-side code and make it entirely client-side. In addition, because of a bug in SharePoint 2010, named anchors might not work correctly; in other words, you might click an item in the TOC and not have the page go to that part of the document. In that case, you can add this code as well, courtesy of fcorbeil on the Microsoft MSDN forum:
<script type="text/javascript">

setTimeout(Reload,2000);
function Reload() {
window.location.hash=self.document.location.hash.substring(1);
}
</script>

16 August 2011

SharePoint: Icon doesn't show for Publishing Hyperlink

Ran into this today. Very strange problem. For whatever reason, the "Display icon" functionality of the Publishing Hyperlink field doesn't work on SharePoint 2010 Server for a site with Publishing enabled. Added URLs to a column of that type for PDF files, but no PDF icons appeared. In fact, the value for the "Display icon" field wasn't "sticky" -- it kept getting unchecked. This article saved the day, with a very easy, non-coding solution using only CSS attribute selectors.

I'm using a custom page layout in my publishing pages with an attached CSS file; in that file, I added the following items to correspond to the location of my icon files (inside SiteAssets); I also added .docx for Word 2007+ document types and .xlsx for Excel 2007+ docs:
a[href$='.pdf'] {

display:inline-block;
padding-left:20px;
line-height:18px;
background:transparent url(/SiteAssets/icons/icon_pdf_16x16.gif) center left no-repeat;
}
a[href$='.doc'], a[href$='.rtf'], a[href$='.txt'], a[href$='.docx'] {
display:inline-block;
padding-left:20px;
line-height:18px;
background:transparent url(/SiteAssets/icons/icon_doc_16x16.gif) center left no-repeat;
}
a[href$='.zip'], a[href$='.gzip'], a[href$='.rar'] {
display:inline-block;
padding-left:20px;
line-height:18px;
background:transparent url(/SiteAssets/icons/icon_zip_16x16.gif) center left no-repeat;
}
a[href$='.xls'], a[href$='.csv'], a[href$='.xlt'], a[href$='.xlw'], a[href$='.xlxs'] {
display:inline-block;
padding-left:20px;
line-height:18px;
background:transparent url(/SiteAssets/icons/icon_xls_16x16.gif) center left no-repeat;
}
The CSS works by finding all anchor tags with an href attribute that ends (using the dollar symbol $) with the specified string; so for PDFs, it uses a[href$='.pdf'] and applies the style rules. And we're only interested in those 4 file types, so those are the ones that have icons in our CSS file.

Reloaded the page and viola! The icon appears to the left of the item as if by magic :)

Also, the article from psyked has links to some nice (and free) icon libraries. All in all, a very good solution. However, we still don't know what's causing the built-in "Display icon" functionality to not work.

Hope this helps others. Thanks.

Beyond app dev: Applying agile techniques to business

http://www.infoworld.com/t/agile-development/beyond-app-dev-applying-agile-techniques-business-169749

Agile, a term that has become associated with building software in short iterations, is making headway as a philosophy for running a business in general, with executives latching on to agile practices such as its heavy emphasis on flexibility and collaboration.

07 August 2011

SharePoint: Conditionally display content for authors and readers

For a current project, I needed to display an Article Date content field in a page layout. The author could pick a date when in Edit View; the reader would only see the value the author had selected, or nothing if no value had been picked.

To do server-side code blocks in a page layout, we'll first need to enable it from our app's web.config file; otherwise it'll throw an error saying "Code blocks are not allowed in this file," as described here. To enable code blocks, open your app's web.config and locate the PageParserPaths tag, which is empty, and modify it to look like this:


Note the VirtualPath; this needs to point to the location of your page layout (or ASPX) file.

Next, we'll use Bart McDonough's blog post to add the EditModePanel and AuthoringContainer; the nesting of these controls is well explained by Bart:











http://www.blogger.com/img/blank.gif
Note the DateTimeField element; that allows authors to edit the "Article Date" field.

Now we need to add some server-side code to determine if we can display the "Article Date," and to format it properly (this code is based on Johan Leino's blog post):
<%= (Microsoft.SharePoint.SPContext.Current.Item["Article Date"] == null) ? "" : 
"Last update on " + DateTime.Parse(Microsoft.SharePoint.SPContext.Current.Item["Article Date"].ToString()).ToString("d MMMM yyyy") +
"." %>
This code displays the text "Last update on 9 August 2011." if there's a value in the "Article Date" content field; otherwise, it displays nothing.

23 June 2011

Enable screensaver on Windows 7

On my current Windows 7 Enterprise 64-bit OS, the screensaver option was disabled: When I right-clicked on the desktop, chose Personalize, the Screen Saver item was greyed out. It also displayed the message at the bottom of the dialog: "One or more of the settings on this page has been disabled by the system administrator."

Some trial-and-error and research revealed the answer.
  1. Back up your registry using these instructions.
  2. Go to Start and in the search box, type regedit
  3. This will display the regedit program at the top of the Start dialog box.
  4. Right-click on the regedit link and choose "Run as Administrator."
  5. In regedit, go to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\System and create a new DWORD (32-bit) Value.
  6. Name the value NoDispScrSavPage
  7. Set the value to 0 (zero).
  8. Close the registry editor.
  9. Reboot your system.
Now the Screen Saver option should be available again in the Personalize area.

Update: One more step: You also need to follow the steps described here, as recommended by Ted Myers on the Windows Secrets Lounge. Finally, I have my screensaver back :)

05 May 2011

An uncertain future for open source .Net

http://www.infoworld.com/d/application-development/uncertain-future-open-source-net-047
"Layoffs at Attachmate put the future of the Mono Project in question, dimming hopes for cross-platform C# development"

15 April 2011

Create a quasi-random number with Apache SSI

Recently, I had a Flash SWF carousel that was consuming XML data. However, when the admin updated the XML, the SHTML page didn't always pull the latest version. The solution was to attach a random value as a querystring to the XML file:
<param name="FlashVars" 
value="paramXMLPath=param.xml?rv=<!--#echo var="rand" -->">
Note the comment tag; that's actually telling Apache to process the information server side using SSI. In this case, I've defined a variable earlier in code:

This is using the built-in timefmt configuration option, which allows you to define how the date and time will be displayed. I'm telling Apache to display the date and time as year, day of year, day of month, hour, minute, and second, all concatenated together. This provides a unique number each time the page is loaded and can be used as a querystring value. This page provides a great list of configuration options and what the % formatting values are.

Rethinking application development in light of 'devops'

http://www.infoworld.com/d/open-source-software/rethinking-application-development-in-light-devops-970

01 April 2011

Create a new window using custom jQuery function

For my current project, I needed to pop up a new window from specific anchor tags on the page. This provided the perfect opportunity to write some custom jQuery to handle this. I started with some research into creating jQuery function; Jeremy Martin's blog post got me started. Next, I got some inspiration from this popup window generator, though I could never get it working for my site.

So the jQuery function looks like this:
/*
* Add a function to jQuery to automate popup windows.
*/
(function($){
$.fn.popWindow = function(options) {

// Declare some default values.
var defaults = {
width: 729,
height: 529,
left: 19,
top: 19,
location: 0,
directories: 0,
status: 0,
toolbar: 0,
menubar: 0,
resizable: 1,
scrollbars: 1,
url: '',
name: 'cms_win',
center: 0
};

// Merge the defaults with the options parameter.
var options = $.extend(defaults, options);

// For each of these objects...
return this.each(function(){
// Get a handle to this HTML object.
obj = $(this);

// Set the left and top if we're centering.
if (options.center){
options.left = (screen.width - options.width)/2;
options.top = (screen.height - options.height)/2;
}

// If user provided a URL, use that; otherwise use the HREF.
var url = (!jQuery(options.url).isEmpty()) ?
options.url : obj.attr("href");

// Concatenate the window.open() function's options.
var features = "width="+options.width+",height="+
options.height+",left="+options.left+",top="+
options.top+",location="+options.location+
",directories="+options.directories+",status="+
options.status+",toolbar="+options.toolbar+
",menubar="+options.menubar+",scrollbars="+
options.scrollbars+",resizable="+
options.resizable;

// Add an onClick() handler to this object.
obj.click(function(){
// On click, open a new window with these arguments.
window.open(url,options.name,features);
return false;
});
});
}
})(jQuery);

/*
* Add a function to jQuery to check null/undefined/blank
* status of object.
*/
(function($){
$.fn.isEmpty = function(){
return (typeof $(this) === "undefined" ||
$(this) === null ||
$(this) === "") ? true : false;
}
})(jQuery);
Note the helper function isEmpty() which serves to check for null, undefined, or blank variables. The folks on StackOverflow.com helped with that function. To use the popWindow() function, ensure you've included the latest jQuery library on your page; add the above code in a script tag; then set the style class on your anchor tag or add an ID so you can reference the object; finally, call it on your page like this:
jQuery(document).ready(function(){
// Set all the objects with pop class to open in new window.
jQuery(".pop").popWindow({
center: 1
});
});
In this case, I'm also taking advantage of the built-in center functionality to center the new window.

14 March 2011

5 simple rules for creating mobile-savvy websites

It's not hard to give smartphone and tablet users compelling access to your sites -- so get started: http://www.infoworld.com/d/mobile-technology/5-simple-rules-creating-mobile-savvy-websites-683

Need to Edit a PDF That Can't Be Edited? Here's How

http://www.pcworld.com/article/222031/need_to_edit_a_pdf_that_cant_be_edited_heres_how.html
What's the point of an electronic document that has to be printed just so you can fill out its fields? Here's one way to work around that restriction.

09 February 2011

Audio files with the JW Flash Player

We're converting some old RM (RealMedia) files to use Flash instead; most are video but a small number are audio-only. To get these to work with the JW Flash Player, I used MediaCoder to convert the files. The container to use is M4A, as discussed in this thread. Here are the settings in MediaCoder:

Start by setting the video to off. Then change the audio settings; note that we set the audio container to MP4. MediaCoder automatically resolves this to M4A for audio-only files:

And finally set the main container type to Default.

07 February 2011

Moving to 64-bit Firefox on Windows 7 x64

Recently, I've noticed that I have a lot of tabs open in Firefox and taking up 700MB or more of memory. Things slow down. What to do? The PC has Win 7 x64 with 8GB of RAM. It might be time to move to the 64-bit version of Firefox and take advantage of the extra legroom on this system.

The problem is how to move the profile from my 32-bit version to the 64-bit "Minefield" beta edition of Firefox 4. The solution? Use the new Sync feature in FF 4, which allows you to store your bookmarks, etc., to a Firefox server, then load the information into another copy of Firefox.

If you open your old 3.6.13 version of Firefox, it, too, will pop up a box to ask if you want to sync things up. Then you can have this be your base version, which holds all your profile. Then when you open the beta 4.0 32-bit, it can pull in the data. And the 64-bit one can also pull the information, too. I'm new to it and haven't yet figured out how the form usernames/passwords are pulled in; the experts say we need to use the 32-bit edition awhile for the sync to pick up all the data.

Either way, this is a fantastic new addition to Firefox. It makes moving profiles so easy. Just ensure that you save your Firefox Sync Key somewhere that you can find it; all other browser versions will require that key to connect.

Some final thoughts: The 32-bit version of the Flash player won't work in the 64-bit version of Firefox; you'll need to download the preview version of Adobe's 64-bit player. In addition, some add-ons won't load via Firefox; you'll have to download the XPI then run them. Firebug loads fine using this technique.

04 February 2011

Cufon not working on Opera

This is an interesting issue: Opera 10.63 for Windows couldn't show the Cufon web font effect, but it worked across all the other browsers. Some research revealed a known issue: If any of your CSS files aren't found, Opera will not be able to apply the Cufon effect.

How do you determine if any of your CSS files are missing? Firefox' Web Developer Toolbar comes to the rescue. Go to the CSS button > View CSS. Now ensure that all the files are expanded so you can see the details. Look for a block similar to the following:
http://localhost:8090/joomla/templates/system/css/system9.css

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">

<head>

<title>Object not found!</title>

<link rev="made" href="mailto:postmaster@localhost" />

<style type="text/css"><!--/*--><![CDATA[/*><!--*/

body { color: #000000; background-color: #FFFFFF; }

a:link { color: #0000CC; }

p, address {margin-left: 3em;}

span {font-size: smaller;}

/*]]>*/--></style>

</head>

<body>

<h1>Object not found!</h1>

<p>

The requested URL was not found on this server.

The link on the

<a href="http://localhost:8090/joomla/">referring

page</a> seems to be wrong or outdated. Please inform the author of

<a href="http://localhost:8090/joomla/">that page</a>

about the error.

</p>

<p>

If you think this is a server error, please contact

the <a href="mailto:postmaster@localhost">webmaster</a>.

</p>

<h2>Error 404</h2>

<address>

<a href="/">localhost</a><br />

<span>02/04/11 15:48:16<br />

Apache/2.2.14 (Win32) DAV/2 mod_ssl/2.2.14 OpenSSL/0.9.8l mod_autoindex_color PHP/5.3.1 mod_apreq2-20090110/2.7.1 mod_perl/2.0.4 Perl/v5.10.1</span>

</address>

</body>

</html>
Note the text "Object not found" (Error 404); Cufon can't handle missing CSS files on Opera and will not render your web font.

02 February 2011

Dynamically replace HTML5 video with Flash in Fancybox

For my current project, I needed to dynamically (based on whether the device supported HTML5 or not) replace HTML5 video tags with a Flash player. What made this more challenging was that the video player needed to be in a Fancybox.

A day's worth of coding resulted in the following JavaScript function. It's heavily commented so it should be self-explanatory. Drop me a comment if something doesn't make sense. You need to include the latest jQuery library for this to work.

function flashPopup(){
// Declare some variables.
var el = "";
var vidFileName = "";
var posterPath = "";
var placeHolderPosterPath = "";
var replacement = "";
var imgTitle = "";
var videoTag = "";
var boxId = "";
var flashId = "";
var dotPosition = "";
var swf = ""

// Loop over each video tag.
$("video").each(function(){
// Reset the variables to empty.
el = "";
vidFileName = "";
posterPath = "";
imgTitle = "";
placeHolderPosterPath = "";
replacement = "";
videoTag = "";
flashId = "";
swf = "";

// Get a reference to the current object.
el = $(this);

// Set some values we'll use shortly.
boxId = this.id + "_flashBox";
flashId = this.id + "_flashPlayer";

/*
Set the ID attribute of the first DIV in this element's parent's
parent. This "box" will have the Fancybox attached to it.
*/
el.parent().parent().find("div:first").attr("id",boxId);


/*
Fetch the MP4/M4V video from the <source> tags. Need to go to the
parent of the current tag to find them.
*/
el.parent().find("source").each(function(){
if ($(this).attr("src").indexOf("m4v") != -1 ||
$(this).attr("src").indexOf("mp4") != -1){
vidFileName = $(this).attr("src").substring($(this).attr("src").lastIndexOf("/")+1);
}
});

/*
IE (< 9) and older browsers use the Flash player, which overlays a 'Play' button
on the poster image by default; so we use a poster image that doesn't have
a play button. Otherwise we'd end up with a play button on top of a play
button.

We have an img tag inside the <video> tag which we'll use (by adding "-ie" to the
filename) for the Flash player's image attribute. The next two lines find the file
and its path.
*/
dotPosition = el.parent().find("img").attr("src").lastIndexOf(".");
posterPath = el.parent().find("img").attr("src").substring(0,dotPosition) + "-ie" + el.parent().find("img").attr("src").substring(dotPosition);

/*
Use the same image but this time don't add "-ie" to its name. This will be for the
main placeholder linked image; when the user clicks it, the Fancybox pops up with
the Flash player.
*/
placeHolderPosterPath = el.parent().find("img").attr("src");

/*
Fetch the image's title attribute, which we'll use for the linked image's title.
This will appear as the title on the Fancybox.
*/
imgTitle = el.parent().find("img").attr("title");

/*
Concatenate the linked image that will take the place of the <video> tag.
*/
replacement = "<a title='" + imgTitle + "' id='" + boxId + "' href='javascript:;'><img src='" +
placeHolderPosterPath + "' style='float:left; padding-left:5px; '/></a>"

// Replace the parent of the current element with the linked image HTML.
el.parent().replaceWith(replacement);

/*
The swfobject library can't be used with Fancybox (as far as I know). but
we can pass a Flash player of our own design into the Fancybox as its content.
Here, we concatenate the Flash player and point to the swfobject player.swf
file. We'll still use that player, but build our own OBJECT and EMBED tags.
*/
swf = "<object id='" + flashId + "' classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' width='372' height='209'>" +
"<param name='movie' value='global/vid/player.swf' />" +
"<param name='quality' value='high' />" +
"<param name='wmode' value='opaque' />" +
"<param name='swfversion' value='6.0.65.0' />" +
"<param name='expressinstall' value='global/vid/expressInstall.swf' />" +
"<!--[if !IE]>--> " +
"<object type='application/x-shockwave-flash' data='global/vid/player.swf' width='372' height='209'> " +
"<!--<![endif]--> " +
"<param name='quality' value='high' /> " +
"<param name='wmode' value='opaque' /> " +
"<param name='swfversion' value='6.0.65.0' /> " +
"<param name=flashvars value='file=" + vidFileName + "&autostart=false&image=" + posterPath + "'>" +
"<param name='expressinstall' value='global/vid/expressInstall.swf' />" +
"<div>" +
"<h4>Content on this page requires a newer version of Adobe Flash Player.</h4>" +
"<p><a href='http://www.adobe.com/go/getflashplayer'><img src='http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif' alt='Get Adobe Flash player' width='112' height='33' /></a></p>" +
"</div>" +
" <!--[if !IE]>--> " +
" </object> " +
" <!--<![endif]--> " +
" </object> ";

/*
Now attach a Fancybox to this item and set its attributes. Note that
we need to disable the autoDimensions for SWF files or you'll get a
long, narrow box showing. We then set the width and height (which
usually requires some trial-and-error).

The two functions for onComplete and onClosed are custom code that
stop/start the AnythingSlider when the user opens the Fancybox and
closes it, respectively.

This entire function acts as an onClick handler for the object to
which it's attached (hence the "end click function" comment).
*/
$("[id="+boxId+"]").fancybox(
{
'content' : swf,
'autoDimensions' :false,
'height' :236,
'width' :375,
'padding' : 5,
'showCloseButton' : true,
'enableEscapeButton': true ,
'titlePosition' : 'outside',
'onComplete' : function() {stopSlider()},
'onClosed' : function() {startSlider()}
}
); // end click function
});
}

Update: A few requests have come in for a version of the code to allow HTML5 videos on a page to be placed into a Fancybox. Here's the demo, with a very special thanks to Pipsqueak.

Here's the HTML code, with the JavaScript followed a little further down:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Fancybox HTML5 Video</title>
<meta name="description" content="">
<meta name="author" content="alex cougarman">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="js/jquery.fancybox-1.3.4/fancybox/jquery.fancybox-1.3.4.css" type="text/css" />
<!--
In addition to the stylesheet for the Fancybox
component, the following styles control the
look of the page and the linked image.
-->
<style type="text/css">
body {
margin:50px 0px; padding:0px;
text-align:center;
font:11px verdana, arial, helvetica, sans-serif;
}

#main {
width:500px;
margin:0px auto;
text-align:left;
padding:15px;
border:1px dashed #333;
background-color:#eee;
}
.img-link
{
border:1px solid #999;
padding:5px
}
</style>
<!--
Include the jQuery library, followed by the
Fancybox and the Easing libraries.
-->
<script type="text/javascript" src="js/jquery.fancybox-1.3.4/jquery-1.4.3.min.js"></script>
<script type="text/javascript" src="js/jquery.fancybox-1.3.4/fancybox/jquery.fancybox-1.3.4.pack.js"></script>
<script type="text/javascript" src="js/jquery.fancybox-1.3.4/fancybox/jquery.easing-1.3.pack.js"></script>

<!-- Add inline script code here. -->

</head>
<body>
<div id="main">
<h3>HTML5 Video in a Fancybox</h3>
<div>
<video id="vid-1" width="480" height="360" poster="btf.jpg" controls preload>
<source src="btf.mp4" type="video/mp4">
<source src="btf.ogv" type='video/ogg; codecs="theora, vorbis"'>
</video>
</div>
</div>
</body>
</html>
Replace the line above in the HTML that says "Add inline script code here." with this JavaScript:
<script type="text/javascript">
$(document).ready(function(){
fancyPopup(); // Replace the video tags.
});

/*
Function finds the video tags on the page. For each,
it fetches the video tag's HTML and replaces it with
a linked image that, when clicked, pops up a Fancybox
containing the HTML5 video.
*/
function fancyPopup() {
// Declare some variables.
var el = "";
var posterPath = "";
var replacement = "";
var videoTag = "";
var fancyBoxId = "";
var posterPath = "";
var videoTitle = "";

// Loop over each video tag.
$("video").each(function () {
// Reset the variables to empty.
el = "";
posterPath = "";
replacement = "";
videoTag = "";
fancyBoxId = "";
posterPath = "";
videoTitle = "";

// Get a reference to the current object.
el = $(this);

// Set some values we'll use shortly.
fancyBoxId = this.id + "_fancyBox";
videoTag = el.parent().html(); // This gets the current video tag and stores it.
posterPath = el.attr("poster");
videoTitle = "Play Video " + this.id;

// Concatenate the linked image that will take the place of the <video> tag.
replacement = "<a title='" + videoTitle + "' id='" + fancyBoxId + "' href='javascript:;'><img src='" +
posterPath + "' class='img-link'/></a>"

// Replace the parent of the current element with the linked image HTML.
el.parent().replaceWith(replacement);

/*
Now attach a Fancybox to this item and set its attributes.

This entire function acts as an onClick handler for the object to
which it's attached (hence the "end click function" comment).
*/
$("[id=" + fancyBoxId + "]").fancybox(
{
'content': videoTag,
'title': videoTitle,
'autoDimensions': true,
'padding': 5,
'showCloseButton': true,
'enableEscapeButton': true,
'titlePosition': 'outside',
}); // end click function
});
}
</script>
Replace the two videos (btf.mp4 and btf.ogv) with your own HTML5-compatible videos and try it. Demo, with a very special thanks to Pipsqueak.

Update: The good folks at Long Tail Video provide a great HTML5 video tag reference.

31 January 2011

30 impressive examples of web design layout

http://www.bestfreewebresources.com/2011/01/30-impressive-examples-of-web-design-layout.html

jQuery Mobile: Touch-Optimized Web Framework for Smartphones & Tablets

"A unified user interface system across all popular mobile device platforms, built on the rock-solid jQuery and jQuery UI foundation. Its lightweight code is built with progressive enhancement, and has a flexible, easily themeable design."

Escape the App Store: 4 ways to create smartphone Web apps

A very nice article on the merits and drawbacks of building HTML apps for handhelds. An excerpt:
It doesn't matter why you want to develop mobile apps. The problem is that following through is still pretty tricky. The iPhone and iPad want code written in Objective-C, and that language remains a bit obscure. Plus, once you're done coding, there's a distinct possibility that Apple will slam the door to the App Store right in your face for some unknowable reason.

28 January 2011

Getting contacts off of a (near-dead) cell phone

This is not related to web development, but might help someone. The hinge on my Verizon Motorola VU204 phone gave out a few weeks back, and the screen portion completely came off last week. I could still get calls and use my Bluetooth headset to answer them; but I couldn't get to my contacts. What to do...

I tried the Verizon store, but they couldn't get the data off the phone; they suggested using the free BitPIM software.

First, I needed a USB cable with full-to-mini connectors. Then my computer needed the USB drivers for the phone, available from Motorola. But the BitPIM couldn't see the phone. What now?

It took some trial-and-error: I ran the "Detect Phone" again. It gave this error:

I chose "Yes" to run the settings. In settings, I changed the Phone Type to V710m (as was suggested in this post). You can also use the Phone Wizard.

Click the Browse button to search for your phone's Com Port; in my case, it was COM4. Click OK to close the main dialog box. To test to see if it's connecting to the phone, go to View > Phone Info.

Now go to Data > Get Phone Data. Check all the boxes for the items you'd like to get from the phone and click OK. The phone will now run through each of the items from that list and retrieve them. In my case, it only got the contacts, which is the main thing I needed. It will pop up a box of the data for each item you chose. Close out of these.

To see the contacts, click the Phonebook item in the tree listing of BitPIM. To save the data, go to File > Export > CSV Contacts (or other format depending on you needs). It will save your contacts in a *.csv file.

After you've saved the contacts and are certain they're there, you can delete your contacts from your old phone by selecting them all under the Phonebook, clicking Edit > Delete. Now go to Data > Send Phone Data. Choose the Phonebook, and click OK. This will delete all your contacts from the phone. Neat!

21 January 2011

Fast recursive directory search with .NET

For a current project, I needed to traverse a document tree and find all the MP4 files, store their properties in a custom class, then search on each file's name using the custom class' property. Then an HTML anchor tag needed to be created so we could change the Real Media file links to point to the MP4 version of the files; the new link would include some JavaScript for the onClick event to pop up a video player for the MP4 file.

Here's the simple custom File class. If you'd like to copy any of the code, simply double-click in the code to select all of it, then use Ctrl/Cmd-C to copy.
Public Class File
Private myFilePath As String
Private myFileName As String
Private myTopLevelFolder As String
Private myCurrentFolder As String

Public Property FilePath As String
Get
Return Me.myFilePath
End Get
Set(ByVal value As String)
Me.myFilePath = value
End Set
End Property

Public Property FileName As String
Get
Return Me.myFileName
End Get
Set(ByVal value As String)
Me.myFileName = value
End Set
End Property

Public Property TopLevelFolder As String
Get
Return Me.myTopLevelFolder
End Get
Set(ByVal value As String)
Me.myTopLevelFolder = value
End Set
End Property

Public Property CurrentFolder As String
Get
Return Me.myCurrentFolder
End Get
Set(ByVal value As String)
Me.myCurrentFolder = value
End Set
End Property
End Class
I started with the great code from the Java2S Site, which searches a directory recursively. For this project, I considered using an ArrayList, because these data structures have great, built-in search functions. A closer examination and some research revealed a better solution: the .NET Dictionary class, which provides great performance for searches.

The Dictionary object stores name-value pairs and functions much like a hash table, but with one major difference: no overhead for boxing and unboxing, as is the case with hash tables. It required just a few changes to the code to use a Dictionary to store the values, and then to search on the filename and path (the same filename might be in multiple directories, so we need to use the path as well to uniquely identify the file).

Here's the main form's code:
' VB.NET folder search code courtesy
' http://www.java2s.com/Code/VB/File-Directory/Findafilesearchdirectoryrecursively.htm

Imports System
Imports System.Windows.Forms
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections.Specialized
Imports System.Text

Public Class FrmFileSearch
Inherits Form

' label that displays current directory
Friend WithEvents lblDirectory As Label

' label that displays directions to user
Friend WithEvents lblDirections As Label

' button that activates search
Friend WithEvents cmdSearch As Button

' text boxes for inputting and outputting data
Friend WithEvents txtInput As TextBox
Friend WithEvents txtOutput As TextBox

#Region " Windows Form Designer generated code "

Public Sub New()
MyBase.New()

'This call is required by the Windows Form Designer.
InitializeComponent()

'Add any initialization after the InitializeComponent() call

End Sub

'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub

'Required by the Windows Form Designer
Private components As System.ComponentModel.Container

'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.txtOutput = New System.Windows.Forms.TextBox()
Me.lblDirections = New System.Windows.Forms.Label()
Me.lblDirectory = New System.Windows.Forms.Label()
Me.txtInput = New System.Windows.Forms.TextBox()
Me.cmdSearch = New System.Windows.Forms.Button()
Me.SuspendLayout()
'
'txtOutput
'
Me.txtOutput.BackColor = System.Drawing.SystemColors.Control
Me.txtOutput.Font = New System.Drawing.Font("Microsoft Sans Serif", 9.75!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
Me.txtOutput.HideSelection = False
Me.txtOutput.Location = New System.Drawing.Point(17, 134)
Me.txtOutput.Multiline = True
Me.txtOutput.Name = "txtOutput"
Me.txtOutput.ReadOnly = True
Me.txtOutput.ScrollBars = System.Windows.Forms.ScrollBars.Vertical
Me.txtOutput.Size = New System.Drawing.Size(460, 61)
Me.txtOutput.TabIndex = 4
Me.txtOutput.UseWaitCursor = True
'
'lblDirections
'
Me.lblDirections.Font = New System.Drawing.Font("Microsoft Sans Serif", 10.0!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
Me.lblDirections.Location = New System.Drawing.Point(17, 82)
Me.lblDirections.Name = "lblDirections"
Me.lblDirections.Size = New System.Drawing.Size(375, 17)
Me.lblDirections.TabIndex = 1
Me.lblDirections.Text = "Enter Path to Search:"
'
'lblDirectory
'
Me.lblDirectory.Font = New System.Drawing.Font("Microsoft Sans Serif", 10.0!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
Me.lblDirectory.Location = New System.Drawing.Point(17, 17)
Me.lblDirectory.Name = "lblDirectory"
Me.lblDirectory.Size = New System.Drawing.Size(375, 85)
Me.lblDirectory.TabIndex = 0
Me.lblDirectory.Text = "Current Directory:"
'
'txtInput
'
Me.txtInput.Location = New System.Drawing.Point(17, 108)
Me.txtInput.Name = "txtInput"
Me.txtInput.Size = New System.Drawing.Size(334, 20)
Me.txtInput.TabIndex = 3
Me.txtInput.Text = "c:\RM_Conversion\"
'
'cmdSearch
'
Me.cmdSearch.Font = New System.Drawing.Font("Microsoft Sans Serif", 8.0!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
Me.cmdSearch.Location = New System.Drawing.Point(355, 108)
Me.cmdSearch.Name = "cmdSearch"
Me.cmdSearch.Size = New System.Drawing.Size(122, 20)
Me.cmdSearch.TabIndex = 2
Me.cmdSearch.Text = "Search Directory"
'
'FrmFileSearch
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(501, 208)
Me.Controls.Add(Me.txtOutput)
Me.Controls.Add(Me.txtInput)
Me.Controls.Add(Me.cmdSearch)
Me.Controls.Add(Me.lblDirections)
Me.Controls.Add(Me.lblDirectory)
Me.Name = "FrmFileSearch"
Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen
Me.Text = "Using Regular Expressions"
Me.ResumeLayout(False)
Me.PerformLayout()

End Sub

#End Region

Dim currentDirectory As String = Directory.GetCurrentDirectory
Dim directoryList As String()
Dim fileArray As String()

'Dim files As New ArrayList()
Dim files As Dictionary(Of String, File) = New Dictionary(Of String, File)

Dim found As NameValueCollection = New NameValueCollection()


Private Sub txtInput_KeyDown(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.KeyEventArgs) _
Handles txtInput.KeyDown

If (e.KeyCode = Keys.Enter) Then
cmdSearch_Click(sender, e)
End If

End Sub

Private Sub cmdSearch_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles cmdSearch.Click

Dim onClick As String
Dim myFile As File
Dim builder As New StringBuilder
Dim count As Integer = 0
Dim startTime As DateTime = DateTime.Now
Dim image As String
Dim save As Boolean
Dim ver5FileName As String = String.Empty
Dim ver2FileName As String = String.Empty
Dim finalFileName As String = String.Empty

Dim foundFile As Boolean = False

If txtInput.Text <> "" Then

' verify that user input is a valid directory name
If Directory.Exists(txtInput.Text) Then
currentDirectory = txtInput.Text

' reset input text box and update display
lblDirectory.Text = "Current Directory:" & vbCrLf & _
currentDirectory

' show error if user does not specify valid directory
Else
MessageBox.Show("Invalid Directory", "Error", _
MessageBoxButtons.OK, MessageBoxIcon.Error)

Return
End If

End If

' clear text boxes
txtInput.Text = ""
txtOutput.Text = ""

onClick = " onclick=" & Chr(34) & "window.open(this.href,'popup','width=347,height=322,scrollbars=no,resizable=no,toolbar=no,directories=no,location=no,menubar=no,status=no,left=0,top=0'); return false" & Chr(34) & ">Play Video</a>"

' search directory
SearchDirectory(currentDirectory)

count = files.Count

' Loop over each key-value pair object
For Each kvp As KeyValuePair(Of String, File) In files
foundFile = False
finalFileName = String.Empty

' Convert the object to the custom File type.
myFile = TryCast(kvp.Value, File)

' Thumbnail image used by the Flash player.
image = "generic.jpg"

' We'll use a StringBuilder for its performance.
builder.Append("File: ")
builder.Append(myFile.CurrentFolder.Replace("\", "/")).AppendLine().AppendLine()

builder.Append("<a href=")
builder.Append(Chr(34))
builder.Append("/mp4-conv/player.htm?file=http://flash.mydomain.com/mp4-conv/")
builder.Append(myFile.CurrentFolder.Replace("\", "/"))
builder.Append("ℑ=/mp4-conv/posters/")
builder.Append(image)
builder.Append(Chr(34))
builder.Append(onClick).AppendLine()
builder.Append("----------------------------------------").AppendLine.AppendLine()
Next kvp

' Clear output for new search.
found.Clear()

' Calculate the execution time using the TimeSpan class.
Dim executionTime As TimeSpan = DateTime.Now - startTime

' Only put general processing info into the textbox for best performance.
txtOutput.Text &= count.ToString & " files processed in " &_
executionTime.Minutes.ToString() & " minutes and " &_
executionTime.Seconds.ToString() & " seconds."

Dim path As String = "c:\RM_Conversion\links.txt"

' If our text file already exists, delete it.
' We'll write the HTML anchor tags to this file.
If System.IO.File.Exists(path) Then
System.IO.File.Delete(path)
Else ' If text file doesn't exist, create it.
System.IO.File.Create(path)
End If

' Write the StringBuilder object to the text file.
' Fetch whether the write was successful (Boolean).
save = SaveTextToFile(builder.ToString, path)

txtOutput.Text &= vbCrLf & "Wrote to " &_
path & "? " & save
files.Clear()

End Sub ' cmdSearch_Click

' Write to a file.
Public Function SaveTextToFile(ByVal strData As String, _
ByVal FullPath As String, _
Optional ByVal ErrInfo As String = "") As Boolean

Dim bAns As Boolean = False
Dim objReader As StreamWriter
Try
objReader = New StreamWriter(FullPath)
objReader.Write(strData)
objReader.Close()
bAns = True
Catch Ex As Exception
ErrInfo = Ex.Message
End Try
Return bAns
End Function

' Dirty way to determine the top level folder in the path.
' There's a way to do this in reg exp, but this worked :)
Private Function GetTopLevelFolder(ByVal filePath As String) As String
Dim arrChar As Array = filePath.Substring(filePath.LastIndexOf("RM_Conversion\") + 7).ToCharArray()
Dim value As String = ""

For i As Integer = 0 To arrChar.Length - 1
If arrChar(i) <> "\" Then
value &= arrChar(i)
Else
Exit For
End If
Next

Return value
End Function


' search directory using regular expression
Private Sub SearchDirectory(ByVal currentDirectory As String)

' for file name without directory path
Try
Dim fileName As String = ""
Dim myFile As String
Dim myDirectory As String
Dim myFileObj As File
Dim currentFolder As String

' regular expression for extensions matching pattern
Dim regularExpression As Regex = _
New Regex("([a-zA-Z0-9]+\.(?<extension>\w+))")

' stores regular-expression-match result
Dim matchResult As Match

Dim fileExtension As String ' holds file extensions

' number of files with given extension in directory
Dim extensionCount As Integer

' get directories
directoryList = _
Directory.GetDirectories(currentDirectory)

' get list of files in current directory
fileArray = Directory.GetFiles(currentDirectory, "*.mp4")

' iterate through list of files
For Each myFile In fileArray
fileName = myFile.Substring( _
myFile.LastIndexOf("\") + 1)

matchResult = regularExpression.Match(fileName)


If (matchResult.Success) Then
fileExtension = matchResult.Result("${extension}")
Else
fileExtension = "[no extension]"
End If


If (found(fileExtension) = Nothing) Then
currentFolder = myFile.ToString().Substring(currentDirectory.LastIndexOf("RM_Conversion\") + 7).Replace("\", "/")

myFileObj = New File()
myFileObj.FileName = fileName
myFileObj.FilePath = myFile.ToString()
myFileObj.TopLevelFolder = Me.GetTopLevelFolder(myFile.ToString())
myFileObj.CurrentFolder = currentFolder
files.Add(myFile.ToString(), myFileObj)
Else
extensionCount = _
Convert.ToInt32(found(fileExtension)) + 1

found(fileExtension) = extensionCount.ToString()
End If
Next

For Each myDirectory In directoryList
SearchDirectory(myDirectory)
Next

Catch unauthorizedAccess As UnauthorizedAccessException
MessageBox.Show("Some files may not be visible due to" _
& " permission settings", "Warning", _
MessageBoxButtons.OK, MessageBoxIcon.Information)

End Try
End Sub
End Class

Public Class MainClass

Shared Sub Main()
Dim myform As Form = New FrmFileSearch()
Application.Run(myform)
End Sub ' Main

End Class

Fewer high school students taking computer science classes

This article points out some disturbing trends in education, and this is not just true at the high school level, but in higher education too. The articles are a few years old; not sure if the pattern holds into the present.

An excerpt from the first article, about high schools,
Nationally, the portion of schools that offer an introductory computer science course has dropped from 78 percent in 2005 to 65 percent this year, and the corresponding decline in AP courses went from 40 to 27 percent, according to a survey by the Computer Science Teachers Association.