﻿/*
You need to include the following prereq files to use this:
 jquery, and json2 (included by default in admin pages)
    
*/


function Filter(bind, value) {
    this.Bind = bind;
    this.Value = value;
}

function BindColumn(bind, header, parent, sortable) {
    this.bind = bind;
    this.header = header;
    this.parent = parent;
    this.sortable = sortable;
}

function ListView(container, editContainer, itemTemplate, editTemplate, pageSize, sortDir, sortCol, dataProvider, fetchOperation, saveOperation, filters, editOptions, itemsToBind, itemsIdentity, perPageOptions, itemsToBindHeaders, customOnItemBind, inlineEdit, inlineEditTemplate, editableColumns, dataStructure, customValidate, enableDelete, customOnItemSaving, enableChildOrdering, enableChildDeletion, customOnItemSaved, enableSearch, enableInsert, enableSelect, onGridViewBound, showTotalInPagingContainer, customOnFilter, showExportToCsvLink, dynamicDefaultDataStructure) {

    //public variables
    this.container = container;
    this.editContainer = editContainer;
    this.pagingContainer = $(container).find(".paging");
    this.pageSize = pageSize;
    this.pageNum = 1;
    this.queryString = new QueryString();
    this.itemTemplate = itemTemplate;
    this.editTemplate = editTemplate;
    this.itemsUniqueID = itemsIdentity;
    this.sortCol = sortCol;
    this.sortDir = sortDir;
    this.totalItems = -1;
    this.itemData = null;
    this.perPageDropDown = null;
    this.filters = filters;
    this.editOptions = editOptions;
    this.modalOptions = new Array();
    this.itemsToBind = itemsToBind;
    this.perPageOptions = perPageOptions;
    this.itemsToBindHeaders = itemsToBindHeaders;
    this.customOnItemBind = customOnItemBind;
    this.inlineEdit = inlineEdit;
    this.inlineEditTemplate = inlineEditTemplate;
    this.editableColumns = editableColumns;
    this.dataStructure = dataStructure;
    this.customValidate = customValidate;
    this.enableDelete = enableDelete;
    this.customOnItemSaving = customOnItemSaving;
    this.enableChildOrdering = enableChildOrdering;
    this.enableChildDeletion = enableChildDeletion;
    this.enableSearch = enableSearch;
    this.enableInsert = enableInsert;
    this.enableSelect = enableSelect;
    this.onGridViewBound = onGridViewBound;
    this.showTotalInPagingContainer = showTotalInPagingContainer;
    this.customOnFilter = customOnFilter;
    this.showExportToCsvLink = showExportToCsvLink;
    this.dynamicDefaultDataStructure = dynamicDefaultDataStructure;

    //private variables
    var _masterHolder = null;
    var _customOnItemSaved = customOnItemSaved;

    this.EDIT_MODE_INSERT = 'insert';
    this.EDIT_MODE_EDIT = 'edit';

    this._init = function () {

        this.setEditLevels($(this.editTemplate));

    }

    this.setEditLevels = function (item) {
        var itr = 0;
        $(item).attr('olv', itr);
        $(item).find('table[name]').each(function () {
            ++itr;
            $(this).attr('olv', itr);
        });
    }

    this.Populate = function (defaultSearch) {

        this.ShowMask();
        var me = this;

        var mainCon = me.GetContainer(me.dataStructure, null, me.itemsToBind);
        $(mainCon).find('.paging').find('.totalRecords').text('Searching...');

       if (defaultSearch) {
            $(this.container).find("input[name=search]").val(defaultSearch);
        }

        var filters = this.GetFiltersArray();

        var json = { pageSize: this.GetItemsPerPage(), pageNum: this.pageNum, sortCol: this.sortCol, sortDir: this.sortDir, filters: filters };


        $.ajax({
            type: "POST",
            url: dataProvider + '/' + fetchOperation,
            data: JSON.stringify(json),
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function (msg) {

                if (msg.d.Error && msg.d.Error.length > 0) {
                    alert(msg.d.Error);
                    me.HideMask();
                } else {
                    me.itemData = msg.d.Data;
                    me.totalItems = msg.d.TotalAvailable;
                    me.PopulateDOM(msg.d.Data, mainCon, null, false);
                }
            },
            error: function (msg) {
                var error = eval("(" + msg.responseText + ")");
                alert(error.Message);

                me.HideMask();
            }
        });

    }

    this.GetFiltersArray = function () {
        var filters = new Array();
        for (var i = 0; i < this.filters.length; i++) {
            var filter = this.filters[i];
            var filterSelectedValue = $(this.container).find("select[filterBinds=" + filter.bind + "]").val();
            filters.push(new Filter(filter.bind, filterSelectedValue));
        }
        var searchText = $(this.container).find("input[name=search]").val();
        if (searchText != '') {
            filters.push(new Filter("Search", searchText));
        }

        if (this.customOnFilter != null) {
            return this.customOnFilter.call(this, filters);
        }

        return filters;
    }

    this.PopulateDOM = function (data, container, parent, isEditing) {

        var tableCon = container;
        $(tableCon).find(".headerRow").siblings().not(".keepRow").remove();
        var me = this;

        for (var i = data.length - 1; i >= 0; i--) {

            var dataItem = data[i];

            var toClone = this.GetItemTemplate(this.dataStructure, parent);
            if (!isEditing) {
                $(toClone).find('.arrowBtns').hide();
            }
            var newRow = this.PopulateRow(dataItem, toClone, parent, isEditing, false);
            if (i % 2 == 1)
                $(newRow).addClass('altRow');

            $(tableCon).find(".headerRow:first").after(newRow);
        }



        if (parent == null) {
            this.SetPaging();
            this.HideMask();
            if (this.onGridViewBound) {
                this.onGridViewBound.apply(this, []);
            }
        }
    }

    this.PopulateRow = function (dataItem, toPopulate, parent, isEditing, isNew) {

        var markup = toPopulate;

        for (prop in dataItem) {
            var isDropDown = typeof this.editOptions[prop] != 'undefined' && this.editOptions[prop].length > 0;
            var isCheckBox = typeof this.editOptions[prop] != 'undefined' && this.editOptions[prop].length == 0;

            if (!isEditing || (isEditing && $.inArray(prop, this.editableColumns) == -1)) {

                if (dataItem[prop] != null && dataItem[prop].toString().indexOf('/Date(') != -1) {
                    $(markup).find('.__' + prop + '__').replaceWith(this.GetShortDateString(dataItem[prop]));
                } else if (dataItem[prop] instanceof Array && $(markup).find("table[container]").length > 0) {
                    var tableToPopulate = $(markup).find("table[container]");
                    this.PopulateDOM(dataItem[prop], tableToPopulate, prop, isEditing);
                } else {
                    if (typeof dataItem[prop] != "object" || dataItem[prop] instanceof Array) {
                        var ourText;
                        var dataItemText = dataItem[prop] != null ? dataItem[prop].toString() : '(null)'; //hack for nulls to save as such instead of string 'null'
                        try { ourText = decodeURIComponent(dataItemText); } catch (err) { ourText = dataItemText; }
                        var trans = false; //see if we transform it
                        if (this.editOptions[prop] != undefined) { //edit options has a transformation to use
                            for (var itr = 0; itr < this.editOptions[prop].length; ++itr) {

                                //support multiple input formats, first is traditional, second is when using Types.GetJsonBindData function
                                var text, value;
                                if (this.editOptions[prop][itr].Text) {
                                    text = this.editOptions[prop][itr].Text;
                                    value = this.editOptions[prop][itr].Value;
                                } else if (this.editOptions[prop][itr].Key) {
                                    text = this.editOptions[prop][itr].Value;
                                    value = this.editOptions[prop][itr].Key;
                                }

                                if (value == ourText) {
                                    $(markup).find('.__' + prop + '__').replaceWith(text);
                                    trans = true;
                                    break;
                                }
                            }
                        }

                        if (!trans) {
                            $(markup).find('.__' + prop + '__').replaceWith(ourText);
                        }
                    }
                }

                if (isEditing && (!(dataItem[prop] instanceof Array) && prop.indexOf('__') != 0)) {
                    var firstCell = $(markup).find('td:first');
                    var valForInput = '';
                    if (dataItem[prop] != null && dataItem[prop].toString().indexOf('/Date(') != -1) {
                        valForInput = dataItem[prop];
                    } else if (dataItem[prop] != null) {
                        valForInput = encodeURIComponent(dataItem[prop]);
                    } else { //dataItem[prop] == null
                        valForInput = "(null)";
                    }
                    $(firstCell).append('<input type="hidden" name="' + prop + '" value="' + valForInput + '" />');
                }

            } else {

                if (isDropDown) { //dropdown
                    $(markup).find('.__' + prop + '__').replaceWith('<select class="' + prop + '" name="' + prop + '"></select>');
                } else if (isCheckBox) {
                    if (dataItem[prop]) {
                        $(markup).find('.__' + prop + '__').replaceWith('<input type="checkbox" name="' + prop + '" checked/>');
                    } else {
                        $(markup).find('.__' + prop + '__').replaceWith('<input type="checkbox" name="' + prop + '" />');
                    }
                } else { //text  input
                    var toPopulateVal = dataItem[prop] != null ? dataItem[prop].toString() : '';
                    if (toPopulateVal.indexOf('/Date(') != -1) {
                        toPopulateVal = this.GetShortDateString(dataItem[prop]);
                    }

                    //replace single and double quotes with their html entity
                    toPopulateVal = toPopulateVal.replace(/"/g, "&quot;").replace(/'/g, "&#039;");

                    $(markup).find('.__' + prop + '__').replaceWith('<input type="text" class="' + prop + '" name="' + prop + '" value="' + toPopulateVal + '" />');
                }

            }
        }


        if (parent == null) {
            var itemID = this.GetItemKey(dataItem);
            $($(markup)[0]).addClass('oxenLvItemRow');
            jQuery.data($(markup).find("a.select")[0], "uid", { uid: itemID });

            //convert to .live at some point
            $(markup).find("a.select").bind("click", function () { me.SelectItem(this) });
            $(markup).find("a.cancel").bind("click", { me: this }, this.CancelEdit);
            $(markup).find("a.save").bind("click", { me: this }, this.Save);

            if (isEditing) {
                $(markup).find('.select').hide();
                $(markup).find('.cancel').show();
                $(markup).find('.save').show();
                if (this.enableDelete) {
                    $(markup).find('.delete').show();
                }
            }

        } else if (isEditing && (this.enableDelete || this.enableChildDeletion)) {
            $(markup).find('.delete').show();
        }

        if (isEditing) {
            this.SetEditOptions(markup, this.editOptions)
            $(markup).find('select').each(function () {
                $(this).val(dataItem[this.name]);
            });
        }

        if (this.customOnItemBind != null && parent == null) {
            this.customOnItemBind.apply(this, [markup, dataItem, isEditing]);
        }

        if (!isNew) {
            $(markup).addClass('saved');
        }

        $(markup).find('a.delete').bind('click', { me: this }, this.EditDeleteClick);

        return markup;
    }

    this.GetItemKey = function (dataItem) {
        var parts;
        if (this.itemsUniqueID.indexOf('+') > 0)
            parts = this.itemsUniqueID.split('+');
        else
            parts = [this.itemsUniqueID];

        var result = '';
        for (var i = 0; i < parts.length; ++i)
            result += dataItem[parts[i]];

        return result;
    }

    this.GetItemTemplate = function (dataStructure, parent) {
        var itemRow = $('<tr></tr>'); //parent row element

        if (parent == null) {
            //create the select element
            var selectCell = $('<td></td>');

            var selectLink = $('<a>edit</a>');
            $(selectLink).addClass('select');
            $(selectCell).append(selectLink);

            var saveLink = $('<a>save</a>');
            $(saveLink).addClass('save');
            $(selectCell).append(saveLink);
            $(saveLink).hide();

            $(selectCell).append('&nbsp;');

            var cancelLink = $('<a>cancel</a>');
            $(cancelLink).addClass('cancel');
            $(selectCell).append(cancelLink);
            $(cancelLink).hide();

            $(itemRow).append(selectCell);
            if(!this.enableSelect)
                $(selectCell).hide();
        }

        //create all the other elements, either from the list of items to 
        //bind provided on init or from the dataitem we are binding
        var itemsToCreate = [];
        if (this.itemsToBind != null) {
            $(this.itemsToBind).each(function () {
                if (this.parent == parent) {
                    itemsToCreate.push(this);
                }
            });
        } else {

            for (prop in dataStructure) {
                itemsToCreate.push(new BindColumn(prop, prop, parent));
            }
        }

        var children = [];
        var me = this;

        $(itemsToCreate).each(function () {

            if (dataStructure[this.bind] instanceof Object) {
                var newParentCon = $('<tr></tr>');
                $(newParentCon).append('<td>&nbsp;&nbsp;&nbsp;&nbsp;<b>' + this.bind + ': </b></td><td colspan="' + itemsToCreate.length + '"></td>');
                var childCon = me.GetContainer(dataStructure[this.bind], this.bind, me.itemsToBind);
                $(childCon).attr("name", this.bind);
                $(newParentCon).find('td:last').append(childCon);
                children.push(newParentCon);

            } else {

                var cell = $('<td></td>');
                var eleItm = $('<span></span>');
                $(eleItm).addClass('__' + this.bind + '__');
                $(cell).append(eleItm);
                $(itemRow).append(cell);
            }
        });

        var endCell = $('<td></td>');
        var delLink = $('<a>delete</a>');
        $(delLink).addClass('delete');
        $(endCell).append(delLink);
        $(delLink).hide();
        $(itemRow).append(endCell);

        if (!this.enableDelete)
            $(endCell).hide();

        if (this.enableChildOrdering && parent != null) {
            var upArrow = $("<div class='upArrow'>&nbsp;</div>");
            var dnArrow = $("<div class='downArrow'>&nbsp;</div>");
            $(upArrow).bind('click', { me: this, dir: 'up' }, this.MoveRowClick);
            $(dnArrow).bind('click', { me: this, dir: 'dn' }, this.MoveRowClick);
            var arrowCon = $("<div class='arrowBtns'></div>");
            $(arrowCon).append(upArrow).append(dnArrow);
            $(endCell).append(arrowCon);
        }

        $(children).each(function () {
            itemRow = $(itemRow).add(this);
        });


        if (parent == null) {
            //save the markup to our object so we dont have to create this again.
            this.itemTemplate = itemRow;
        }

        return $(itemRow);
    }

    this.MoveRowClick = function (eventArgs) {
        var me = eventArgs.data.me;
        var dir = eventArgs.data.dir;

        var toSwap = null;
        var myRow = $(this).parents('tr:first');

        if (dir == 'up') {
            toSwap = $(myRow).prev();
        } else if (dir == 'dn') {
            toSwap = $(myRow).next();
        }

        //ensure that we can swap
        if (toSwap.length > 0 && !($(toSwap).hasClass('keepRow') || $(toSwap).hasClass('headerRow'))) {
            if (dir == 'up') {
                $(toSwap).before(myRow);
            } else if (dir == 'dn') {
                $(toSwap).after(myRow);
            }
        }
    }

    this.AddRowClick = function (eventArgs) {

        var me = eventArgs.data.me;

        var parentCon = me.GetParentRow(this, true);
        var parentConOnly = me.GetParentTable(this);
        var parentName = $(parentConOnly).attr('name') == undefined ? null : $(parentConOnly).attr('name');

        if (!$(parentCon).hasClass('editing') && parentName != null) {
            var itemData = me.GetRowItemData(parentCon);
            parentCon = me.PopulateInlineEdit(parentCon, itemData);
        }

        var emptyItemToBind = dataStructure;
        var newThis = this;
        if (parentName != null) {
            emptyItemToBind = dataStructure[parentName];
            newThis = $(parentCon).find('table[name=' + parentName + ']').find('a.addRow');
        }

        var defaultDataStructure = me.dynamicDefaultDataStructure.call() || me.dataStructure;

        var toPopulate = me.GetItemTemplate(defaultDataStructure, parentName);
        me.PopulateRow(emptyItemToBind, toPopulate, parentName, true, true);
        $(newThis).parents('tr:first').before(toPopulate);

        return;
    }

    this.GetRowItemData = function (row) {
        var select = $(row).find('a.select');
        var itemUid = jQuery.data(select[0], "uid").uid;
        var itemData = this.GetDataItemByRowID(itemUid);

        return itemData;
    }

    this.GetDataItemByRowID = function (id) {
        var itemData = null;
        for (var i = 0; i < this.itemData.length; i++) {
            var key = this.GetItemKey(this.itemData[i]);
            if (key == id) {
                itemData = this.itemData[i];
                break;
            }
        }

        return itemData;
    }

    this.GetRowByRowID = function (id) {
        var itemIndex = null;
        for (var i = 0; i < this.itemData.length; i++) {
            var key = this.GetItemKey(this.itemData[i]);
            if (key == id) {
                itemIndex = i;
                break;
            }
        }

        return $(this.container).children().children().not('.keepRow')[i];
    }

    this.SetRowElement = function (id, name, val) {
        var row = this.GetRowByRowID(id);
        var element = $(row).find('[name=' + name + ']');

        if ($(element).is(':input')) {
            $(element).val(val);
        } else {
            $(element).text(val);
        }


    }

    this.GetParentRow = function (item, includeChildren) {

        if ($(item).hasClass('oxenLvItemRow')) {
            if (includeChildren) {
                return this.GetChildRows(item);
            } else {
                return item;
            }
        }

        var parentCon = $(item).parents('tr:first');
        while ($(parentCon).parents('table:first').data('parent') != null) {
            parentCon = $(parentCon).parents('tr:first');
        }

        while (!$(parentCon).hasClass('oxenLvItemRow') && !$(parentCon).hasClass('keepRow')) {
            parentCon = $(parentCon).prev();
        }

        if (includeChildren) {
            return this.GetChildRows(parentCon);
        } else {
            return parentCon;
        }
    }

    this.GetParentTable = function (item) {
        if ($(item).attr('container') != null) {
            return item;
        }

        var parentCon = $(item).parents('table[container]:first');
        return parentCon;
    }

    this.GetChildRows = function (parentRow) {
        var nextItem = $(parentRow).next();
        while (nextItem != null && nextItem.length > 0 && !($(nextItem).hasClass('keepRow') || $(nextItem).hasClass('oxenLvItemRow'))) {
            parentRow = $(parentRow).add(nextItem);
            nextItem = $(nextItem).next();
        }

        return parentRow;
    }

    this.GetContainer = function (dataStructure, parent, toBind) {

        var me = this;

        if (parent == null && _masterHolder != null) {
            //$(_masterHolder).children().children().not('.keepRow').remove();
            return _masterHolder;
        }

        var itemsToCreate = [];
        if (toBind != null) {
            $(toBind).each(function () {
                if (this.parent == parent) {
                    itemsToCreate.push(this);
                }
            });
        } else {

            for (prop in dataStructure) {
                itemsToCreate.push(new BindColumn(prop, prop, parent));
            }
        }

        var totalCols = itemsToCreate.length + 1;
        if (this.enableDelete)
            totalCols += 1;

        var myCon = $('<table class="tableClass" container=container></table>');
        $(myCon).data('parent', parent);

        if (parent == null) {
            $(myCon).append('<tr class="keepRow filtersRow"><td colspan="' + totalCols + '" class="filtersCon"><div>Filters:</div></td></tr>');
            $(myCon).append('<tr class="keepRow searchRow"><td colspan="' + totalCols + '"><span>Search: </span><input name="search" type="text" /> <a class="searchBtn">Search</a> <a class="clearSearchBtn">Clear</a></td></tr>');
            $(myCon).append('<tr class="keepRow perpageRow"><td colspan="' + totalCols + '"><div style="float:left;padding-right:10px;">Items Per Page:</div><div class="perpage"></div></td></tr>');
            $(myCon).append('<tr class="keepRow pagingRow"><td colspan="' + totalCols + '"><div class="paging"></div><div class="export"><a class="exportBtn">Export</a></div></td></tr>');

            if (!me.enableSearch) {
                $(myCon).find('input[name=search]').parent().parent().hide();
            }
        }

        var searchTxt = myCon.find("input[name=search]");
        var searchBtn = myCon.find(".searchBtn");

        searchTxt.keypress(function (e) {
            if (e.keyCode === 13) {
                searchBtn.click();
                e.preventDefault();
                e.stopPropagation();
            }
        }) 

        var headerRow = $('<tr class=\'keepRow headerRow\'></tr>');
        if (parent == null) {
            var emptyCol = $('<td>');
            $(headerRow).append(emptyCol);
            if (!this.enableSelect)
                $(emptyCol).hide();
        }
        $(itemsToCreate).each(function () {
            if (this.parent == parent && (dataStructure[this.bind] == null || !(dataStructure[this.bind] instanceof Object))) {
                var headerItemCon = $('<td></td>');
                var headerText = null;
                if (this.sortable) {

                    //                    var dirIndicator = null;
                    //                    if (me.sortCol == this.bind) {
                    //                        dirIndicator = $('<span></span>');
                    //                        if(me.sortDir == 'asc') {
                    //                            $(dirIndicator).addClass('asc');
                    //                        } else {
                    //                            $(dirIndicator).addClass('desc');
                    //                        }
                    //                    }

                    headerText = $('<a class=\'colHead colSort\'></a>');
                    $(headerText).append(this.header);
                    $(headerText).bind('click', { me: me, col: this.bind }, me.SortColumn);
                } else {
                    headerText = $('<span class=\'colHead\'>' + this.header + '</span>');
                }

                $(headerItemCon).append(headerText);
                $(headerRow).append(headerItemCon);
            }
        });

        var deleteCellHolder = $('<td>&nbsp;</td>');
        $(headerRow).append(deleteCellHolder); //delete cell will be below this
        $(myCon).append(headerRow);

        if (!this.enableDelete)
            $(deleteCellHolder).hide();

        if (me.enableInsert) {
            $(myCon).append('<tr class="keepRow insertRow"><td colspan="' + totalCols + '"><div><a class="addRow">Add New Entry</a></div></td></tr>');
            $(myCon).find('a.addRow').bind('click', { me: me }, me.AddRowClick);
        }

        if (parent == null) {
            //            $(myCon).append('<tr class="keepRow"><td colspan="' + totalCols + '" class="filtersCon"><div>Filters:</div></td></tr>');
            //            $(myCon).append('<tr class="keepRow"><td colspan="' + totalCols + '">Search: <input name="search" type="text" /> <a class="searchBtn">Update Search</a> <a class="clearSearchBtn">Clear Search</a></td></tr>');
            //            $(myCon).append('<tr class="keepRow perpageRow"><td colspan="' + totalCols + '"><div style="float:left;padding-right:10px;">Items Per Page:</div><div class="perpage"></div></td></tr>');
            //            $(myCon).append('<tr class="keepRow pagingRow"><td colspan="' + totalCols + '"><div class="paging"></div></td></tr>');

            var prevPerPageVal = $('div.perpage select').val();
            var prevSrcVal = $('input[name=search]').val();

            $(this.container).replaceWith(myCon);
            this.container = myCon;

            this.SetFilters(this.filters);
            this.pagingContainer = $(myCon).find('.paging');

            //this.SetPaging();
            this.SetPerPage();

            if (typeof prevPerPageVal != 'undefined') {
                $(this.container).find('div.perpage select').val(prevPerPageVal);
            }

            if (typeof prevSrcVal != 'undefined') {
                $(this.container).find('input[name=search]').val(prevSrcVal);
            }

            _masterHolder = this.container;

            $(myCon).append('<tr class="loadingRow"><td colspan="' + totalCols + '"><div class="loading"></div></td></tr>');
        }

        return myCon;
    }

    this.SortColumn = function (eventArgs) {
        var me = eventArgs.data.me;
        var col = eventArgs.data.col;

        if (me.sortCol == col) {
            if (me.sortDir == 'asc')
                me.sortDir = 'desc';
            else
                me.sortDir = 'asc';
        } else {
            me.sortCol = col;
        }

        me.Populate();
    }

    this.CancelEdit = function (eventArgs) {
        var me = eventArgs.data.me;

        var parentCon = me.GetParentRow(this, true);
        if (!$(parentCon).hasClass('saved')) {
            $(parentCon).remove();
            return;
        }

        var itemData = me.GetRowItemData(parentCon);

        var toClone = me.GetItemTemplate(me.dataStructure, null);
        me.PopulateRow(itemData, toClone, null, false, false);
        $(toClone).find('tr:first').removeClass('editing');
        $($(parentCon)[0]).before(toClone);
        $(parentCon).remove();
    }

    this.SelectItem = function (item) {
        var parentCon = me.GetParentRow(item, true);
        var itemData = this.GetRowItemData(parentCon);

        if (this.inlineEdit == true) {
            this.PopulateInlineEdit(parentCon, itemData);
        } else {
            this.PopulateEdit(itemData);
        }
    }

    this.GetRowAndSiblings = function (sender) {
        var parentCon = $(sender).parents('.oxenLvItemRow:first');
        var nextItem = $(parentCon).next();

        while (nextItem != null && nextItem.length > 0  && (!($(nextItem).hasClass('keepRow') || $(nextItem).hasClass('oxenLvItemRow')))) {
            parentCon = $(parentCon).add(nextItem);
            nextItem = $(nextItem).next();
        }

        return parentCon;
    }

    this.PopulateInlineEdit = function (item, itemData) {
        var rowCon = item;

        var markup = this.GetItemTemplate(this.dataStructure, null);
        this.PopulateRow(itemData, markup, null, true, false);
        $(markup).find('tr:first').addClass('editing');

        //$(markup).find('a.delete').bind('click', {me: this }, this.EditDeleteClick);

        var key = this.GetItemKey(itemData);
        jQuery.data($(markup).find("a.select")[0], "uid", { uid: key });

        $($(markup)[0]).addClass('oxenLvItemRow').addClass('editing');
        //$(markup).find('a.addRow').bind('click', { me: this }, this.AddRowClick);
        $($(rowCon)[0]).prev().after(markup);
        $(rowCon).remove();

        return markup;
    }

    this.PopulateEdit = function (dataItem) {
        var me = this;
        var markup = $(this.editTemplate);
        this.SetEditOptions(markup, this.editOptions);

        for (var i = 0; i < this.modalOptions.length; i++) {
            var queryOpps = this.modalOptions[i];
            $.each($("a.thickbox"), function () { $(this).attr("href", me.queryString.AppendToQueryString($(this).attr("href"), queryOpps.key, queryOpps.value)); });
        }

        this.SetMode(this.EDIT_MODE_EDIT);
        this.PopulateFields($(this.editTemplate), dataItem);
    }

    this.PopulateFields = function (markup, dataItem) {
        var me = this;
        var diProp = {};
        for (diProp in dataItem) {
            var field = $(markup).find('[name=' + diProp + ']');

            $.each(field, function () {
                if (dataItem[diProp] != null) {

                    if ($(this).is("span")) {
                        if (dataItem[diProp].toString().indexOf('/Date(') != -1) {
                            $(this).html(me.GetShortDateString(dataItem[diProp].toString()));
                        } else {
                            $(this).html(dataItem[diProp].toString());
                        }

                    } else if ($(this).is("input")) {

                        if ($(this).attr("type") == "checkbox") {
                            $(this).attr("checked", dataItem[diProp]);
                        } else if ($(this).attr("type") == "text") {
                            $(this).val(dataItem[diProp].toString());
                        } else if ($(this).attr("type") == "hidden") {
                            $(this).val(dataItem[diProp].toString());
                        }

                    } else if ($(this).is("select")) {

                        $(this).val(dataItem[diProp]);

                    } else if ($(this).is("textarea")) {

                        $(this).val(dataItem[diProp]);

                    } else if ($(this).is("a")) {

                        var url = $(this).attr("href");
                        $(this).attr("href", me.queryString.AppendToQueryString(url, diProp, dataItem[diProp]));

                    } else if ($(this).is("img")) {

                        var url = $(this).attr("src");
                        url = me.queryString.AppendToQueryString(url, diProp, dataItem[diProp]);
                        url = me.queryString.AppendToQueryString(url, "versioning", (Math.floor(Math.random() * 1000000)).toString());
                        $(this).attr("src", url);

                    } else if ($(this).is('table') && typeof $(this).attr("templateID") != "undefined") {

                        var templateID = $(this).attr("templateID");
                        var tableItemTemplate = $("." + templateID);
                        var newRows = document.createElement('span');

                        for (var j = 0; j < dataItem[diProp].length; j++) {
                            var childMarkup = $(tableItemTemplate).clone().removeClass(templateID);
                            me.SetEditOptions(childMarkup, me.editOptions);
                            me.PopulateFields(childMarkup, dataItem[diProp][j]);
                            $(childMarkup).find('.delete').bind('click', { me: me }, me.EditDeleteClick);
                            $(childMarkup).appendTo(newRows);
                            $(childMarkup).addClass('saved');
                        }

                        $(this).find('tr').not('.keepRow').remove();
                        $(this).find('.header').after($(newRows).find('tr'));

                    }
                }
            });
        }
    }

    this.PerPageClick = function (eventArgs) {
        var me = eventArgs.data.me;
        var newPerPage = me.GetItemsPerPage();

        if (me.pageSize == newPerPage) {
            return;
        }

        me.pageSize = newPerPage;
        if ((me.pageNum - 1) * newPerPage > me.totalItems) {
            me.pageNum = 1;
        }

        me.Populate();
    }

    this.SetPerPage = function () {

        if (this.perPageOptions == null) {
            return;
        }

        var defaultAmounts = this.perPageOptions;

        var dropDown = document.createElement('select');
        for (var itr = 0; itr < defaultAmounts.length; itr++) {

            var newSelectOption = document.createElement('option');
            newSelectOption.text = ' ' + defaultAmounts[itr] + ' ';
            newSelectOption.value = defaultAmounts[itr];
            newSelectOption.selected = defaultAmounts[itr] == this.pageSize;

            try {
                dropDown.add(newSelectOption, null); // standards compliant; doesn't work in IE
            }
            catch (ex) {
                dropDown.add(newSelectOption); // IE only
            }
        }

        var me = this;
        $(dropDown).bind("change", { me: me }, me.PerPageClick);
        $(this.container).find(".perpage").append(dropDown);
        this.perPageDropDown = dropDown;
    }

    this.GetItemsPerPage = function () {
        if (this.perPageOptions == null) {
            return 32767;
        }
        return (typeof me.perPageDropDown != 'undefined' ? parseInt($(me.perPageDropDown).val()) : 32767);
    }

    this.SetPaging = function () {
        var numPages = 0;
        var pageSize = this.GetItemsPerPage();
        if (pageSize > 0) {
            numPages = Math.ceil(this.totalItems / pageSize);
        } else {
            numPages = 0;
        }
        var paging = $(this.pagingContainer);
        var me = this;

        $(paging).find("span").remove();

        for (var i = 1; i <= numPages; i++) {

            var newLink = document.createElement('a');
            tn = document.createTextNode(i.toString());
            newLink.appendChild(tn);

            $(newLink).bind("click", function () { me.PageClick(this) });

            var newLinkCon = document.createElement('span');
            if (i == this.pageNum) {
                newLinkCon.className = 'pagingLink selected';
            } else {
                newLinkCon.className = 'pagingLink';
            }

            newLinkCon.appendChild(newLink);

            $(paging).append(newLinkCon);
        }

        if (this.showTotalInPagingContainer) {
            var s = document.createElement('span');
            s.className = 'totalRecords';
            $(s).text('Showing ' + this.totalItems + " records");
            $(paging).append(s);
        }
    }

    this.PageClick = function (sender) {
        var newPageNum = parseInt($(sender).html());
        this.pageNum = newPageNum;
        this.Populate();
    }

    this.SetMode = function (mode) {

        return;

        var modeCon = this.editTemplate;
        var modeInput = $(modeCon).find("[name=mode]");
        if (modeInput.length == 0) {
            $(modeCon).prepend("<input type=\"hidden\" name=\"mode\" />");
            modeInput = $(modeCon).find("[name=mode]");
        }

        $(this.editContainer).find(".editorMode").html(mode);
        $(modeInput).val(mode);

        if (mode == this.EDIT_MODE_INSERT) {
            $(this.editContainer).find(".insertNew").hide();
            $(this.editTemplate).find(".editOnly").hide();
        } else {
            $(this.editContainer).find(".insertNew").show();
            $(this.editTemplate).find(".editOnly").show();
        }
    }

    this.BindInsertView = function (eventArgs) {

        var editInputs = $(this.editTemplate).find("[name]");
        $.each(editInputs, function () {
            if ($(this).is("span")) {
                $(this).html('');
            } else if ($(this).is("input")) {
                if ($(this).attr("type") == "checkbox") {
                    $(this).attr("checked", 'checked');
                } else if ($(this).attr("type") == "text") {
                    $(this).val('');
                } else if ($(this).attr("type") == "hidden") {
                    $(this).val('');
                }
            }
        });

        eventArgs.data.me.SetMode(eventArgs.data.me.EDIT_MODE_INSERT);
    }

    this.RefreshListView = function () {
        this.Populate();
    }

    this.GetShortDateFromJsonString = function (jsonDate) {
        return new Date(parseInt(/\/Date\((\d+).*/.exec(jsonDate)[1]));
    }

    this.GetShortDateString = function (jsonDate) {

        var date;
        try {
            date = this.GetShortDateFromJsonString(jsonDate);
        } catch (err) {
            return '';
        }

        var result = '';

        var month = date.getMonth() + 1;
        var day = date.getDate();
        var year = date.getFullYear();

        result = month + "/" + day + "/" + year;

        var hours = date.getHours();
        if (hours > 12)
            hours -= 12;
        var minutes = date.getMinutes();
        if (minutes < 10) {
            minutes = "0" + minutes;
        }
        result += " " + hours + ":" + minutes + " ";
        if (date.getHours() > 11) {
            result += "PM";
        } else {
            result += "AM";
        }

        return result;
    }

    this.ShowMask = function () {
        var rowsToMask = $(this.container).find(".headerRow").siblings().not(".keepRow");

        if (rowsToMask.length > 0) {
            var maskWidth = $(rowsToMask).width();
            var maskHeight = 0;
            $(rowsToMask).each(function () {
                maskHeight += $(this).height();
            })

            var offset = $(rowsToMask).find(":first").offset();

            var maskMarkup = "<div id='gridMask'></div>";
            $("body").append(maskMarkup);
            $("#gridMask").css({ 'width': maskWidth + 'px', 'height': maskHeight + 'px', 'top': offset.top + 'px', 'left': offset.left + 'px' });
        }
    }

    this.SearchClick = function (eventArgs) {

        var me = eventArgs.data.me;
        me.pageNum = 1;
        me.pageSize = me.GetItemsPerPage();
        me.Populate();
    }

    this.ClearSearchClick = function (eventArgs) {

        var me = eventArgs.data.me;
        $(me.container).find("input[name=search]").val('');

        me.Populate();
    }

    this.ExportClick = function (eventArgs) {

        var me = eventArgs.data.me;
        bas.utils.getCsv(me.itemData, 'YeahBuddy.csv');
    }

    this.HideMask = function () {
        $("#gridMask").remove();
    }

    this.FilterChange = function (eventArgs) {
        var me = eventArgs.data.me;
        me.pageNum = 1;
        me.pageSize = me.GetItemsPerPage();
        me.Populate();
    }

    this.SetFilters = function (filters) {

        var filtersCon = $(this.container).find(".filtersCon");

        if (filters.length == 0) {
            $(filtersCon).hide();
        }

        for (var i = 0; i < filters.length; i++) {
            var filter = filters[i];

            var filterCon = document.createElement("div");
            filterCon.className = "filterCon " + filter.bind;
            var tn = document.createTextNode(filter.title + ": ");
            filterCon.appendChild(tn);

            var newDropDown = document.createElement("select");
            newDropDown.setAttribute("filterBinds", filter.bind);
            var filterSelectOptions = filter.data;

            for (var itr = 0; itr < filterSelectOptions.length; itr++) {

                var newSelectOption = document.createElement('option');
                newSelectOption.text = ' ' + filterSelectOptions[itr].Text + ' ';
                newSelectOption.value = filterSelectOptions[itr].Value;

                try {
                    newDropDown.add(newSelectOption, null); // standards compliant; doesn't work in IE
                }
                catch (ex) {
                    newDropDown.add(newSelectOption); // IE only
                }
            }

            $(newDropDown).bind("change", { me: me }, this.FilterChange);
            if (filter.initialVal) {
                $(newDropDown).val(filter.initialVal);
            }

            filterCon.appendChild(newDropDown)

            $(filtersCon).append(filterCon);
        }

        $(this.container).find(".searchBtn").bind("click", { me: me }, this.SearchClick);
        $(this.container).find(".clearSearchBtn").bind("click", { me: me }, this.ClearSearchClick);
        $(this.container).find(".exportBtn").bind("click", { me: me }, this.ExportClick);
    }

    this.SetEditOptions = function (editContainer, editOptions) {

        for (key in editOptions) {

            var dropDown = $(editContainer).find("select[name=" + key + "]")[0];

            //no dropdown exists for edit option or it has already been populated
            if (dropDown == undefined || dropDown.length > 0) {
                continue;
            }

            var editSelectOptions = editOptions[key];

            for (var itr = 0; itr < editSelectOptions.length; itr++) {

                var newSelectOption = document.createElement('option');

                //support different formats of data, first is traditional, second is when using Types.GetJsonBindData function
                var text, value;
                if (editSelectOptions[itr].Text) {
                    text = editSelectOptions[itr].Text;
                    value = editSelectOptions[itr].Value;
                } else if (editSelectOptions[itr].Key) {
                    text = editSelectOptions[itr].Value;
                    value = editSelectOptions[itr].Key;
                }

                newSelectOption.text = ' ' + text + ' ';
                newSelectOption.value = value;

                try {
                    dropDown.add(newSelectOption, null); // standards compliant; doesn't work in IE
                }
                catch (ex) {
                    dropDown.add(newSelectOption); // IE only
                }
            }
        }

        var me = this;
    }

    this.EditDeleteClick = function (eventArgs) {

        var me = eventArgs.data.me;
        var clickRow = $(this).parents('tr:first');


        if ($(clickRow).hasClass('toDelete')) {
            $(this).text('delete');
            $(clickRow).removeClass('toDelete');
            return;
        }

        if ($(clickRow).hasClass('saved')) {
            $(clickRow).addClass('toDelete');
            $(this).text('undo');
        } else {
            if ($(clickRow).hasClass('oxenLvItemRow')) {
                me.GetRowAndSiblings(this).remove();
            } else {
                $(clickRow).remove();
            }
        }

    }

    this.EditAddClick = function (eventArgs) {
        var me = eventArgs.data.me;
        var parentTab = $(this).parents('table:first');
        var clickRow = $(this).parents('tr:first');

        if (typeof $(parentTab).attr("templateID") != "undefined") {
            var templateClass = $(parentTab).attr("templateID");
            var tableItemTemplate = $("." + templateClass);
            var newRow = $(tableItemTemplate).clone().removeClass(templateClass);
            me.SetEditOptions(newRow, me.editOptions);
            $(newRow).find('.delete').bind('click', { me: me }, me.EditDeleteClick);
            $(clickRow).before(newRow);
        }
    }

    this.Save = function (eventArgs) {
        var me = eventArgs.data.me;

        var values = {};
        var curOlv = 0;
        var callerParent = me.GetRowAndSiblings(this);
        var isNew = !$(callerParent).hasClass('saved');

        values = me.SaveRecurse(callerParent, curOlv, values);

        if (me.customOnItemSaving != null) {
            me.customOnItemSaving.call(this, values);
        }

        if (me.customValidate != null) {
            var ctmValRslt = me.customValidate.call(this, me);
            if (!ctmValRslt) {
                return;
            }
        }

        var toDelete = $(callerParent).hasClass('toDelete');
        var json = { toSave: values, toDelete: toDelete };

        $.ajax({
            type: "POST",
            url: dataProvider + '/' + saveOperation,
            data: JSON.stringify(json),
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function (msg) {

                if (toDelete) {
                    $(callerParent).remove();
                } else {

                    if (msg.d.Error && msg.d.Error.length > 0) {
                        alert(msg.d.Error);
                        return;
                    }

                    if (isNew) {
                        me.itemData.push(msg.d.Data);
                    } else {
                        var itemUid = $(callerParent).find("a.select").data('uid').uid;
                        for (var i = 0; i < me.itemData.length; i++) {
                            var key = me.GetItemKey(me.itemData[i]);
                            if (key == itemUid) {
                                me.itemData[i] = msg.d.Data;
                                break;
                            }
                        }
                    }

                    var toPopulate = me.GetItemTemplate(me.dataStructure, null);
                    me.PopulateRow(msg.d.Data, toPopulate, null, false, false);
                    $($(callerParent)[0]).prev().after(toPopulate);
                    $(callerParent).remove();

                    if (_customOnItemSaved != null) {
                        _customOnItemSaved.call(this, msg.d.Data);
                    }
                }
            },
            error: function (msg) {
                alert(me.GetErrorMessage(msg.responseText));
            }
        });

    }

    this.GetErrorMessage = function (response) {
        var begin = response.indexOf('"{"Message":') + 13;
        var end = response.indexOf('","StackTrace":"');

        return response.substring(begin, end);
    }

    this.SaveRecurse = function (forms, curOlv, saveObj) {

        var me = this;

        $(forms).each(function () {

            var parentName = null;
            if (!$(this).hasClass('oxenLvItemRow')) {
                parentName = $(this).find('[container]').attr('name');
                saveObj[parentName] = [];

                $(this).find('[container]').find('tr').not('.keepRow').not('.headerRow').each(function () {
                    var childSaveObj = {};
                    childSaveObj = me.SaveRow(this, childSaveObj);
                    if (childSaveObj != null) {
                        saveObj[parentName].push(childSaveObj);
                    }
                });

            } else {
                me.SaveRow(this, saveObj);
            }


        });

        return saveObj;

    }

    this.SaveRow = function (row, saveObj) {
        var savedPropertiesCount = 0;
        $(row).find(':input').not(':button').each(function () {

            var isHiddenInput = $(this).is('input[type=hidden]');
            if (($(this).is(':visible') || isHiddenInput) &&
                ($(this).parents('.toDelete').length == 0 || $(this).parents('.oxenLvItemRow').length != 0)) {

                if ($(this).is(':checkbox')) {
                    saveObj[this.name] = $(this).is(':checked');
                }
                else {
                    if ($(this).val().toString().indexOf('/Date(') != -1) {
                        var date = me.GetShortDateFromJsonString($(this).val());
                        saveObj[this.name] = date;
                    } else {
                        var val;

                        //only decode non hidden inputs so that we can allow encoded values for user entered data
                        if (isHiddenInput) {
                            try { val = decodeURIComponent($(this).val()) } catch (ex) { val = $(this).val(); };
                        } else {
                            val = $(this).val();
                        }

                        if (val === "(null)")
                            val = null;
                        saveObj[this.name] = val;
                    }
                }

                savedPropertiesCount++;

            }
        });

        if (savedPropertiesCount > 0) {
            return saveObj;
        } else {
            return null;
        }
    }

    this.isEmpty = function (ob) {
        for (var i in ob) { return false; }
        return true;
    }


    //start this up
    var me = this;
    this.SetMode('insert');
    this.SetFilters(filters);
    //this.SetEditOptions(editOptions);
    this.SetPerPage();

    $(this.editContainer).find(".insertNew").bind("click", { me: me }, this.BindInsertView);
    $(this.editContainer).find(":input[type=button]").bind("click", { me: me }, this.Save);
    $(this.editContainer).find('.add').bind('click', { me: me }, this.EditAddClick);

    this._init();
}

function QueryString() {
    this.location = window.location.search;
    this.result = {};

    this.Get = function (key) {
        return this.result[key];
    }

    this.AppendToQueryString = function (url, key, value) {

        var queryStart = url.indexOf('?');
        var keyLoc = url.indexOf(key, queryStart);

        if (keyLoc != -1) { //if present remove it! ack!
            var endInd = url.indexOf('&', keyLoc) + 1;
            if (endInd == 0) {
                endInd = url.length;
            }
            url = url.substring(0, keyLoc) + url.substring(endInd);
        }

        var result = '';
        if (queryStart != -1) {
            var begin = queryStart;
            result = url.substring(0, begin + 1);
            result += key + '=' + value + '&';
            result += url.substring(begin + 1);
        } else {
            result = url + '?' + key + '=' + value;
        }

        return result;
    }

    var keyValues = this.location.replace('?', '').split('&');
    for (var i = 0; i < keyValues.length; i++) {
        var keyValueItem = keyValues[i].split('=');
        this.result[keyValueItem[0]] = keyValueItem[1];
    }
}




(function ($) {
    $.fn.oxenListView = function (options) {

        var opts = $.extend({}, $.fn.oxenListView.defaults, options);

        var container = this;
        var listView = new ListView(container, opts.editContainer, opts.itemTemplate, opts.editTemplate, opts.pageSize, opts.sortDir, opts.sortCol, opts.dataProvider, opts.fetchOperation, opts.saveOperation, opts.filters, opts.editOptions, opts.itemsToBind, opts.itemsIdentity, opts.perPageOptions, opts.itemsToBindHeaders, opts.customOnItemBind, opts.inlineEdit, opts.inlineEditTemplate, opts.editableColumns, opts.dataStructure, opts.customValidate, opts.enableDelete, opts.customOnItemSaving, opts.enableChildOrdering, opts.enableChildDeletion, opts.customOnItemSaved, opts.enableSearch, opts.enableInsert, opts.enableSelect, opts.onGridViewBound, opts.showTotalInPagingContainer, opts.customOnFilter, opts.showExportToCsvLink, opts.dynamicDefaultDataStructure);

        if(opts.immediatePopulation) {
            listView.Populate(opts.defaultSearch);
        }
        return listView;
    }

    $.fn.oxenListView.defaults = {
        editContainer: null,
        sortDir: 'desc',
        sortCol: 'CreateDate',
        pageSize: 10,
        pageNum: 1,
        filters: [],
        editOptions: {},
        dataProvider: '',
        fetchOperation: '',
        saveOperation: '',
        itemTemplate: null,
        editTemplate: null,
        itemsToBind: null,
        itemsToBindHeaders: null,
        itemsIdentity: null,
        perPageOptions: [10, 20, 50, 100, 200],
        customOnFilter: null,
        customOnItemBind: null,
        customValidate: null,
        customOnItemSaving: null,
        customOnItemSaved: null,
        inlineEdit: false,
        inlineEditTemplate: null,
        editableColumns: null,
        dataStructure: null,
        enableDelete: false,
        enableChildOrdering: false,
        enableChildDeletion: false,
        enableSearch: true,
        enableInsert: true,
        enableSelect: true,
        immediatePopulation: true,
        onGridViewBound: null,
        showTotalInPagingContainer: false,
        showExportToCsvLink: true,
        dynamicDefaultDataStructure: function() { return false; },
        defaultSearch: null
    }
})(jQuery);


