code.nontalk.com

Code snippets for C#, T-SQL and JavaScript

Friday, December 19, 2008

Simple Html Email Template Class

I very often have to send HTML formatted email addresses from a web application. I have taken many approaches when building the HTML message. Lately I have been using html files as templates which have replaceable "tokens". Then I read the file into a string then do a series of replace statements like so:

string name = "Jesse";
string city = "Minneapolis";

string body = FileHelper.Read("~/templates/email.htm");
body = body.Replace("{Name}", name);
body = body.Replace("{City}", city);

[...send email...]

After noticing how many times I have done this same thing, I decided to build a simple class to make this easier. My new code looks like this.

string name = "Jesse";
string city = "Minneapolis";

Template t = new Template("~/templates/email.htm");
t.Tokens.Add("{Name}", name);
t.Tokens.Add("{City}", city);
string body = t.Replace();

[...send email...]

It's actually more lines of code but it 'feels' cleaner to me. Here's the class.

using System;
using System.Collections.Generic;

public class Template
{
    public string Contents { get; set; }
    public Dictionary Tokens { get; set; }

    public Template() {}

    public Template(string filename)
    {
        this.Tokens = new Dictionary();
        this.Contents = FileHelper.Read(filename);
    }

    public Template(string contents, Dictionary tokens)
    {
        this.Contents = contents;
        this.Tokens = tokens;
    }

 public string Replace()
 {
        foreach (KeyValuePair item in this.Tokens)
        {
            this.Contents = this.Contents.Replace(item.Key, item.Value);
        }
        return this.Contents;
 }

    public static string GetReplacedFile(string filename, Dictionary tokens)
    {
        Template t = new Template(filename);
        t.Tokens = tokens;
        return t.Replace();
    }

    public static string ReplaceTokens(string contents, Dictionary tokens)
    {
        Template t = new Template(contents, tokens);
        return t.Replace();
    }
}

Here's the FileHelper class that I use.

using System;
using System.Configuration;
using System.Drawing;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

/// 
/// Provides utility methods for working with files.
/// 
public class FileHelper
{

 public static String ImageExtPattern
 {
  get { return @"png|jpg|jpeg|gif|tiff|tif"; }
 }

 public static string Read(String filename)
 {
  if (!Path.IsPathRooted(filename))
  {
   filename = System.Web.HttpContext.Current.Server.MapPath(filename);
  }
  return ReadFromDisk(filename);
 }

 private static string ReadFromDisk(String filename)
 {
  StringBuilder sb = new StringBuilder();
  StreamReader sr = File.OpenText(filename);
  String str = sr.ReadLine();

  while (str != null)
  {
   sb.Append(str + "\n");
   str = sr.ReadLine();
  }

  sr.Close();
  return sb.ToString();
 }

 public static void SaveFile(String directory, String filename, System.Web.HttpPostedFile file)
 {
  if (!Path.IsPathRooted(directory))
  {
   directory = HttpContext.Current.Server.MapPath(directory);
  }

  if (!Directory.Exists(directory))
  {
   Directory.CreateDirectory(directory);
  }

  String path = Path.Combine(directory, filename);

  file.SaveAs(path);
  file.InputStream.Close();
 }

 public static void DeleteFile(String path, String name)
 {
        if (string.IsNullOrEmpty(name)) return;
        string filePath = Path.Combine(path, name);
        DeleteFile(path);
 }

 public static void DeleteFile(String path)
 {
        if (string.IsNullOrEmpty(path)) return;
        if (!Path.IsPathRooted(path)) path = HttpContext.Current.Server.MapPath(path);
  if (File.Exists(path)) File.Delete(path);
 }


    public static Boolean Exists(String path, String name)
    {
        if (string.IsNullOrEmpty(name)) return false;
        string filePath = Path.Combine(path, name);
        return Exists(filePath);
    }

    public static Boolean Exists(String path)
 {
        if (string.IsNullOrEmpty(path)) return false;
        if (!Path.IsPathRooted(path)) path = HttpContext.Current.Server.MapPath(path);
  return File.Exists(path);
 }
}

Labels:

Tuesday, April 24, 2007

Convert Relative Paths to Absolute Using Regular Expressions

I ran into a situation where I needed to screen scrape some content from a site and display it on my own site. This works really well except for dependent files like javascripts, SWFs and images that had src attributes with relative paths. So I figured it wouldn't be that hard to create a helper method to find and replace them using Regular Expressions. So here it is:

public static String ConvertRelativePathsToAbsolute(String text, String absoluteUrl)
{
	String value = Regex.Replace(text,
		"<(.*?)(src|href)=\"(?!http)(.*?)\"(.*?)>",
		"<$1$2=\"" + absoluteUrl + "$3\"$4>",
		RegexOptions.IgnoreCase | RegexOptions.Multiline);
	
	// Now just make sure that there isn't a // because if
	// the original relative path started with a / then the
	// replacement above would create a //.

	return value.Replace(absoluteUrl + "/", absoluteUrl);
}

Sample Usage:

String html = "<p><img src=\"images/dot.gif\" alt=\"test\" /></p>";
String baseUrl = "http//www.nontalk.com/";
String replacedHtml = ConvertRelativePathsToAbsolute(html, baseUrl);
// replacedHtml => <p><img src="http://www.nontalk.com/images/dot.gif" alt=\"test\" /></p> 

Labels: ,

Thursday, November 02, 2006

CSS + Javascript Workaround for Styling Form Inputs in Internet Explorer (IE)

Checkboxes, textboxes, radio buttons and submit buttons all share the same HTML tag name <input>. The type attribute is what distinguishes each element from each other. For example:

Markup Result
<input type="text" />
<input type="checkbox" />
<input type="radio" />
<input type="button" value="button" />

CSS2 gives us the the ability to create style rules based on attributes of elements. So we could create a rule such as:

input[type='button'] {
  border: dashed 2px green;
}
input[type='text'] {
  border: solid 2px red;
}

This would apply a dashed green border around all the buttons on a page and a solid red border around all text boxes. Unfortunately....you guessed it....Internet Explorer does not support attribute selectors in CSS. So if we want apply different styles to each type of input, then we either have to create inline styles like

<input type="text" style="border: solid 2px red;" />
or apply a class to each element and then create a corresponding CSS class definition.
<style type="text/css">
  .textbox { border: solid 2px red; }
</style>
<input type="text" class="textbox" />

Using inline styles is almost never a good idea, and adding a class attribute on every form element is tedious, so I wanted to figure out a way to make this easy on all of us until the day when Microsoft decides to support CSS more fully.

My idea was to create a JavaScript which would find each <input> element on the page and add a class name corresponding to the type of <input> it is.

View an Example

Here is the Javascript code:

<script type="text/javascript">
    function addClassNamesToInputs() {
        var inputs = document.getElementsByTagName("input");
        for (var i=0; i<inputs.length; i++) {
            inputs[i].className = inputs[i].type;
        }
    }
</script>

And here is the CSS which would go along with it:

<style type="text/css">
    body { font-family: sans-serif; font-size: 75%; }
    
    input {
        border: solid 2px red;
        }
    input.radio {
        border: none;
        }
    input.checkbox {
        border: none;
        width: 30px;
        height: 30px;
        }
    input.submit {
        border: solid 2px black;
        font-family: monospace;
        font-size: 18px;
        }
    input.reset {
        border: solid 2px green;
        font-family: Sans-Serif;
        font-size: 11px;
        font-weight: bold;
        }
    input.button {
        border: solid 2px blue;
        font-family: Serif;
        font-size: 25px;
        }
</style>

If you use the prototype.js library, you would want to use the Element.addClassName() function in the script because, the script above will overwrite any existing class name

<script type="text/javascript">
    function addClassNamesToInputs() {
        var inputs = document.getElementsByTagName("input");
        for (var i=0; i<inputs.length; i++) {
            inputs[i].addClassName(inputs[i].type);
        }
    }
</script>

Labels: , ,

Thursday, October 19, 2006

Read/Modify Querystring Variables with Javascript

There are a lot of nice little javascripts out there which allow you to easily read values from the query string, but I wanted a script that would allow you to easily read and modify querystring values. I created the following script which relies on the Prototype library (version 1.5.0_rc0):

/*--------------------------------------------------------------------------*/
/*  QueryString Object
*  (c) 2006 Jesse Gavin http://code.nontalk.com/
*
*  Depends upon:
*  - Prototype JavaScript framework, version 1.5.0_rc0
*    (c) 2005 Sam Stephenson <sam@conio.net>
*
*  This script is freely distributable under the terms of an MIT-style license.
*  For details, see: http://code.nontalk.com/
*
/*--------------------------------------------------------------------------*/

var QueryString = {
   params : $H(window.location.search.toQueryParams()),
  
   get : function(key, defaultValue) {
       if (defaultValue == null) defaultValue = null;
       var value = this.params[key]
       if (value==null) value = defaultValue;
       return value;
   },
  
   set : function(key, value) {
       this.params[key] = value;
   },
  
   remove : function(key) {
       this.params = this.params.collect(function(param) {
           if (key != param.key) return param;
       }).compact();
   },
  
   make : function() {
       return "?" + this.params.collect(function(param) {
           return escape(param.key) +"="+ escape(param.value);
       }).join("&");
   },
  
   go : function() {
       window.location.href = location.pathname + this.make();
   }
}

Download QueryString.js

P.S. I got the idea for this from a CodeProject article written by Uwe Keim. He created a C# class to manage QueryString variables, which does all this and more on the server side.

Labels: