Spot the web RSS 2.0
# Sunday, March 09, 2008

A common - and desirable - technique for constructing JavaScript-based web applications is that of progressive enhancement: only providing capable browsers with the features that they are capable of utilizing - but providing incapable browsers with an adequate, albeit degraded, experience otherwise.

This provides the best of both worlds: Users of modern browsers (the majority audience) can get the best experience while those that are using incapable browsers (such as most mobile devices) will still get an interface that suits them well.

There's one thing, overriding in all of this, however: Progressive enhancement is nearly only ever applied to the JavaScript functionality of web applications. Presumably it's assumed that if a browser is capable of supporting the desired JavaScript features of an application then it must, also, be capable of supporting the specific CSS styling as well.

One technique that has greatly interested me, as of late, is one employed by the Filament Group (a local design shop here in Boston): Progressive CSS Enhancement. The premise is that progressive enhancement is done with page styling in mind, primarily, rather than from a purely-JavaScript perspective.

This is particularly important for a couple reasons:

  • It should be easy to degrade page styling in a manner that isn't reliant upon CSS browser hacks - this technique makes it so.
  • Not all pages utilize heavy JavaScript (and, thus, progressive JavaScript enhancement does not apply to them).

Their technique works as follows: You choose to provide the user with, either, the enhanced or the decreased experience by default. In either case a basic script is run which attempts to verify a couple CSS styling behaviors along with some basic JavaScript functionality (just enough to be able to run the test).

A couple of the CSS techniques that they test for:

  • Box model: make sure the width and padding of a div add up properly using offsetWidth.
  • Positioning: position a div and check its positioning using offsetTop and offsetLeft.
  • Float: float 2 divs next to each other and evaluate their offsetTop values for equality.
  • Clear: test to make sure a list item will clear beneath a preceding floated list item.
  • Overflow: wrap a tall div with a shorter div with overflow set to 'auto', and test its offsetHeight.

With those in place you can pretty safely begin designing a useful CSS-based layout. Note that the experience will only ever be upgraded if all of the tests pass - if any fail then it simply won't continue. Obviously there'll still exist some browser discrepancies (like in the differences in the box model between Internet Explorer 6 and most other browsers) but that's usually an acceptable level of hackage (meaning that you won't have to deviate much from what you're already doing).

The actual implementation is quite simple. It consists of a number of JavaScript-based rules that test for behavior. For example the following rule tests for a working box model:

var newDiv = document.createElement('div');
document.body.appendChild(newDiv);
newDiv.style.visibility = 'hidden';
newDiv.style.width = '20px';
newDiv.style.padding = '10px';
var divWidth = newDiv.offsetWidth;
if(divWidth != 40) {document.body.removeChild(newDiv); return false;}

That check, alone, is able to knock off a number of older browser whom aren't able to successfully implement that CSS behavior. Currently all the rules are in a large code block, which makes maintenance unwieldily. I think that this library could definitely benefit from extensibility (being able to add/remove rules that you wish to honor).

When it comes time to actually use this technique within your application there are a number of strategies that you can use. However, for the sake of discussion here, let's assume that you're sending, by default, the degraded experience to the client (optionally upgrading if the browser is capable). Then you would be able to use these two techniques:

  • A class of "enhanced" is assigned to the body element to be used for optional CSS scoping (such as: body.enhanced {background: red;}).
  • Any links to alternate stylesheets that have a class of "enhanced" will be enabled.

In this manner you can specify all of your stylesheets in your header with some disabled (being alternate stylesheets) or with some CSS rules being only applied with the body.enhanced match.

Their implementation also allows you to only execute JavaScript if all the rules pass - however I'm not sure if that's an acceptable solution, in this situation. If you want to verify that your desired JavaScript functionality is able to operate then you should check for just that. However, in this case, we can get the other side of the equation: Verifying that CSS works as you would expect it to, knowing that an adequate experience can be provided.

If you're curious as to which devices are supported by the default rules in the test file you can view the result matrix on the tool's site.

I definitely think that this technique has a lot of merit, especially in the realm of mobile-accessible web sites. Since it's virtually impossible to design, and test, your pages to work on such a large number of obscure platforms this degraded strategy is really one that will help to benefit both you, and your users, in the long run.

Sunday, March 09, 2008 11:24:37 AM (Jerusalem Standard Time, UTC+02:00)  #    Comments [0] - Trackback
CSS | Programming | Web Design
# Friday, March 07, 2008

This article explains how you can extract all Tables, including Data, to an Excel file. Every Table will have it's own worksheet. The code does need an excel application installed on the server, while it's using Office Interop.

To get this code to work, you need to add a reference to Excel.dll by using Add Reference on the project and selecting Microsoft Excel 9.0 (or 10.0) Object Library from the COM tab on the Add Reference dialog.

And then import the following namespace:

Imports System.Runtime.InteropServices.Marshal


Now add the following class to your project:

Private Sub create(ByVal sDatabaseName As String)
Dim dsTables As DataSet = New DataSet

'Get all Tables from database
dsTables = getAllTables(sDatabaseName)
'Create Excel Application, Workbook, and WorkSheets
Dim xlExcel As New Excel.Application
Dim xlBooks As Excel.Workbooks
Dim xlBook As Excel.Workbook
Dim tblSheet As Excel.Worksheet
Dim xlCells As Excel.Range
Dim sFile As String

'File name for the excel file
sFile = Server.MapPath("~\Sheets\" & sDatabaseName & "_data.xls")

xlExcel.Visible = False : xlExcel.DisplayAlerts = False
xlBooks = xlExcel.Workbooks
xlBook = xlBooks.Add

For i As Integer = 0 To dsTables.Tables.Count - 1
    tblSheet = xlBook.Worksheets.Add
    tblSheet.Name = dsTables.Tables(i).TableName
    xlCells = tblSheet.Cells
    'Fill all cells with data
    GenerateExcelFile(dsTables.Tables(i), xlCells)
Next

'Remove initial excel sheets. Within a try catch because the database
'could be empty (a workbook without worksheets is not allowed)
Try
    Dim
SheetCount As Integer = xlExcel.Sheets.Count
    CType(xlExcel.Sheets(SheetCount - 0), Excel.Worksheet).Delete()
    CType(xlExcel.Sheets(SheetCount - 1), Excel.Worksheet).Delete()
    CType(xlExcel.Sheets(SheetCount - 2), Excel.Worksheet).Delete()
Catch ex As Exception

End Try

'Save the excel file
xlBook.SaveAs(sFile)

'Make sure all objects are disposed
xlBook.Close()
xlExcel.Quit()
ReleaseComObject(xlCells)
ReleaseComObject(tblSheet)
ReleaseComObject(xlBook)
ReleaseComObject(xlBooks)
ReleaseComObject(xlExcel)
xlExcel = Nothing
xlBooks = Nothing
xlBook = Nothing
tblSheet = Nothing
xlCells = Nothing
'Let the Garbage Collector know it can get to work
GC.Collect()

'Export Excel for download
Try
HttpContext.Current.Response.ContentType = "application/octet-stream"
HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment; filename=" + System.IO.Path.GetFileName(sFile))
HttpContext.Current.Response.Clear()
HttpContext.Current.Response.WriteFile(sFile)
HttpContext.Current.Response.End()
Catch ex As Exception
'An exception will be thrown, but can just be ignored
End Try

End Sub


To generate the individual sheets, the following Sub is used:

Private Sub GenerateExcelFile(ByRef table As DataTable, ByVal xlCells As Excel.Range)
Dim dr As DataRow, ary() As Object
Dim
iRow As Integer, iCol As Integer

'Output Column Headers
For iCol = 0 To table.Columns.Count - 1
    xlCells(1, iCol + 1) = table.Columns(iCol).ToString
    xlCells(1).EntireRow.Font.Bold =
True
Next

'Output Data
For iRow = 0 To table.Rows.Count - 1
    dr = table.Rows.Item(iRow)
    ary = dr.ItemArray
    For iCol = 0 To UBound(ary)
        xlCells(iRow + 2, iCol + 1) = ary(iCol).ToString
        Response.Write(ary(iCol).ToString & vbTab)
    Next
Next
xlCells.Columns.AutoFit()
End Sub


And now the trick to getting all tables and data from a database:

Public database as String

Public ReadOnly Property getAllTables(ByVal sDB As String) As DataSet
    Get
       
database = sDB
        Dim m_dshelp As DataSet = New DataSet
        getRequestedAllTables(m_dshelp)
        Return m_dshelp
    End Get
End
Property


Private Function getRequestedAllTables(ByRef p_dataset As DataSet) As Boolean

'Retrieve all tablenames from the database:
Dim sSQL As String
Dim
dsTables As DataSet = New DataSet
sSQL =
"SELECT [TableName] = so.name, [RowCount] = MAX(si.rows) " & _
"FROM sysobjects so, sysindexes si " & _
"WHERE so.xtype = 'U' AND si.id = OBJECT_ID(so.name) AND si.rows > 0 " & _
"GROUP BY so.name " & _
"ORDER BY 2 DESC"

getData(sSQL, "Tables", dsTables)

'Loop thrue all tables and do a SELECT *. Then add them to the dataset
For i As Integer = 0 To dsTables.Tables(0).Rows.Count - 1
    sSQL =
"SELECT * FROM " & dsTables.Tables(0).Rows(i).Item(0)
    getData(sSQL, dsTables.Tables(0).Rows(i).Item(0), p_dataset)
Next
End
Function


Private Function getData(ByVal p_sql As String, ByVal p_table As String, ByRef pdataset As DataSet) As Boolean

Dim objDataAdapter As SqlDataAdapter
Dim objcommand As SqlCommand
objcommand
= New SqlCommand(p_sql, getConnection)
objDataAdapter =
New SqlDataAdapter(objcommand)
objDataAdapter.Fill(pdataset, p_table)
End Function


Private Function getConnection() As SqlConnection
If (ConfigurationManager.AppSettings("SQLPW") <> "") Then
    getConnection = New SqlConnection("Server=" & _
    ConfigurationManager.AppSettings("
SQLserver") & ";password=" & _
    ConfigurationManager.AppSettings(
"SQLPW") & "; user=" & _
    ConfigurationManager.AppSettings("
SQLUser") & ";database=" & database)
Else
    getConnection = New SqlConnection("Data Source=" & _
   ConfigurationManager.AppSettings(
"SQLserver") & ";Initial Catalog=" & _
    database &
";Integrated Security=True")
End If
End Function

Friday, March 07, 2008 11:42:11 AM (Jerusalem Standard Time, UTC+02:00)  #    Comments [0] - Trackback
.Net | Programming | Visual Studio
# Wednesday, March 05, 2008

When structuring your markup there are many paths you can choose to reach the same goal. Most of the times it's a matter of preferences which element you wish to apply on a specific spot. I have my own personal preferences. One of them is preferring BUTTON over INPUT type="submit" elements. Why? Well W3C says it "Buttons created with the BUTTON element function just like buttons created with the INPUT element, but they offer richer rendering possibilities".

It's the rendering that I am after. You can easily apply image replacement techniques to buttons and make your form look good. Most interesting feature is that BUTTON element may have content. I will use that possibility in this tutorial.

So, the goal is to create and style button that can handle variable length so there is no need for later interventions. We'll treat button element as a container and add some markup.

Take a look at the demo | Download zip file

HTML

Here it is:

<button><span><em>Button text</em></span></button>

This is a valid code, and it gives us a lot to work with.

Please note, I am using two child elements instead of only one because I couldn't get rid of some paddings that button preserved. If anyone succeeds in styling with one child element only, please let me know :)

Concept

This concept is probably familiar to you from various navigation tab styling techniques. We have one long background image:

image

This one is 550px wide. I believe that is more than sufficient for buttons :)
So, we apply this image as a background image of a top element (in this case SPAN), place it to top left and add some left padding. Nested element (in this case EM) have the same background image but placed to top right and with right padding. As the content text grows so will the button.

image

Height of the button is fixed in my example but you can use similar technique and some more markup and place the same background image to all 4 corners.
To make sure that the text is vertically centered I use line-height property.

CSS

button{
	border:none;
	background:none;
	padding:0;
	margin:0;
	width:auto;
	overflow:visible;					
	text-align:center;	
	white-space:nowrap;	
	height:40px;
	line-height:38px;			
	}

Resetting button's default styling.

button span, button em{
	display:block;
	height:40px;
	line-height:38px;			
	margin:0;
	color:#954b05;
	}

Setting child elements. Note that value of height property is different than line-height. That's because I have 2px shadow at the bottom. Line-height vertically centers the text within the button graphic, shadow is excluded.

button span{
	padding-left:20px;
	background:url(bg_button.gif) no-repeat 0 0;
	}	
button em{
	font-style:normal;
	padding-right:20px;
	background:url(bg_button.gif) no-repeat 100% 0;
	}

Setting backgrounds and paddings for both child elements.

As I mentioned earlier, it would be more elegant if I could use BUTTON and SPAN only, but I couldn' t get rid of BUTTON paddings.

Wednesday, March 05, 2008 10:14:42 AM (Jerusalem Standard Time, UTC+02:00)  #    Comments [0] - Trackback
CSS | Design | Web Design
Navigation
Archive
<March 2008>
SunMonTueWedThuFriSat
2425262728291
2345678
9101112131415
16171819202122
23242526272829
303112345
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
Guy Levin
Sign In
Statistics
Total Posts: 63
This Year: 0
This Month: 0
This Week: 0
Comments: 14
Themes
All Content © 2010, Guy Levin