Thursday, September 10, 2009

Simple Slideshow System: S5

S5 has been around a few years now, but some may not be aware of it. S5 stands for 'Simple Standards-Based Slide Show System'. The XHTML code is simple to modify (adding / removing slides) and understand. Styling via CSS is also very simple to do. It gracefully degrades - so if JavaScript is disabled, you can still see the content. Slideshows are also printer friendly.

Like a slideshow you would do in Microsoft Office PowerPoint or OpenOffice Impress, you have to make sure not too much content is added to a slide, since any overflow is hidden. The keyboard or mouse can be used to navigate the S5 slideshow (although there are no context menu options).

Wednesday, September 09, 2009

WebSlide

What is WebSlide? WebSlide is a JavaScript based web application front-end for presenting mockups and other images in a web browser. Click in the demo area and either click buttons with your mouse, or use shortcut keys →, ←, z, x, and n. For more information on using and setting up a WebSlide, view the online documentation. Features
  • No server-side requirements
  • User-configurable
  • Create custom themes with CSS
  • Keyboard navigation
  • Zoom images in & out
  • Optional password protection
Visit WebSlide | Full Screen Demo

Sunday, September 06, 2009

Google Wave: 5 Ways It Could Change the Web

Google Wave arrives on September 30th. On that day, Google will start sending out 100,000 invites to non-developers to its much-anticipated real-time communication platform. An interesting article '5 ways Google Wave could change the web' has been published over at Mashable.

1. Wave-Powered Forums
2. Wave-powered Commenting System
3. Wave-Based Content Management System
4. Wave for Customer Support
5. Wave for Education

Read the full article over here

Saturday, September 05, 2009

Microsoft.NET Framework directory locations

Since there are directories all over the place for the Microsoft.NET Framework, I have decided to compile a list of some of the key ones used.

Compilers, GACUtil, ASP.NET Register IIS/SQL, Assemblies etc.

.NET 1.0: C:\Windows\Microsoft.NET\Framework\v1.0.3705

.NET 1.1: C:\Windows\Microsoft.NET\Framework\v1.1.4322

.NET 2.0: C:\Windows\Microsoft.NET\Framework\v2.0.50727

After 2.0, more directories were added, the build number was also omitted from the directory name. Some key utilities (for Global Assembly Cache (GAC), ASP.NET, Code Access Security Policy Tool (caspol.exe)) weren't part of future versions, so, as a result, .NET 2.0 is required by them.

.NET 3.0 uses .NET 2.0 compiler. New directories:

  • C:\Windows\Microsoft.NET\Framework\v3.0\Windows Communication Foundation
  • C:\Windows\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation (only SQL scripts in here)
  • C:\Windows\Microsoft.NET\Framework\v3.0\WPF (Windows Presentation Foundation)
  • C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0 (for reference when compiling / developing)

.NET 3.5

  • C:\Windows\Microsoft.NET\Framework\v3.5 (compiler, WPF, MSBuild)
  • C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5 (for reference when compiling / developing)

Wednesday, September 02, 2009

IxEdit

IxEdit is a JavaScript-based interaction design tool for the web. With IxEdit, designers can practice DOM-scripting without coding to change, add, move, or transform elements dynamically on your web pages. Especially, IxEdit must be useful to try various interactions rapidly in the prototyping phase of your web application.

Website: http://www.ixedit.com/ | Demo: http://www.ixedit.com/samples/

Tuesday, September 01, 2009

jQTouch

jQTouch is a jQuery plugin for creating mobile applications with just HTML, CSS & JavaScript.

A jQuery plugin for mobile web development on the iPhone, Android, Palm Pre, and other forward-thinking devices.

Link: http://www.jqtouch.com/

Monday, August 24, 2009

New a CMS - you need GetSimple.

GetSimple is an XML based lite Content Management System. To go along with it’s best-in-class user interface, its loaded with features that every website needs. GetSimple is truly the simplest way to manage a small-business website + no MySQL Database required! its all, as above, managed by XML awesome, Check out the site, download it and check it out!

URL: http://get-simple.info/

Friday, August 21, 2009

jQuery newsticker: SharePoint WebPart

It is possible to integrate the jQuery newsticker plugin into a SharePoint site using a WebPart: News Ticker Web Part.

SmartTools is used to integrate jQuery into SharePoint, via the SmartTools.jQuery component.

Anyone else integrated the newsticker plugin into a module / add-on for a Web CMS?

Monday, July 27, 2009

Visual Studio Macro: Wrap Selected Text in Tag

This simple macro allows you to wrap whatever text is selected in a tag - a feature that somehow is missing from Visual Studio 2008...

    Sub SurroundWithTag()
        Dim selectedText = DTE.ActiveDocument.Selection.Text
        Dim tag As String
        tag = InputBox("Enter tag name", "Tag", "strong")
        If (tag = "") Then
            MsgBox("No tag defined")
        Else
            ' no end tag needed as Visual Studio creates the end tag..
            DTE.ActiveDocument.Selection.Text = String.Format("<{0}>{1}", tag, selectedText)
        End If
    End Sub

Based on the answer to this question found on Stack Overflow: Macro to wrap selected text with tags in Visual Studio (which also details how you can create a macro)

Wednesday, July 08, 2009

jQuery Quick Tip: Extract CSS Background Image

jQuery allows you to get the background image of any element on a web page:

$("#myelement").css("background-image");

However, this returns it in an undesirable format: url(http://example.com/images/image.jpg) or url("http://example.com/images/image.jpg"). With a bit of string replacement, you can get extract the URL:

function extractUrl(input)
{
 // remove quotes and wrapping url()
 return input.replace(/"/g,"").replace(/url\(|\)$/ig, "");
}

So now you can just do this:

extractUrl($("#myelement").css("background-image"))

Which will return the URL on its own http://example.com/images/image.jpg.

Monday, May 18, 2009

ASP.NET Snippet: Delete All Users and Roles

Deleting all users and related data from a site using ASP.NET authentication is fairly simple. Useful if copying a database and you want to remove all the users, but keep the structure and other data (e.g. user content) intact.

Delete all users:

foreach (MembershipUser u in Membership.GetAllUsers())
{
 Membership.DeleteUser(u.UserName, true);
}

Delete all roles:

foreach (string role in Roles.GetAllRoles())
{
 Roles.DeleteRole(role);
}

Friday, May 15, 2009

ASP.NET Snippet: Quick Password Reset

Here is a simple way, via code to reset a password when you are using the built-in ASP.NET authentication system. Useful if you either don't have a reset password form, or you just want to quickly change a password. Create a blank page, and place in the code behind Page_Load event.

C#

    protected void Page_Load(object sender, EventArgs e)
    {
        MembershipUser u = Membership.FindUsersByName("Username")["Username"];
        u.UnlockUser();
        u.ChangePassword(u.ResetPassword(), "newpassword");
    }

VB

    Public Sub Page_Load(sender As Object, e As EventArgs)
        Dim u As MembershipUser = Membership.FindUsersByName("Username")("Username")
        u.UnlockUser()
        u.ChangePassword(u.ResetPassword(), "newpassword")
    End Sub

Just delete the page when done.

Update (8 July 2009): If a question and answer is required when you create a user, you have to pass on the answer to u.ResetPassword, e.g. u.ChangePassword(u.ResetPassword("answer"), "newpassword"), otherwise ResetPassword won't work.

Tuesday, May 05, 2009

Get ListControl values (CheckBoxList, RadioButtonList, DropDownList, ListBox) from a Repeater the easy way (ASP.NET C#)

Building on Get TextBox values from a Repeater the easy way (ASP.NET C#), here is a function that will get the selected values from WebControls that inherit from ListControl (CheckBoxList, RadioButtonList, DropDownList and ListBox)

It returns null if the ListControl matching the given ID does not exist in the repeater or an empty string array if nothing is selected.

public string[] ListControlValues(RepeaterItem itm, string controlId)
{
 string[] output = null;
 ListControl t = itm.FindControl(controlId) as ListControl;
 if (t != null)
 {
  ArrayList ar = new ArrayList();
  foreach (ListItem li in t.Items)
  {
   if (li.Selected) ar.Add(li.Value);
  }
  output = (string[])ar.ToArray(typeof(string));
 }
 return output;
}

Use in Repeater ItemCommand

protected void MyRepeater_ItemCommand(object sender, RepeaterCommandEventArgs e)
{
 switch (e.CommandName.ToString())
 {
  case "Save":
   int recordID = Convert.ToInt32(e.CommandArgument);
   string[] listBoxValues = ListControlValues("ListBox1");
   if(listBoxValues != null)
   {
     foreach(string s in listBoxValues)
     {
      
     }
   }
   string[] dropDownValues = ListControlValues("DropDown1");
   if(dropDownValues != null)
   {
    // since dropdown can only have one item selected...
    string dropDownValue = dropDownValues[0];
   }
   ....
   
   break;
  default:
   break;
 }
}

Friday, May 01, 2009

Get TextBox values from a Repeater the easy way (ASP.NET C#)

The asp:Repeater control allows you to attach an ItemCommand event to it, for instance, updating a record in a database.

<asp:Repeater ID="MyRepeater" DataSource='<%# MyData %>' runat="server" 
 OnItemCommand="MyRepeater_ItemCommand">
 <HeaderTemplate><ul></HeaderTemplate>
 <ItemTemplate><li>
 Url: <asp:TextBox ID="Urdl" Text='<%# Eval("Url") %>' runat="server" />
 Text: <asp:TextBox ID="Text" Text='<%# Eval("Text") %>' runat="server" />
 <asp:Button ID="SaveButton" CommandName="Save" CommandArgument='<%# Eval("RecordID") %>' Text="Save" runat="server" />
 </li></ItemTemplate>
 <FooterTemplate></ul></FooterTemplate>
</asp:Repeater>

MyRepeater_ItemCommand is then defined:

protected void MyRepeater_ItemCommand(object sender, RepeaterCommandEventArgs e)
{
 switch (e.CommandName.ToString())
 {
  case "Save":
   int recordID = Convert.ToInt32(e.CommandArgument);
   TextBox url = e.Item.FindControl("Url") as TextBox;
   TextBox text = e.Item.FindControl("Text") as TextBox;
   
   ....
   
   break;
  default:
   break;
 }
}

However this has several downsides:

  1. If you mistype the control id, you get a NullReferenceException
  2. The code can get cluttered and harder to understand as more TextBox's are added.

To help mitigate this, this function gets the value of the TextBox, or null if the TextBox is not found.

public string TextBoxValue(RepeaterItem itm, string controlId)
{
 string output = null;
 TextBox t = itm.FindControl(controlId) as TextBox;
 if (t != null)
 {
  output = t.Text;
 }
 return output;
}

Then you can get the value without an exception occurring:

string url = TextBoxValue(e.Item, "Url");
string text = TextBoxValue(e.Item, "Text");
if(url != null)
{
    // update url in record
}
if(text == null)
{
    // update text in record
}

The code could then be used in a class and used on multiple pages, so if a page doesn't have a TextBox with a certain ID, the corresponding data field is not updated (e.g. on one page you might want a user to update the URL, but on another more restricted page, you may only want them to change the text). Code behind would then just be:

protected void MyRepeater_ItemCommand(object sender, RepeaterCommandEventArgs e)
{
 RepeaterMethods.MyRepeater_ItemCommand(sender, e);
}

And the class could be:

using System;
using System.Collections.Generic;
using System.Web;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;

/// <summary>
/// Summary description for RepeaterMethods
/// </summary>
public class RepeaterMethods
{
 public RepeaterMethods()
 {
  //
  // TODO: Add constructor logic here
  //
 }

 public static void MyRepeater_ItemCommand(object sender, RepeaterCommandEventArgs e)
 {
  switch (e.CommandName.ToString())
  {
   case "Save":
    string url = TextBoxValue(e.Item, "Url");
    string text = TextBoxValue(e.Item, "Text");
    if (url != null)
    {
     // update url in record
    }
    if (text == null)
    {
     // update text in record
    }
    break;
   default:
    break;
  }
 }

 public static string TextBoxValue(RepeaterItem itm, string controlId)
 {
  string output = null;
  TextBox t = itm.FindControl(controlId) as TextBox;
  if (t != null)
  {
   output = t.Text;
  }
  return output;
 }
}

Binding data to a Repeater using Lazy Loading (ASP.NET C#)

Lazy loading is a method of only loading data as and when you need it. Rather than loading it on page load, you can define a property that can then be bound to a WebControl.

For example, in the page is an asp:Repeater:

        <asp:Repeater ID="MyRepeater" DataSource='<%# MyData %>' runat="server">
            <HeaderTemplate><ul></HeaderTemplate>
            <ItemTemplate><li><a href="<%# Eval("Url") %>"><%# Eval("Text") %></a> (Record ID: <%# Eval("RecordID") %>)</li></ItemTemplate>
            <FooterTemplate></ul></FooterTemplate>
        </asp:Repeater>

MyData in the DataSource attribute of asp:Repeater is a property defined in the CodeBehind page:

    private DataTable _MyData;
    public DataTable MyData
    {
        get
        {
            if (_MyData == null)
            {
                _MyData = LoadMyData();
            }
            return _MyData;
        }
    }

LoadMyData gets the data you want to show (normally it would be loaded from a database, rather than rows added in this case)

    private DataTable LoadMyData()
    {
        Trace.Write("LoadMyData");
        DataTable dt = new DataTable();
        dt.Columns.Add(new DataColumn("RecordID", Type.GetType("System.Int32")));
        dt.Columns.Add(new DataColumn("Text", Type.GetType("System.String")));
        dt.Columns.Add(new DataColumn("Url", Type.GetType("System.String")));

        DataRow row;
        int index = 1;

        // new row
        row = dt.NewRow();
        // populate row
        row["RecordID"] = index;
        row["Text"] = "Google";
        row["Url"] = "http://www.google.co.uk";
        // add row to table
        dt.Rows.Add(row);

        // increment index
        index++;

        // new row
        row = dt.NewRow();
        // populate row
        row["RecordID"] = index;
        row["Text"] = "Yahoo";
        row["Url"] = "http://www.yahoo.co.uk";
        // add row to table
        dt.Rows.Add(row);

        return dt;
    }

The Trace.Write is there to show that the data isn't loaded if it is not bound to a WebControl in the page. This can be tested by turning on tracing (adding Trace="True" to <%@ Page) and removing DataSource='<%# MyData %>' from the Repeater.

You would then bind the page on load:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            DataBind();
        }
    }

LazyLoadRepeater.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="LazyLoadRepeater.aspx.cs" Inherits="LazyLoadRepeater" Trace="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Lazy Loading</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>Search Engines</h1>
        <asp:Repeater ID="MyRepeater" DataSource='<%# MyData %>' runat="server">
            <HeaderTemplate><ul></HeaderTemplate>
            <ItemTemplate><li><a href="<%# Eval("Url") %>"><%# Eval("Text") %></a> (Record ID: <%# Eval("RecordID") %>)</li></ItemTemplate>
            <FooterTemplate></ul></FooterTemplate>
        </asp:Repeater>
    </div>
    </form>
</body>
</html>

LazyLoadRepeater.aspx.cs:

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;

public partial class LazyLoadRepeater : System.Web.UI.Page
{
    private DataTable _MyData;
    public DataTable MyData
    {
        get
        {
            if (_MyData == null)
            {
                _MyData = LoadMyData();
            }
            return _MyData;
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            DataBind();
        }
    }

    private DataTable LoadMyData()
    {
        Trace.Write("LoadMyData");
        DataTable dt = new DataTable();
        dt.Columns.Add(new DataColumn("RecordID", Type.GetType("System.Int32")));
        dt.Columns.Add(new DataColumn("Text", Type.GetType("System.String")));
        dt.Columns.Add(new DataColumn("Url", Type.GetType("System.String")));

        DataRow row;
        int index = 1;

        // new row
        row = dt.NewRow();
        // populate row
        row["RecordID"] = index;
        row["Text"] = "Google";
        row["Url"] = "http://www.google.co.uk";
        // add row to table
        dt.Rows.Add(row);

        // increment index
        index++;

        // new row
        row = dt.NewRow();
        // populate row
        row["RecordID"] = index;
        row["Text"] = "Yahoo";
        row["Url"] = "http://www.yahoo.co.uk";
        // add row to table
        dt.Rows.Add(row);

        return dt;
    }
}

Thursday, April 16, 2009

Chinook: Cross-Database (SQL Server, Oracle, MySQL) Sample

The Chinook database is a sample database that is an alternative to the Northwind sample. One of the advantages it has over Northwind is that it can be installed on SQL Server (including the Compact Edition), Oracle and MySQL. Therefore it can be used to test Object-relational mapping (ORM) frameworks (e.g. NHibernate, SubSonic etc).

Wednesday, April 15, 2009

Code Generation with MyGeneration

Rather than type repetitive code, a code generator could be used to do it for you. MyGeneration is one such piece of software that will help. As the output is just text, you can output code in any language (ASP.NET, C#, VB.NET, PHP, JavaScript etc) - although with a bit of extra work, PDF's can be generated (using iTextSharp - which would need to be downloaded and the dll saved in the MyGeneration install folder). It can query many database types (SQL Server, Oracle, SQLite, MySQL, PostgreSQL and more), but a database isn't required for it to be of use. You can set up an interface (to select columns in a table for example), but it is optional.

For example, to loop through columns in a database table:

Template Code

<%
public class GeneratedTemplate : DotNetScriptTemplate
{
 public GeneratedTemplate(ZeusContext context) : base(context) {}
 
 private string DatabaseName;
 private string TableName;
 private ITable table;
 private ArrayList columns;
 private string PrimaryKey = string.Empty;

 //---------------------------------------------------
 // Render() is where you want to write your logic    
 //---------------------------------------------------
 public override void Render()
 {
  DatabaseName = input["databaseName"].ToString();
  TableName = input["tableName"].ToString();
  table = MyMeta.Databases[DatabaseName].Tables[TableName];
  columns = input["lstColumns"] as ArrayList;
  try
  {
   PrimaryKey = table.PrimaryKeys[0].Name; 
  }
  catch (System.Exception ex)
  {
   // no primary key
  }
  foreach (string columnName in columns)
  {
  %>
  <li><%= columnName %></li><%
  }
 }
 
 // check if column has been selected via UI
 private bool IsInColumns(Column c)
 {
  foreach (string columnName in columns)
  {
   if (columnName == c.Name) return true;
  }
  return false;
 }
}
%>

Interface Code

public class GeneratedGui : DotNetScriptGui
{
 public GeneratedGui(ZeusContext context) : base(context) {}
 
 private string DatabaseName;
 private string TableName;
 private string PrimaryKey = string.Empty;
 private ArrayList columns;
 private ITable table;

 //-----------------------------------------
 // The User Interface Entry Point
 //-----------------------------------------
 public override void Setup()
 {

  ui.Width  = 500;
  ui.Height = 350;
  
  // Setup Database selection combobox.
  GuiLabel lblDatabases = ui.AddLabel("lblDatabases", "Select a database:", "Select a database in the dropdown below.");
  GuiComboBox cmbDatabases = ui.AddComboBox("databaseName", "Select a database.");
  
  // Setup Tables selection multi-select listbox.
  GuiLabel lblTables = ui.AddLabel("lblTables", "Select table:", "Select table from the combobox below.");
  GuiComboBox cmbTables = ui.AddComboBox("tableName", "Select a table.");
  
  // setup columns list box
  GuiLabel lblColumns = ui.AddLabel("lblColumns", "Select columns:", "Select columns from the listbox below.");
  GuiListBox lstColumns = ui.AddListBox("lstColumns", "Select columns.");
  
  lstColumns.Height = 130;
  
  // bind data to the controls
  cmbDatabases.BindData(MyMeta.Databases);
  cmbDatabases.SelectedValue = MyMeta.DefaultDatabase.Name;
  cmbTables.BindData( MyMeta.Databases[cmbDatabases.SelectedValue].Tables );
  
  // Attach the onchange event to the cmbDatabases control.
  cmbDatabases.AttachEvent("onchange", "cmbDatabases_onchange");
  cmbTables.AttachEvent("onchange", "cmbTables_onchange");
  
  ui.ShowGui = true;
 }
 
 public void cmbDatabases_onchange(GuiComboBox control)
 {
  GuiComboBox cmbDatabases = ui["databaseName"] as GuiComboBox;
  GuiComboBox cmbTables = ui["tableName"] as GuiComboBox;
  cmbTables.BindData( MyMeta.Databases[cmbDatabases.SelectedValue].Tables );
  GuiListBox lstColumns = ui["lstColumns"] as GuiListBox;
  lstColumns.Clear();
 }
 
 public void cmbTables_onchange(GuiComboBox control)
 {
  try 
  {
   GuiComboBox cmbDatabases = ui["databaseName"] as GuiComboBox;
   GuiComboBox cmbTables = ui["tableName"] as GuiComboBox;
   GuiListBox lstColumns = ui["lstColumns"] as GuiListBox;
   lstColumns.BindData( MyMeta.Databases[cmbDatabases.SelectedValue].Tables[cmbTables.SelectedValue].Columns );
  }
  catch (Exception ex)
  {
  }
 }
}

To loop through a collection (e.g. ArrayList).

Template Code

<%
public class GeneratedTemplate : DotNetScriptTemplate
{
 public GeneratedTemplate(ZeusContext context) : base(context) {}

 //---------------------------------------------------
 // Render() is where you want to write your logic    
 //---------------------------------------------------
 public override void Render()
 {
  int i;
  ArrayList items = new ArrayList();
  items.Add("Array Item 1");
  items.Add("Array Item 2");
  items.Add("Array Item 3");
  items.Add("Array Item 4");
  items.Add("Array Item 5");
  
  for(i = 0; i < items.Count; i++)
  {
  %>
    <p><%= items[i].ToString() %></p>
  <%
  }
 }

}
%>

Interface Code

public class GeneratedGui : DotNetScriptGui
{
 public GeneratedGui(ZeusContext context) : base(context) {}

 //-----------------------------------------
 // The User Interface Entry Point
 //-----------------------------------------
 public override void Setup()
 {
  // ** UNCOMMENT CODE BELOW TO SEE UI **

  //ui.Width  = 100;
  //ui.Height = 100;
  //GuiLabel lblDemo = ui.AddLabel("lblDemo", "Demo", "Demo Tooltip");
  //ui.ShowGui = true;
 }

}

MyGeneration comes with a template library built in, so chances are, someone may have already done a template that does what you want. You can post your own templates online as well (MyGeneration can download templates from this site, so you don't need to actually go to the site to get them).

It is also on SourceForge: http://sourceforge.net/projects/mygeneration/, so you can download the source code for it and make changes or add features.

Friday, April 10, 2009

GIMP Button Templates - Web 2.0 Style

Created some GIMP images that can be used as templates for creating buttons. Available in 64x64 and 256x64 sizes. Text can be altered and the background style changed (by altering visibility).

Sunday, April 05, 2009

Raphaël - a JavaScript library for creating vector images

Raphaël enables you to create geometric shapes / vector images in your web page without needing to know the syntax of Vector Markup Language (VML) or Scalable Vector Graphics (SVG). You can follow Raphaël on Twitter.

Sample code (example from website) shows how easy it is to work with.

// Creates canvas 320 × 200 at 10, 50
var paper = Raphael(10, 50, 320, 200);
// Creates circle at x = 50, y = 40, with radius 10
var circle = paper.circle(50, 40, 10);
// Sets the fill attribute of the circle to red (#f00)
circle.attr("fill", "#f00");
// Sets the stroke attribute of the circle to white (#fff)
circle.attr("stroke", "#fff");

Animation of the shapes created can also be done with it (e.g. circles into squares).

Wednesday, March 11, 2009

Pure Accessible JavaScript Tabs

Found some code I did a while ago (pre jQuery) of a simple tabbed navigation implementation using JavaScript and basic CSS. Posting if anyone is interested.

HTML

<ul id="tabnav">
 <li><a href="#tab1">Tab 1</a></li>
 <li><a href="#tab2">Tab 2</a></li>
 <li><a href="#tab3">Tab 3</a></li>
</ul>
<div id="tabs">
 <div id="tab1"><a name="tab1"></a>
  <h1>Tab 1</h1>
  This is the contents of Tab 1
 </div>
 <div id="tab2"><a name="tab2"></a>
  <h1>Tab 2</h1>
  This is the contents of Tab 2
 </div>
 <div id="tab3"><a name="tab3"></a>
  <h1>Tab 3</h1>
  This is the contents of Tab 3
 </div>
</div>

CSS

#tabnav {
 width: 100%;
 overflow: hidden;
 list-style-type: none;
 margin: 0;
 padding: 0;
}
#tabnav li {
 float: left;
 padding: 6px;
 margin: 0;
}
#tabnav li.activetab {
 font-weight: bold;
}

JavaScript (hopefully the inline comments should explain how it works). Add in script tag after HTML

// setup tabs, default is 'tab2'
setupTabs("tab2");
 
function setupTabs(tabDefault) {
 if (document.getElementById) {
  // get tab navigator
  var tabnav = document.getElementById('tabnav');
  if(tabnav) {
   // get list items in tab navigator
   var tablist = tabnav.getElementsByTagName('li');
   // loop through list items
   for (i=0;i<tablist.length;i++) {
    // get tab links (i.e. anchor tags)
    var tablinks = tablist[i].getElementsByTagName('a');
    // loop through tab links
    for (j=0;j<tablinks.length;j++) {
     // get hash for current tablink (i.e. the #tabname portion)
     var hash = tablinks[j].hash;
     // set onclick attribute to toggle the tabs (does not work in IE)
     tablinks[j].setAttribute("onclick","toggleTabs('"+hash.substring(1)+"'); return false;");
     // detect IE (other browsers don't support attachEvent)
     if (tablinks[j].attachEvent) {
      // set onclick to toggle tabs
      tablinks[j].onclick = Function("toggleTabs('"+hash.substring(1)+"'); return false;");
     }
     // if has is default tab
     if (hash == ("#" + tabDefault)) {
      // set parent nodes css class to 'activetab' (i.e. 'li' tag) and show tab
      tablinks[j].parentNode.className = "activetab";
      showTab(tabDefault);
     } else {
      // set parent nodes css class to empty (i.e. 'li' tag) and hide tab
      tablinks[j].parentNode.className = ""
      hideTab(hash.substring(1));
     }
    }
   }
  }
 }
}
function showTab(tabId) {
 if (document.getElementById) {
  // get current tab
  var currentTab = document.getElementById(tabId);
  // show tab by setting display style to blank string
  currentTab.style.display = "";
  // get anchor tags in current tab
  var currentTabAnchors = currentTab.getElementsByTagName('a'); 
  // loop through anchor tags
  for (k=0;k<currentTabAnchors.length;k++) {
   // if anchor tag has same name as current tag id, hide it
   if(currentTabAnchors[k].name==tabId) {
    currentTabAnchors[k].style.display = "none";
   }
  }
 }
}
function hideTab(tabId) {
 if (document.getElementById) {
  document.getElementById(tabId).style.display = "none";
 }
}
function toggleTabs(tabId) {
 if (document.getElementById) {
  // an array of all the sections
  var sectionnames = new Array();
  // get tab navigator
  var tabnav = document.getElementById('tabnav');
  // get list items in tab navigator
  var tablist = tabnav.getElementsByTagName('li');
  // loop through list items
  for (i=0;i<tablist.length;i++) {
   // get tab links (i.e. anchor tags)
   var tablinks = tablist[i].getElementsByTagName('a');
   // loop through tab links
   for (j=0;j<tablinks.length;j++) {
    // get hash for current tablink (i.e. the #tabname portion)
    var hash = tablinks[j].hash;
    // add has to sections array
    sectionnames.push(hash.substr(1));
    // if hash is selected tab
    if (hash == ("#" + tabId)) {
     // set parent nodes css class to 'activetab' (i.e. 'li' tag)
     tablinks[j].parentNode.className = "activetab";
    } else {
     // set parent nodes css class to empty (i.e. 'li' tag)
     tablinks[j].parentNode.className = ""
    }
   }
  }
  // get tabs
  var tabs = document.getElementById('tabs');
  // get tab sections
  var sections = tabs.childNodes;
  // loop through sections
  for (i=0;i<sections.length;i++) {
   // if node is not a div, then continue to next loop item
   if (sections[i].nodeName!='DIV') continue;
   // if current section id matches tabId show it
   if(sections[i].id==tabId) {
    showTab(sections[i].id);
   } else {
    // get child anchor nodes
    var atags = sections[i].getElementsByTagName("a");
    // if anchor nodes have been found
    if (atags.length > 0) {
     // loop through the section names
     for (j=0;j<sectionnames.length;j++) {
      // if first anchor tags name matches the current section id, hide the tab
      if(sectionnames[j] == atags[0].getAttribute("name")) {
       hideTab(sections[i].id);
      }
     }
    }
    
   }
  }
 }
 return false;
}

Thursday, January 22, 2009

Shrink Image (JavaScript)

This function shrinks an image, so its dimensions are no bigger than the maximum dimensions you define.

// get new dimensions so it fits within the maximum
// o = original dimensions / image, m = maximum dimensions
function shrink(o, m)
{
    // r = resized
    var r = {width: o.width, height: o.height};
    // if an image, rather than an object, resize it
    if(o.nodeName && o.nodeName.toLowerCase() == "img") r = o;
    if(r.width > m.width)
    {
        r.height = r.height * (m.width / r.width);
        r.width = m.width;
        if(r.height > m.height)
        {
            r.width = r.width * (m.height / r.height);
            r.height = m.height;
        }
    }
    else if(r.height > m.height)
    {
        r.width = r.width * (m.height / r.height);
        r.height = m.height;
    }
    return r;
}

To use, simply supply the image and the dimensions to constrain it to:

var image = document.getElementById("image");
var max = {width: 1024, height: 768};
shrink(image, max);

It also works with an object instead of an image, for non-image related dimension calculation.

var original = {width: 1200, height: 960};
var max = {width: 1024, height: 768};
var shrunk = shrink(original, max);

Changing jQuery UI's Dialog widget's default options

While jQuery UI's Dialog widget has good default options, there may be circumstances when you want to change them. For instance, you may want all dialog windows to be modal windows, with a semi-transparent background and the bgiframe plugin applied. Place this code at the end of the page, or within $(document).ready.

$.extend($.ui.dialog.defaults, {
 overlay : { background: "#000", opacity: 0.8},
 modal: true,
 bgiframe: true
});

Saturday, January 10, 2009

Pixie – small, simple, website maker

Recently, I have been looking at Pixie, to see how it works as a simple system for managing websites. It is open source (GPL v3), written to web standards (XHTML Strict, Microformats) and powered by jQuery and PHP/MySQL.

It consists of several page types – dynamic (blog, news), static or module (e.g. contact form, events, links etc). Plugins add additional functionality to modules (like allowing comments on blog posts). Blocks allow you to add content alongside your content (e.g. display RSS content from BBC News).

Easy enough to extend, with detailed guides for module development (so you can create additional ones to those that are bundled with Pixie) block development and theme development.

A few additional blocks I have created: News (latest content from a page named ‘news’) and Google Maps. These can then be shown on any page (so you can see news on your home page for example).

Wednesday, January 07, 2009

Google Gears Desktop API - Working with files

The Google Gears Desktop API allows you to create shortcuts on your desktop (only to a page on the same site, not to another) and work with files. This example shows you how you can filter the file types when choosing files. gears_init.js required.

<!DOCTYPE html>
<html>
 <head>
 <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
 <title>Google Desktop API - Working with files</title>
<script type="text/javascript" src="gears_init.js"></script>
<script type="text/javascript">
var fileformats = {

 // word document
 word: 'application/msword',
 // word 2007 document
 wpml: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',

 // excel spreadsheet
 excel: 'application/vnd.ms-excel',
 // excel 2007 spreadsheet
 ssml: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',

 // powerpoint presentation
 powerpoint: 'application/vnd.ms-powerpoint',
 // powerpoint 2007 presentation
 pml: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
 
 // jpeg
 jpeg: 'image/jpeg',
 
 // png
 png: 'image/png',
 
 // gif
 gif: 'image/gif',
 
 // tiff
 tiff: 'image/tiff'
}


// http://code.google.com/apis/gears/api_desktop.html
var desktop = google.gears.factory.create('beta.desktop');

function officeFiles()
{
 desktop.openFiles(openFilesCallback, {singleFile: false,
  filter: [fileformats.word, fileformats.wpml,
   fileformats.excel, fileformats.ssml,
   fileformats.powerpoint, fileformats.pml]
 });
}

function imageFiles()
{
 desktop.openFiles(openFilesCallback, {singleFile: false,
  filter: [fileformats.jpeg, fileformats.png,
   fileformats.gif, fileformats.tiff]
 });
}


function openFilesCallback(files)
{
 if(files.length == 0)
 {
  alert("No files selected");
 }
 else
 {
  var selectedFiles = [];
  for(var i = 0; i < files.length; i++)
  {
   selectedFiles.push(files[i].name + " (" + files[i].blob.length + " bytes)");
  }
  alert("Selected files: " + selectedFiles);
 }
}
</script>
 </head>
 <body>
  <p><a href="http://code.google.com/apis/gears/api_desktop.html">Google Gears Desktop API</a></p>
  <form action="" method="post">
   <input value="Select Office Files" type="button" id="selectofficefiles">
   <input value="Select Image Files" type="button" id="selectimagefiles">
  </form>
<script type="text/javascript">
var selectofficefiles = document.getElementById("selectofficefiles");
selectofficefiles.onclick = function()
{
 officeFiles();
 return false;
}

var selectimagefiles = document.getElementById("selectimagefiles");
selectimagefiles.onclick = function()
{
 imageFiles();
 return false;
}
</script>
 </body>
</html>

The benefit of using Google Gears is that you can get the file size before uploading the file, so a check can be done beforehand to see if the file is to big (e.g. if(files[i].blob.length > 1048576) alert(files[i].name + " too big (> 1MB)");.

Friday, January 02, 2009

Free Graph Paper (for printing)

There are a few sites that offer paper (graph, maths, speciality) you can print off (as PDF) for free. Usually with a credit to the site at the bottom of the page.

Konigi offers various storyboard and wireframe PDF's available in 8.5" x 11" (no A4 available). So they are useful when creating a site design mockup or a story.

Print Free Graph Paper offers Cartesian, Engineering, Polar, Isometric, Logarithmic, Hexagonal, Probability and Smith Chart templates available in Letter and A4 sizes.

MathSphere also has paper types the previous site doesn't.

Incompetech.com has various paper generators for different page sizes (you can create a custom size as well), not just graph paper, but speciality and writing as well (Chinese, Japanese, Fretboard, Cornell etc). You can even define what colour the lines should be.

Useful when you don't need a full pad, the stationer doesn't sell the paper you want, or the shops are closed and you need paper before they are open again (you have a deadline or late submission).