Friday, May 15, 2009

Article: Absolutely relative


from http://www.wpdfd.com/issues/78/absolutely_relative

Absolutely relative

by Joe Gillespie — Sep 1, 2004

The concepts of absolute and relative positioning in CSS can be quite confusing and are often misunderstood.

Absolute positioning puts CSS boxes (divs) at specific pixel locations in the browser window, just like pinning cards on a notice board. Usually, the absolute position is measured from the top left corner of the page but it doesn't have to be, as you will see.

For relative positioning, the analogy I often use is that of polystyrene blocks in a swimming pool – they float until they are pushed downwards by another one from above. If you specify float: left or float: right, that makes them float horizontally instead of vertically.

Used exclusively, absolutely positioned divs produce hard, static layouts which invariably break in the plethora of screen sizes we have to contend with.

Relatively positioned layouts, on the other hand, can adjust to the size and shape of the browser window and are much more likely to give satisfactory results on the maximum number of systems – but that means the actual layouts are less predictable and sometimes get just plain silly when the lines of text get too long.

By combining relative and absolute positioning, you get more control without losing the flexibility. The key to this is the fact that absolutely positioned boxes don't have to be placed relative to the browser's edges, they can be positioned relative to any div you put them in.

If you put an absolutely positioned box inside a relatively positioned box, it moves with that box. It is absolutely relative!

It's just like walking around inside a plane. The plane is travelling relative to the airport but when you move around inside it, you seem to be at some point, say, six feet away from your seat and two feet to the left.

Apart from the wisdom of having layouts that are less likely to break, this also opens up a whole new set of possibilities. You can put images or captions next to blocks of text and they stay there. Unlike the rectangular constraints imposed by using tables, these 'satellite' divs can go anywhere in a complete 360° arc around the text – or over or behind it.

Keep things flexible

I try to avoid the top-left way of thinking that many designers seem to embrace by default. I like to float my content in the horizontal centre of the page so that it looks balanced on any monitor and not lop-sided. To do this, you can specify right and left margins and let the content stretch or specify the content width and let the margins take up the slack. To keep Internet Explorer happy, it is also necessary to put the entire page inside ...

or, more correctly, use . Either will validate.

That's just one way of doing it. It works fine for all monitors 800 x 600 or larger. If you need to accommodate smaller screens, you can use percentages.

Having established a 'page' div, I just put relatively positioned rows one above another much like the rows in a table. Inside each row are three columns. col1 holds the vertical section headings, col2 is for headings and pictures and col3 is the main text. The height of each row is determined by the amount of text in col3.

A box's z-index property controls the layering. Most of the time, you don't have to be too concerned about z-index. Absolutely position boxes adopt their z-index values automatically based upon the position of the div in the markup – the further down in the markup, the higher its layer will be.

Sometimes, you might want to manipulate the z-index value if the default is not giving the effect you want. All you have to do is to make sure that the frontmost box has a higher z-index value. You don't have to change z-index values in steps of 1. In fact, it is better to give them steps of 10 or 100 in case you want to put something in between at a later date.

So, now you have an image that can be positioned anywhere around a text box, behind or in front. You can also play that trick the other way round. If you want to add a small caption to an image, put the .textpanel inside the .picbox. In this instance, you should probably set the width and height of .picbox and the width of .textpanel to a value smaller than .picbox width. If you want the caption to overprint the picture totally or partly, just place it wherever you like inside .picbox.

Then, you can do both and go all the way. Make the picture relative to the textpanel AND make the caption relative to the picture.

Experiment! Once you start playing with these techniques you will find all kinds of interesting layout possibilities.

About the styles

For all these examples, I have created basic classes in the main stylesheet in the head section of this page. I then modified those styles locally using 'inline' styling for their margin-left and margin-top properties. Have a look at the source.

Monday, May 11, 2009

How to dynamically compress JavaScript files and CSS files?

How to dynamically compress JavaScript files and CSS files?

If web page uses too much JavaScript files and CSS files, it takes time to load every file to client side, especially if using HTTPS. The code below shows how to compress all JavaScript and CSS files into one download to Client side, which saves bandwidth and loading speed.

The idea is actually add a new httpHandlers.

First, in the web.config, add code below to the system.web section.






Second, in the .aspx page, other than add reference to JavaScript files or CSS files, use the code below instead.

<%= customLibrary.ScriptCombiner.GetScriptTags("ElementEditor") %>

Here, "customLibrary" is your own library, "ScriptCombiner" is a class in your library, "GetScriptTags" is a function to do the job.


using System;
using System.IO;
using System.Text;
using System.Web;

public class ScriptCombiner : IHttpHandler
{
private readonly static TimeSpan CACHE_DURATION = TimeSpan.FromDays(30);
private HttpContext context;

public void ProcessRequest(HttpContext context)
{
this.context = context;
HttpRequest request = context.Request;

// Read setName, version from query string
string setName = (request["s"]!=null)?request["s"]:string.Empty;
string version = (request["v"]!=null)?request["v"]:string.Empty;

// Decide if browser supports compressed response
bool isCompressed = this.CanGZip(context.Request);

// If the set has already been cached, write the response directly from
// cache. Otherwise generate the response and cache it
if (!this.WriteFromCache(setName, version, isCompressed))
{
using (MemoryStream memoryStream = new MemoryStream(8092))
{
// Decide regular stream or gzip stream based on whether the response can be compressed or not
//using (Stream writer = isCompressed ? (Stream)(new GZipStream(memoryStream, CompressionMode.Compress)) : memoryStream)
using (Stream writer = isCompressed ? (Stream)(new ICSharpCode.SharpZipLib.GZip.GZipOutputStream(memoryStream)) : memoryStream)
{
// Read the files into one big string
StringBuilder allScripts = new StringBuilder();
foreach (string fileName in GetScriptFileNames(setName))
allScripts.Append(this.readAllText(context.Server.MapPath(fileName)));

// Minify the combined script files and remove comments and white spaces
JavaScriptMinifier minifier = new JavaScriptMinifier();
string minified = minifier.Minify(allScripts.ToString());

// Send minfied string to output stream
byte[] bts = Encoding.UTF8.GetBytes(minified);
writer.Write(bts, 0, bts.Length);
}

// Cache the combined response so that it can be directly written
// in subsequent calls
byte[] responseBytes = memoryStream.ToArray();
context.Cache.Insert(GetCacheKey(setName, version, isCompressed),
responseBytes, null, System.Web.Caching.Cache.NoAbsoluteExpiration,
CACHE_DURATION);

// Generate the response
this.WriteBytes(responseBytes, isCompressed);
}
}
}
private string readAllText(string filePath)
{
StreamReader reader = new StreamReader(filePath, System.Text.Encoding.Default);
byte[] data=Encoding.Default.GetBytes(reader.ReadToEnd());
reader.Close();
//return (new ASCIIEncoding()).GetString(data);
return (new UTF8Encoding()).GetString(data);
}
private bool WriteFromCache(string setName, string version, bool isCompressed)
{
byte[] responseBytes = context.Cache[GetCacheKey(setName, version, isCompressed)] as byte[];

if (responseBytes == null || responseBytes.Length == 0)
{
//irAutoLog.addLog("NoCache", "HttpCombiner." + setName + "." + version + "." + isCompressed);
return false;
}
//irAutoLog.addLog("GetCache", "HttpCombiner." + setName + "." + version + "." + isCompressed);
this.WriteBytes(responseBytes, isCompressed);
return true;
}

private void WriteBytes(byte[] bytes, bool isCompressed)
{
HttpResponse response = context.Response;

response.AppendHeader("Content-Length", bytes.Length.ToString());
response.ContentType = "application/x-javascript";
if (isCompressed)
response.AppendHeader("Content-Encoding", "gzip");
else
response.AppendHeader("Content-Encoding", "utf-8");

context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetExpires(DateTime.Now.Add(CACHE_DURATION));
context.Response.Cache.SetMaxAge(CACHE_DURATION);

response.ContentEncoding = Encoding.Unicode;
response.OutputStream.Write(bytes, 0, bytes.Length);
response.Flush();
}

private bool CanGZip(HttpRequest request)
{
string acceptEncoding = request.Headers["Accept-Encoding"];
if ((acceptEncoding!=null && acceptEncoding!=string.Empty) &&
(acceptEncoding.IndexOf("gzip")>-1 || acceptEncoding.IndexOf("deflate")>-1))
return true;
return false;
}

private string GetCacheKey(string setName, string version, bool isCompressed)
{
return "HttpCombiner." + setName + "." + version + "." + isCompressed;
}

public bool IsReusable
{
get { return true; }
}

// private helper method that return an array of file names inside the text file stored in App_Data folder
private static string[] GetScriptFileNames(string setName)
{
string[] fileNames = new string[] {};

switch (setName)
{
case "Reports":
fileNames = new string[2]
{
"js/jsGlobalReporting.js",
"js/jsReport_New.js"
};
break;
case "ReportViewer":
fileNames = new string[6]
{
"js/jsGlobalReporting.js",
"js/json.js",
"js/jsJSONObject.js",
"js/jsReportViewer_Grid_View.js",
"js/jsReportViewer_PageElement_Advanced.js",
"js/jsReportViewer_Advanced.js"
};
break;
case "ElementEditor":
fileNames = new string[6]
{
"js/jquery.min.js",
"js/jquery.fn.js",
"js/jquery.grid.edit.js",
"js/json2.js",
"js/jsJSONObject.js",
"js/jsReportViewer_ElementEditor.js"
};
break;
case "PageEditor":
fileNames = new string[5]
{
"js/jquery.min.js",
"js/jquery.fn.js",
"js/json2.js",
"js/jsJSONObject.js",
"js/jsReportViewer_PageEditor.js"
};
break;
case "ReportSetup":
fileNames = new string[3]
{
"js/json.js",
"js/jsGlobalReporting.js",
"js/jsReportViewer_ReportSetup.js"
};
break;
case "FormViewer":
fileNames = new string[5]
{
"js/jquery-1.3.2.min.js",
"js/jquery-ui-1.7.1.custom.min.js",
"js/jquery.fn.js",
"js/json2.js",
"js/jsFormViewer.js"
};
break;
case "Forms":
fileNames = new string[4]
{
"js/jquery.min.js",
"js/jquery.fn.js",
"js/json2.js",
"js/jsForms.js"
};
break;
case "FormDesigner":
fileNames = new string[4]
{
"js/jquery.min.js",
"js/jquery.fn.js",
"js/json2.js",
"js/jsFormDesigner.js"
};
break;
case "SignOff":
fileNames = new string[4]
{
"js/jquery.min.js",
"js/jquery.fn.js",
"js/json2.js",
"js/jsReport_SignOff.js"
};
break;
}
return fileNames;
}

public static string GetScriptTags(string setName)
{
string result = null;
System.Version ver = irGlobal.getExecutingAssemblyVersion();
string version = (ver==null)?"100":ver.ToString();
//#if (DEBUG)
// foreach (string fileName in GetScriptFileNames(setName))
// {
// result += String.Format("\n", VirtualPathUtility.ToAbsolute(fileName), version);
// }
//#else
result += String.Format("", setName, version);
//#endif
return result;
}
}

Use JSMIN to dynamically minify javascript in C#

JSMIN was created by Douglas Crockford to minify javascript file or portion. The code below shows how to do it in C#.

using System;
using System.IO;
using System.Text;

/* Originally written in 'C', this code has been converted to the C# language.
* The author's copyright message is reproduced below.
* All modifications from the original to C# are placed in the public domain.
*/

/* jsmin.c
2007-05-22

Copyright (c) 2002 Douglas Crockford (www.crockford.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
public class JavaScriptMinifier
{
const int EOF = -1;

StreamReader sr;
StreamWriter sw;
int theA;
int theB;
int theLookahead = EOF;

public string Minify(string src)
{
MemoryStream srcStream = new MemoryStream(Encoding.Unicode.GetBytes(src));
MemoryStream tgStream = new MemoryStream(8092);

using (sr = new StreamReader(srcStream, Encoding.Unicode))
{
using (sw = new StreamWriter(tgStream, Encoding.Unicode))
{
jsmin();
}
}

return Encoding.Unicode.GetString(tgStream.ToArray());
}

/* jsmin -- Copy the input to the output, deleting the characters which are
insignificant to JavaScript. Comments will be removed. Tabs will be
replaced with spaces. Carriage returns will be replaced with linefeeds.
Most spaces and linefeeds will be removed.
*/
void jsmin()
{
theA = '\n';
action(3);
while (theA != EOF)
{
switch (theA)
{
case ' ':
{
if (isAlphanum(theB))
{
action(1);
}
else
{
action(2);
}
break;
}
case '\n':
{
switch (theB)
{
case '{':
case '[':
case '(':
case '+':
case '-':
{
action(1);
break;
}
case ' ':
{
action(3);
break;
}
default:
{
if (isAlphanum(theB))
{
action(1);
}
else
{
action(2);
}
break;
}
}
break;
}
default:
{
switch (theB)
{
case ' ':
{
if (isAlphanum(theA))
{
action(1);
break;
}
action(3);
break;
}
case '\n':
{
switch (theA)
{
case '}':
case ']':
case ')':
case '+':
case '-':
case '"':
case '\'':
{
action(1);
break;
}
default:
{
if (isAlphanum(theA))
{
action(1);
}
else
{
action(3);
}
break;
}
}
break;
}
default:
{
action(1);
break;
}
}
break;
}
}
}
}
/* action -- do something! What you do is determined by the argument:
1 Output A. Copy B to A. Get the next B.
2 Copy B to A. Get the next B. (Delete A).
3 Get the next B. (Delete B).
action treats a string as a single character. Wow!
action recognizes a regular expression if it is preceded by ( or , or =.
*/
void action(int d)
{
if (d <= 1)
{
put(theA);
}
if (d <= 2)
{
theA = theB;
if (theA == '\'' || theA == '"')
{
for (; ; )
{
put(theA);
theA = get();
if (theA == theB)
{
break;
}
if (theA <= '\n')
{
throw new Exception(string.Format("Error: JSMIN unterminated string literal: {0}\n", theA));
}
if (theA == '\\')
{
put(theA);
theA = get();
}
}
}
}
if (d <= 3)
{
theB = next();
if (theB == '/' && (theA == '(' || theA == ',' || theA == '=' ||
theA == '[' || theA == '!' || theA == ':' ||
theA == '&' || theA == '|' || theA == '?' ||
theA == '{' || theA == '}' || theA == ';' ||
theA == '\n'))
{
put(theA);
put(theB);
for (; ; )
{
theA = get();
if (theA == '/')
{
break;
}
else if (theA == '\\')
{
put(theA);
theA = get();
}
else if (theA <= '\n')
{
throw new Exception(string.Format("Error: JSMIN unterminated Regular Expression literal : {0}.\n", theA));
}
put(theA);
}
theB = next();
}
}
}
/* next -- get the next character, excluding comments. peek() is used to see
if a '/' is followed by a '/' or '*'.
*/
int next()
{
int c = get();
if (c == '/')
{
switch (peek())
{
case '/':
{
for (; ; )
{
c = get();
if (c <= '\n')
{
return c;
}
}
}
case '*':
{
get();
for (; ; )
{
switch (get())
{
case '*':
{
if (peek() == '/')
{
get();
return ' ';
}
break;
}
case EOF:
{
throw new Exception("Error: JSMIN Unterminated comment.\n");
}
}
}
}
default:
{
return c;
}
}
}
return c;
}
/* peek -- get the next character without getting it.
*/
int peek()
{
theLookahead = get();
return theLookahead;
}
/* get -- return the next character from stdin. Watch out for lookahead. If
the character is a control character, translate it to a space or
linefeed.
*/
int get()
{
int c = theLookahead;
theLookahead = EOF;
if (c == EOF)
{
c = sr.Read();
}
if (c >= ' ' || c == '\n' || c == EOF)
{
return c;
}
if (c == '\r')
{
return '\n';
}
return ' ';
}
void put(int c)
{
sw.Write((char)c);
}
/* isAlphanum -- return true if the character is a letter, digit, underscore,
dollar sign, or non-ASCII character.
*/
bool isAlphanum(int c)
{
return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
c > 126);
}
}

Use SharpZipLib to GZip in C#

SharpZipLib is a free C# compression library. The code below shows how to use it to gzip staff in C#.

using System;
using System.IO;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.GZip;

public static byte[] Compress(byte[] buffer) {
MemoryStream ms = new MemoryStream();

Stream stream = new GZipOutputStream(ms, buffer.Length);

try {
stream.Write(buffer, 0, buffer.Length);
}
finally {
stream.Close();
}

return ms.ToArray();
}

public static byte[] Decompress(byte[] buffer) {
MemoryStream ms = new MemoryStream();
byte[] data = new byte[4096];

Stream stream = new GZipInputStream(new MemoryStream(buffer));

try {
while (true) {
int bytes = stream.Read(data, 0, data.Length);

if (bytes < 1) {
break;
}

ms.Write(data, 0, bytes);
}
}
finally {
stream.Close();
}

return ms.ToArray();
}