Thursday, November 15, 2012

Punchout Grid implementation

I was recently working on a client side grid implementation for a messaging application and stumbled upon EntitySpaces's Punchout Grid. It is a very lightweight Knockout grid implementation which I found quite easy to customize and put to use in a generic sort of way. It supports paging and sorting, however the sorting methods were left up to the developer to implement (you can see some of the sorting methods I have added in the punchout_grid.js file for basic types).

I have slightly modified the template to add some of the Twitter bootstrap table styles, and added some custom knockout bindings for cell bound and header bound events. The beauty of this approach is you significantly improve page load times (trust me, I've compared with several other jQuery grids), and it enables you to write much cleaner code leveraging Knockout's DOM/Javascript object binding. This will help you avoid writing excess jQuery methods to manipulate the DOM and make use of the powerful MVVM pattern (making your UI much more flexible and interactive).

To use the grid, simply construct a JSON data property in your view model (I used an extension to pass in my strongly typed MVC view's JSON data), and pass in the grid column definitions in the columns property:
//strongly typed model property names
var idPropertyName = '@Html.DisplayNameFor(model => model.FirstOrDefault().ID)';
var flaggedPropertyName = '@Html.DisplayNameFor(model=> model.FirstOrDefault().Flagged)';

var viewModel = {
   //data
   data: ko.observableArray(@(new MvcHtmlString(Model.ToJSON()))),
   
   //knockout observableArray 
   columns: ko.observableArray([
               {
                   "columnName": idPropertyName, "dataType": "Int32", "displayName": idPropertyName, "isNullable": false,
                   "isVisible": false, "isSortable": false, "propertyName": idPropertyName
               },
               {
                   "columnName": flaggedPropertyName, "dataType": "Boolean", "displayName": flaggedPropertyName, "isNullable": false,
                   "isVisible": true, "isSortable": true, "propertyName": flaggedPropertyName
               }
            ])
        }

Next implement any custom bindings/events needed for your grid (in this case I'm adding a custom css class to two of the grid columns):
ko.bindingHandlers.cellBound = {
    update: function (element, valueAccessor, allBindingsAccessor) {
        switch (valueAccessor()) {
            case flaggedPropertyName:
                $(element).text() == 'true' ? $(element).attr("class", "flagged") : $(element).attr("class", "unflagged");
                break;
            case attachmentPropertyName:
                $(element).text() > 0 ? $(element).attr("class", "attachment") : $(element).attr("class", "noattachment");
                break;
            default:
                return;
        }
    }
}

And finally add your html div for the grid:

Handling grid cell selections become as simple as the following:

When a row becomes selected, Knockout updates the data bound text box, and automatically updates the grid after the user edits the textbox.

The full implementation is on github here. Feel free to make use in your own projects, and enjoy!

Sunday, June 3, 2012

GoDaddy Blogger Subdomain Forwarding

While setting up this blog, I was having trouble forwarding my "www" subdomain correctly within the GoDaddy Domain Manager page to my blogger blog. Currently, I want my top level (naked) domain (http://patrickmriley.net) along with my "www" subdomain (http://www.patrickmriley.net) to both forward to my Blogger blog. This can be achieved by using the GoDaddy DNS Zone File Editor and the GoDaddy Forwarding and Masking window (you might need to do some tinkering here, so grab a drink before trying at home). There is now a utility being offered by GoDaddy to do this but it doesn't seem to work very well for this scenario, so I have drawn up the steps below.

Step 1: Update your Zone file in the DNS Manager (A records for top level and www must point to 64.202.189.170 to tell GoDaddy this is a forwarded domain/subdomain, the blog subdomain should have a CNAME record pointed to ghs.google.com as indicated below)



Step 2: Use the GoDaddy Domain Manager Forwarding and Masking window to forward your domain and www subdomain to the desired subdomain. 


Step 3: Give it a few minutes and see if the forwarding takes effect. This can take up to 48 hours so be patient. If you get it right the first time, you'll save yourself a lot of frustration.

Hopefully this helps someone else out there!

Wednesday, May 30, 2012

PeekDataReader

For a current project, I needed to write an XML file for a batch transaction process. It just so happens that several transactions needed to be wrapped within a single batch element for a single order. What I needed was a simple extension class that wrapped SqlDataReader and allowed me to “peek” at the next row in the result set to check if the transaction was still within the current batch.
While doing a brief search, I found the following helper on Stack Overflow (SO). The correct way to do this is by implementing IDataReader as you cannot extend SqlDataReader. This is because SqlDataReader’s type constructor is marked internal in System.Data.dll and cannot be inherited.
The implementation on SO did not contain the pass-thru attributes and methods needed to implement IDataReader, IDataRecord, and IDisposable, so I decided to post this on my blog. Hope this helps someone out there!
sealed public class PeekDataReader : IDataReader
    {
        private IDataReader wrappedReader;
        private bool wasPeeked;
        private bool lastResult;

        public PeekDataReader(IDataReader wrappedReader)
        {
            this.wrappedReader = wrappedReader;
        }

        public bool Peek()
        {
            // If the previous operation was a peek, do not move...
            if (this.wasPeeked)
                return this.lastResult;

            // This is the first peek for the current position, so read and tag
            bool result = Read();
            this.wasPeeked = true;
            return result;
        }

        public bool Read()
        {
            // If last operation was a peek, do not actually read
            if (this.wasPeeked)
            {
                this.wasPeeked = false;
                return this.lastResult;
            }

            // Remember the result for any subsequent peeks
            this.lastResult = this.wrappedReader.Read();
            return this.lastResult;
        }

        public bool NextResult()
        {
            this.wasPeeked = false;
            return this.wrappedReader.NextResult();
        }

        #region IDataRecord pass-thru attributes and methods
        public int FieldCount { get { return this.wrappedReader.FieldCount; } }
        public object this[int i] { get { return this.wrappedReader[i]; } }
        public object this[string name] { get { return this.wrappedReader[name]; } }
        public bool GetBoolean(int i) { return this.wrappedReader.GetBoolean(i);}
        public byte GetByte(int i) { return this.wrappedReader.GetByte(i); }
        public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) {
            return this.wrappedReader.GetBytes(i,fieldOffset,buffer,bufferoffset,length);}
        public char GetChar(int i) { return this.wrappedReader.GetChar(i); }
        public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
        {
            return this.wrappedReader.GetChars(i,fieldoffset,buffer,bufferoffset,length);
        }
        public IDataReader GetData(int i)
        {
            return this.wrappedReader.GetData(i);
        }
        public string GetDataTypeName(int i) { return this.wrappedReader.GetDataTypeName(i); }
        public DateTime GetDateTime(int i) { return this.wrappedReader.GetDateTime(i); }
        public decimal GetDecimal(int i) { return this.wrappedReader.GetDecimal(i); }
        public double GetDouble(int i) { return this.wrappedReader.GetDouble(i); }
        public Type GetFieldType(int i) { return this.wrappedReader.GetFieldType(i); }
        public float GetFloat(int i) { return this.wrappedReader.GetFloat(i); }
        public Guid GetGuid(int i) { return this.wrappedReader.GetGuid(i); }
        public short GetInt16(int i) { return this.wrappedReader.GetInt16(i); }
        public int GetInt32(int i) { return this.wrappedReader.GetInt32(i); }
        public long GetInt64(int i) { return this.wrappedReader.GetInt64(i); }
        public string GetName(int i) { return this.wrappedReader.GetName(i); }
        public int GetOrdinal(string name) { return this.wrappedReader.GetOrdinal(name); }
        public string GetString(int i) { return this.wrappedReader.GetString(i); }
        public object GetValue(int i) { return this.wrappedReader.GetValue(i); }
        public int GetValues(object[] values) { return this.wrappedReader.GetValues(values); }
        public bool IsDBNull(int i) { return this.wrappedReader.IsDBNull(i); }
        #endregion

        #region IDataReader pass-thru attributes and methods
        public int Depth { get { return this.wrappedReader.Depth; } }
        public bool IsClosed { get { return this.wrappedReader.IsClosed; } }
        public int RecordsAffected { get { return this.wrappedReader.RecordsAffected; } }
        public void Close() { this.wrappedReader.Close(); }
        public DataTable GetSchemaTable() { return this.wrappedReader.GetSchemaTable(); }
        #endregion

        #region IDisposable
        public void Dispose() { this.wrappedReader.Dispose(); }
        #endregion
    }


Sample use:
using (IDataReader reader = new PeekDataReader(/* actual reader */))
{
    if (reader.Peek())
    {
        // perform some operations on the first row if it exists...
    }

    while (reader.Read())
    {
        // re-use the first row, and then read the remainder...
    }
}