﻿///<reference path="jquery-1.3.1-vsdoc.js"/>
///<reference path="jquery.cookie.js"/>
///<reference path="jquery.clipboard.js"/>
///<reference path="jquery.debug.js"/>
//<reference path="jquery-ui.js"/>
///<reference path="jquery.hotkeys-0.7.8-packed.js"/>
///<reference path="jquery.disable.text.select.js"/>
//<reference path="swfobject.js"/>

//<reference path="MicrosoftAjax.js"/>



//Example code for adding containers to the body of the page for dynamic themeing.
//$(document).ready(function() {
//    $(".CommonContentBox").before("<div class=\"CommonContentBoxBefore\"></div>").after("<div class=\"CommonContentBoxAfter\"></div>"); //.wrap("<div class=\"CommonContentBoxOuter\"><div class=\"r1\"><div class=\"r2\"><div class=\"r3\"><div class=\"r4\"></div></div></div></div></div>");
//    $(".CommonTitle").wrap("<div class=\"CommonTitleOuter\"><div class=\"r1\"><div class=\"r2\"><div class=\"r3\"><div class=\"r4\"></div></div></div></div></div>");
//});


(function($) {
    $.fn.removeCss = function(properties) {
        var q = $(this);
        if (typeof (properties) == "Array") {
            for (var t = 0; t < properties.length; t++) {
                q.get(0).style.removeAttribute(properties[t]);
            }
        } else {
            q.get(0).style.removeAttribute(properties);
        }
        return q;
    };
    $.fn.fadeInForIE = function(speed, callback) {
        var q = $(this)
        q.animate({ opacity: 1 }, speed, function() {
            if (jQuery.browser.msie)
                q.removeCss('filter');
            if (callback != undefined)
                callback();
        });
        return q;
    };
})(jQuery);

var tpsTabbedArea = {}
tpsTabbedArea.PageInit= function() {
    tpsTabbedArea.InitTabbedAreas();
};

tpsTabbedArea.InitTabbedAreas= function() {

    var tabbedAreas = $(".tpsTabbedArea");
    var tabbedArea;

    var tabs = tabbedAreas.children(".tabs:eq(0)").children(".tab");
    var panes = tabbedAreas.children(".panes:eq(0)").children(".pane");
    tabs.click(tpsTabbedArea.TabClick).eq(0).addClass('tabSelected').disableTextSelect();
    panes.not(":eq(0)").hide();
    panes.eq(0).show();

    tabbedAreas.show();

};

tpsTabbedArea.TabClick = function(TabBeingClicked) {

    var tabToSelect = typeof (TabBeingClicked) != "object" ? $("#" + TabBeingClicked) : $(this);
    var tabsToDeselect = tabToSelect.parent().find(".tab").not(tabToSelect).removeClass("tabSelected")
    $(tabToSelect).addClass("tabSelected");
    var paneToShow = $(tabToSelect.get(0).id.replace(/tab(.+)/, "#pane$1"));
    var panesToHide = tabToSelect.parents(".tpsTabbedArea").eq(0).children(".panes").children(".pane").not(paneToShow);
    paneToShow.show();
    panesToHide.hide();

};
$(tpsTabbedArea.PageInit);


/***********************************************
 * Hiding Pane                                 *
 **********************************************/

var tpsHidingPane ={};
tpsHidingPane.PageInit= function() { tpsHidingPane.InitHidingPanes(); }
tpsHidingPane.InitHidingPanes= function() {
    var hidingPanes = $(".tpsHidingPane");
    hidingPanes.find(".handle").click(tpsHidingPane.HandleClick).addClass("closed").disableTextSelect();
    hidingPanes.find(".pane").hide();
    hidingPanes.css({ opacity: 0, display: 'block' }).fadeInForIE();
};
tpsHidingPane.HandleClick= function(HandleBeingClicked) {
    var HandleBeingClicked = typeof (HandleBeingClicked) != "object" ? $("#" + HandleBeingClicked + " .handle") : $(this);
    var ThePane = HandleBeingClicked.parent().children(".pane");
    HandleBeingClicked.toggleClass("closed");
    ThePane.toggle();
} ;
$(tpsHidingPane.PageInit);


/***********************************************
 * Context Gesture                             *
 **********************************************/

var tpsContextGesture = {}
tpsContextGesture.PageInit= function() {

    };
tpsContextGesture.AttachGesture= function(dataitem, menu, fnGetDataContext) {
    //Call this when a dataitem is added or updated.
    //menu can either be the menu itself or a function pointer which will return a menu.
    //console.log("attaching gesture to " + dataitem.ThreadSubject);
    dataitem.UI.eq(0).draggable({
        start: tpsContextGesture.StartGesture,
        /*drag: tpsContextGesture.DuringGesture,*/
        stop: tpsContextGesture.EndGesture,
        helper: function() { return $('<div class="tpsDragSource"></div>'); },
        cursorAt: { top: 16, left: 16 }, //HACK
        delay: 100,
        greedy: true
    });
    dataitem.UI.eq(0).data("dragmenu", menu);
    dataitem.UI.eq(0).data("fnGetDataContext", fnGetDataContext);
    //console.log("attaching gesture for " + dataitem.UI.find(".lnkSubject").text());
};
    /*DuringGesture: function(evt, ui) {
    //console.log('gesturing');
    },*/
tpsContextGesture.StartGesture = function(evt, ui) {
    //this method gets called when a dataitem is drug
    //it will display the gesture drop targets in the appropriate locations and wire their events
    //console.log("starting gesture");
    //evt.stopPropagation();
    if (tpsContextGesture.GestureStarted == true) return;
    tpsContextGesture.GestureStarted = true;

    var menu = $(this).data("dragmenu");
    if (typeof (menu) == "function") menu = menu(); //The function will return the menu.

    if (!menu) {
        tpsContextGesture.GestureStarted = false;
        return;
    }

    var fnGetDataContext = $(this).data("fnGetDataContext");

    //debugger;
    // Initalize the final ContextGesture structure. This will eventually be returned to StartGesture.
    var ContextGestureMenu = {
        GestureRegionUI: $('<div class="tpsGestureMenu"></div>').css('opacity', 0)
        , GestureCenterUI: $('<div class="tpsGestureCenter"></div>')
        , GestureBackgroundUI: $('<div class="tpsGestureBackground"></div>')
        , GestureHoverLabelUI: $('<div class="tpsGestureLabel">(select one)</div>')
        , Coord: { Left: evt.pageX, Top: evt.pageY }
        , GroupedMenuItems: tpsContextGesture.SliceGesturesIntoFourSidesAndCorners(menu)
        , Droppables: []
        , TileSize: menu.TileSize
    };
    //debugger;
    tpsContextGesture.DrawMenu(ContextGestureMenu, fnGetDataContext);

    ContextGestureMenu.GestureBackgroundUI
        .appendTo(ContextGestureMenu.GestureRegionUI)
        .css({ left: evt.pageX - (ContextGestureMenu.GestureBackgroundUI.width() / 2) })
        .css({ top: evt.pageY - (ContextGestureMenu.GestureBackgroundUI.height() / 2) });

    //Add label, affix to the bottom, except for an overlap of 1/3
    ContextGestureMenu.GestureHoverLabelUI
        .appendTo(ContextGestureMenu.GestureRegionUI)
        .css({ left: evt.pageX - (ContextGestureMenu.GestureBackgroundUI.width() / 2) })
        .css({ top: (evt.pageY + (ContextGestureMenu.GestureBackgroundUI.height() / 2)) - (ContextGestureMenu.GestureHoverLabelUI.height()/3) });

    tpsContextGesture.RunningContextGestureMenu = ContextGestureMenu;
    ContextGestureMenu.GestureRegionUI.animate({ 'opacity': 1 }, 500);
    //debugger;
    //if ($(".tpsDragSource").length > 0) alert("found drag source!");
    //for (var i = 0; i < menu.length; i++) {

    //}

};

tpsContextGesture.DrawMenu = function(ContextGestureMenu, fnGetDataContext) {

    if (ContextGestureMenu == null) return;



    //Bank of functions we can draw from when drawing tiles.  Correct functions will be pulled in based on which side we're working on.
    //These functions act like a property getter & setter in one - if you don't pass newvalue it will behave as just a getter.
    var fnCoordLeft = function(coord, newvalue) {
        if (typeof (newvalue) != "undefined") coord.Left = newvalue;
        return coord.Left;
    }
    var fnCoordTop = function(coord, newvalue) {
        if (typeof (newvalue) != "undefined") coord.Top = newvalue;
        return coord.Top;
    }
    var fnSizeWidth = function(size, newvalue) {
        if (typeof (newvalue) != "undefined") size.Width = newvalue;
        return size.Width;
    }
    var fnSizeHeight = function(size, newvalue) {
        if (typeof (newvalue) != "undefined") size.Height = newvalue;
        return size.Height;
    }

    //Make the square big enough to fit all the items on the left side.  (the grouping algorithm ensures even distribution, with remainders stuffed in the first side(s).)
    //We are ignoring corners for the purposes of this calculation, hence the length offsets & and "if length > 2" below
    ContextGestureMenu.CenterSquareSize = {
        Width: Math.max(ContextGestureMenu.GroupedMenuItems[0].length - 1, 1) * ContextGestureMenu.TileSize.Width
        , Height: Math.max(ContextGestureMenu.GroupedMenuItems[0].length - 1, 1) * ContextGestureMenu.TileSize.Height
    }
    if (ContextGestureMenu.GroupedMenuItems[0].length > 2) {
        var increaseSizeBy = (ContextGestureMenu.GroupedMenuItems[0].length - 2) * ContextGestureMenu.TileSize.Margin;
        ContextGestureMenu.CenterSquareSize.Width += increaseSizeBy;
        ContextGestureMenu.CenterSquareSize.Height += increaseSizeBy;
    }
    //debugger;

    //Drop the core UI controls on the page and get them setup right.
    ContextGestureMenu.GestureCenterUI
        .css('width', ContextGestureMenu.CenterSquareSize.Width)
        .css('height', ContextGestureMenu.CenterSquareSize.Height)
        .css('left', ContextGestureMenu.Coord.Left - (ContextGestureMenu.CenterSquareSize.Width / 2))
        .css('top', ContextGestureMenu.Coord.Top - (ContextGestureMenu.CenterSquareSize.Height / 2))
        .eq(0).appendTo(ContextGestureMenu.GestureRegionUI);

    ContextGestureMenu.GestureRegionUI.eq(0).appendTo($(document.body));

    //Loop through each side & corner of the menu.
    for (var pos = 0; pos < 4; pos++) {
        /*                                                                                           
        * The Axis functions are used to return the X/Y coords appropriate for which side we are on.  
        * The "Flowing Axis" is the axis where the tiles run parallel to the inner square.           
        * The "Outside Axis" is always the opposite axis of the flowing axis.  On the left side,    
        * the flowing axis is Vertical, therefore the Outside axis is the Horizontal.                 
        * The "Flowing Direction" is used to reverse the flow of layout (numerically) so that the    
        * tiles always flow Clockwise.  Therefore, the left side would have a negative flowing         
        *                                                                                           
        *             SIDES:    Flows:         Flowing Dir:    Outside:              
        *   * * * *    Left:    Vert (Top);    - (Up);         Horiz (Left);       
        *   * ### *   Right:    Vert (Top);    + (Down);       Horiz (Left);            
        *   * ### *     Top:    Horiz (Left);  + (Right);      Vert (Top);            
        *   * * * *  Bottom:    Horiz (Left);  - (Left);       Vert (Top);          
        *                                                                                           
        * These concepts are defined here in a generic way so we can follow the same algorithm on     
        * each side without tons of "if" statements complicating the logic of the inner loop.        
        *                                                                                           
        * NOTE: "pos" values mean: 0=Left; 1=Top; 2=Right; 3=Bottom;                                  
        *                                                                                          */

        //Axis functions provide coordinates related to the side we're on.
        var fnOutsideAxis = (pos == 0 || pos == 2) ? fnCoordLeft : fnCoordTop;  // outside axis (Left Side= Go left of the inner sqr) direction relative to the inner square.
        var fnFlowingAxis = (pos == 0 || pos == 2) ? fnCoordTop : fnCoordLeft;  // flowing axis (Left Side= Flows tiles Vertically) (direction along the inner square, s/b opposite of the fnOutsideAxis)

        //Similarly, the Size Axis functions provide Width & Height values related to which axis we're using.
        var fnSizeOutsideAxis = (pos == 0 || pos == 2) ? fnSizeWidth : fnSizeHeight; // Set the function which will get/set the size of the square along the outside axis (either width or height, depending on which side)
        var fnSizeFlowingAxis = (pos == 0 || pos == 2) ? fnSizeHeight : fnSizeWidth; // Set the function which will get/set the size of the square along the flowing axis (either width or height, depending on which side)

        var FlowingDirection = (pos == 0 || pos == 3) ? -1 : 1; // Left & Bottom flow backward with respect to their Flowing Axis, therefore -1 for them.
        var OutsideDirection = (pos == 0 || pos == 1) ? -1 : 1; // Left & Top sides need to be offset backward to reach the outside edge.

        var sides = ["l", "t", "r", "b"];
        var side = sides[pos];



        //Calculate how far from the center of the InnerSquare these elements should appear on the Outside Axis.
        OutsideAxisOffset = OutsideDirection * (fnSizeOutsideAxis(ContextGestureMenu.CenterSquareSize) + ContextGestureMenu.TileSize.Margin);

        //Get the menu items for this side
        items = ContextGestureMenu.GroupedMenuItems[pos];

        //Determine the margin based on how many we're trying to show.
        var PerItemMargin = ContextGestureMenu.TileSize.Margin;
        if (PerItemMargin * (items.length - 1) + (fnSizeFlowingAxis(ContextGestureMenu.TileSize) * items.length) < fnSizeFlowingAxis(ContextGestureMenu.CenterSquareSize)) {
            var PerItemMargin = fnSizeFlowingAxis(ContextGestureMenu.CenterSquareSize) - (fnSizeFlowingAxis(ContextGestureMenu.TileSize) * Math.max(items.length - 1, 1)) / Math.max(items.length - 2, 1);
        }

        //Calculate the location
        var CurrentFlowingAxisValue = fnFlowingAxis(ContextGestureMenu.Coord) + (FlowingDirection * (items.length > 2) ? items.length - 2 : 0);

        //Loop through each menu item...
        for (var i = 0; i < items.length; i++) {
            // Setup a new drop target.
            var UIDropTarget = $('<div class="tpsGestureTarget"><span></span></div>');

            ContextGestureMenu.Droppables.push(UIDropTarget);

            //console.log('{' + pos + '.' + i + '} OutsideOffset=' + OutsideAxisOffset + '; OutsideAxis=' + fnOutsideAxis(ContextGestureMenu.Coord) + '; FlowingAxisValue=' + CurrentFlowingAxisValue + 'PerItemMargin: ' + PerItemMargin);

            //Set it's coordinates based on where we are now.
            items[i].Coord = {};
            fnOutsideAxis(items[i].Coord, fnOutsideAxis(ContextGestureMenu.Coord) + OutsideAxisOffset); //OutsideAxisOffset is always the same for each item in the side.
            fnFlowingAxis(items[i].Coord, CurrentFlowingAxisValue);

            //Update the current position on the flowing axis...
            CurrentFlowingAxisValue += FlowingDirection * (PerItemMargin + fnSizeFlowingAxis(ContextGestureMenu.TileSize));

            var corners = ["t", "r", "b", "l"];
            var cssclass = side;
            if (i > 0 && i == items.length - 1)
                cssclass += corners[pos];

            //Put the drop target at the center point we defined - Offset by half of TileSize since CSS wants Left/Top instead of center.
            UIDropTarget
                .addClass(cssclass)
                .attr("title", items[i].Name)
                .css("left", items[i].Coord.Left - (ContextGestureMenu.TileSize.Width / 2))
                .css("top", items[i].Coord.Top - (ContextGestureMenu.TileSize.Height / 2))
                .appendTo(ContextGestureMenu.GestureRegionUI)
                .droppable({
                    hoverClass: "GestureHover",
                    drop: function(fnTargetMethod, fnGetDataContext) { return function() { fnTargetMethod(fnGetDataContext()); }; } (items[i].Method, fnGetDataContext), //HACK: Need the caller object reference....(in this case, the tpsDataList instance)
                    over: function(name) { return function() { ContextGestureMenu.GestureHoverLabelUI.text(name); } } (items[i].Name),
                    out: function() { ContextGestureMenu.GestureHoverLabelUI.text(""); },
                    tolerance: "pointer"
                });
                if (items[i].CssClass) {
                    UIDropTarget.addClass(items[i].CssClass)
                }
            //console.log(cssclass);
            //debugger;
        }

    }

};

tpsContextGesture.SliceGesturesIntoFourSidesAndCorners= function(menu) {
    //Odd indexes are the corners & contain just the item (or null),
    //even are sides starting with 0=left, clockwise

    //determine # of corners
    var sumCorners = 4;
    if (menu.MenuItems.length <= 8) sumCorners = menu.MenuItems.length - 4;
    if (sumCorners < 0) sumCorners = 0;

    //determine count per side
    var sumOnlySides = (menu.MenuItems.length - sumCorners);
    var perSide = Math.floor(sumOnlySides / 4);
    var perSideRemain = sumOnlySides % 4;

    var results = [];
    var thisresult;
    var roundup;
    var currentMenuIndex = 0;
    var currentCorner = 0;
    var sumThisSide;
    for (var pos = 0; pos < 4; pos++) {
        thisresult = [];
        sumThisSide = perSide;
        roundup = pos < perSideRemain;
        if (roundup) sumThisSide++;
        thisresult = menu.MenuItems.slice(currentMenuIndex, currentMenuIndex + sumThisSide);
        currentMenuIndex += sumThisSide;

        //odd (corner, single obj or null)
        if (currentCorner < sumCorners) {
            thisresult.push(menu.MenuItems[currentMenuIndex]);
            currentCorner++;
            currentMenuIndex++;
        }

        results.push(thisresult);
    }

    return results;

};
tpsContextGesture.EndGesture= function() {
    //called after item is dropped, regardless if inside a drop target
    if (tpsContextGesture.RunningContextGestureMenu != null)
        tpsContextGesture.RunningContextGestureMenu.GestureRegionUI.remove();
    tpsContextGesture.GestureStarted = false;
};

$(tpsContextGesture.PageInit);


var tpsDataList = function(node, options) {
    this.DataSource = new Array();
    this.List = null;
    this.ListBody = null;
    this.ListSelectors = null;
    this.RepeaterTemplate = null;
    this.CurrentSorter = null;
    this.CurrentSorterDirection = null;
    this.RefreshAlternateLinesTimer = null;

    this.List = node;
    this.ListHead = this.List.children(".tpsListHead");
    this.ListBody = this.List.children(".tpsListBody");
    this.ListSelectors = this.List.children(".tpsListSelectors");
    this.RepeaterTemplate = this.ListBody.find(".tpsItemTemplate").eq(0);

    this.Options = options;
    if (this.Options.SelectionMode == null) this.Options.SelectionMode = tpsDataList.SelectionMode.None;
    if (this.Options.NewItemsAddedTo == null) this.Options.NewItemsAddedTo = tpsDataList.NewItemsPosition.Bottom;

    //Selectors provide "Select All" / "none" features, plus any extra types provided by the calling code.
    //These are only good in Multi-select mode, and will be ignored otherwise.
    if (this.Options.SelectionMode == tpsDataList.SelectionMode.Multi) this.BindAutoSelectors();


    //If we've been here before, a cookie may specify the last sorter used, which we'll happily default to.
    var StoredPreviousSorterName = $.cookie(this.GetPersonalizedCookieName("CurrentSorterName"));
    var StoredPreviousSorterDirection = $.cookie(this.GetPersonalizedCookieName("CurrentSorterDirection"));
    var iThis = this;

    if (this.Options.Sorters != null) {
        for (var SorterNum = 0; SorterNum < this.Options.Sorters.length; SorterNum++) {
            var iSorter = this.Options.Sorters[SorterNum];
            //Link the sorting options with the label that'll be clicked.
            iSorter.SortLabel.data("sorter", iSorter);
            if (iSorter.Sorter == null && iSorter.SortColumn != null) {
                var fnSortColumn = iSorter.SortColumn;
                iSorter.Sorter = function(dataitem1, dataitem2) {
                    var datacolumn1 = fnSortColumn(dataitem1);
                    var datacolumn2 = fnSortColumn(dataitem2);
                    if (datacolumn1 == datacolumn2) return 0;
                    if (datacolumn1 > datacolumn2) return 1;
                    return -1;
                }

            }
            iSorter.SortLabel.click(function() {
                var ClickedElement = $(this);
                var SortedAscendingAlready = (iThis.CurrentSorterDirection == tpsDataList.SorterDirection.Ascending && iThis.CurrentSorter.SorterName == ClickedElement.data("sorter").SorterName);
                iThis.SortList(ClickedElement.data("sorter"), SortedAscendingAlready ? tpsDataList.SorterDirection.Descending : tpsDataList.SorterDirection.Ascending);
            });
            //If there isn't a previous sorter & this one is the default *OR* this sorter IS the previously used one, set the current sorter.
            if ((StoredPreviousSorterName == null && iSorter.IsDefault) || (StoredPreviousSorterName == iSorter.SorterName)) {
                this.CurrentSorter = iSorter;
                this.CurrentSorterDirection = (StoredPreviousSorterName != null && StoredPreviousSorterDirection != null) ? StoredPreviousSorterDirection : tpsDataList.SorterDirection.Ascending;
            }
        }
        if (this.CurrentSorter != null) this.CurrentSorter.SortLabel.addClass(this.CurrentSorterDirection == tpsDataList.SorterDirection.Descending ? this.Options.SortedDescendingCssClass : this.Options.SortedAscendingCssClass)
    }
    $.cookie(this.GetPersonalizedCookieName("selected"), "");


};

tpsDataList.prototype = {

    GetPersonalizedCookieName: function(DataKey) {
        return [this.Options.PersonalizationName, "_", DataKey].join('');
    },

    SortList: function(newSorter, SorterDirection) {

        if (newSorter != null)
            this.CurrentSorter = newSorter;
        if (SorterDirection != null)
            this.CurrentSorterDirection = SorterDirection;

        //Drop a cookie indicating the last sorter used.
        $.cookie(this.GetPersonalizedCookieName("CurrentSorterName"), newSorter.SorterName, { expires: 7 });
        $.cookie(this.GetPersonalizedCookieName("CurrentSorterDirection"), SorterDirection, { expires: 7 });

        //Sort it.
        this.DataSource.sort(newSorter.Sorter);
        if (SorterDirection == tpsDataList.SorterDirection.Descending) this.DataSource.reverse();

        //Comb it.
        var dataitem;
        for (var i = 0; i < this.DataSource.length; i++) {
            dataitem = this.DataSource[i];

            //fix alternating rows
            dataitem.UI.toggleClass("alt", (i % 2 == 0));

            //element were inserting before:
            var targetElement = this.ListBody.children().eq(i);

            // skip if this operation would just put it where it already is. (not the funnest bug in the world to find!)
            if (this.Options.DataSourceGetKey(targetElement.data("dataitem")) == this.Options.DataSourceGetKey(dataitem)) continue;

            dataitem.UI.insertBefore(targetElement);
        }

        //Update sort labels.
        for (var i = 0; i < this.Options.Sorters.length; i++) {
            this.Options.Sorters[i].SortLabel.removeClass(this.Options.SortedAscendingCssClass).removeClass(this.Options.SortedDescendingCssClass);
        };
        newSorter.SortLabel.addClass(SorterDirection == tpsDataList.SorterDirection.Descending ? this.Options.SortedDescendingCssClass : this.Options.SortedAscendingCssClass)

    },

    BindAutoSelectors: function() {
        var o = this;
        this.ListSelectors.append($("<a href=\"javascript:void(0);\">all</a>").click(function() { o.SelectSomeRows(function() { return true; }, false) }));
        this.ListSelectors.append(", ").append($("<a href=\"javascript:void(0);\">none</a>").click(function() { o.SelectSomeRows(function() { return true; }, false, true) }));
        if (this.Options.Selectors == null) return;
        for (var i = 0; i < this.Options.Selectors.length; i++) {
            var x = i;
            if (this.Options.Selectors[i].Name != null) {
                this.ListSelectors.append(", ").append($("<a href=\"javascript:void(0);\">" + this.Options.Selectors[i].Name + "</a>").click(function() {
                    o.SelectSomeRows(o.Options.Selectors[x].Selector, false);
                }));
            }
            if (this.Options.Selectors[i].ReverseName != null) {
                this.ListSelectors.append(", ").append($("<a href=\"javascript:void(0);\">" + this.Options.Selectors[i].ReverseName + "</a>").click(function() {
                    o.SelectSomeRows(o.Options.Selectors[x].Selector, true);
                }));
            }

        }
    },

    SelectSomeRows: function(fnWhichRows, reversematch, uncheck) {
        if (uncheck == null) uncheck = false;
        for (var i = 0; i < this.DataSource.length; i++) {
            var chked = this.DataSource[i].UI.hasClass("selected");
            var matched = fnWhichRows(this.DataSource[i]);
            if (reversematch) matched = !matched;
            if (matched && chked == uncheck) this.RowSelectionToggle(this.DataSource[i].UI);
        }
    },

    GetSelectedDataItems: function() {
        return $.map(this.ListBody.find(".selected"), function(a) { return $(a).data("dataitem"); });
    },
    GetSelectedCheckBoxes: function() {
        return this.ListBody.find(".selected").find(this.Options.SelectionCheckboxJquery);
    },

    GetRow: function(dataitemkey) {
        for (var i = 0; i < this.DataSource.length; i++) {
            if (this.Options.DataSourceGetKey(this.DataSource[i]) == dataitemkey) {
                return this.DataSource[i];
            }
        }
        return null;
    },

    SetRow: function(dataitem, olddataitem) {
        if (olddataitem == null)
            olddataitem = this.GetRow(this.Options.DataSourceGetKey(dataitem));
        if (olddataitem == null) this.InsertRow(dataitem);

    },

    ChooseWhichMenu: function() {
        var count = this.GetSelectedDataItems().length;
        for (var i = 0; i < this.Options.GestureContexts.Menus.length; i++) {
            context = this.Options.GestureContexts.Menus[i];
            if ((count == 0 && context.Type == tpsDataList.SelectionMode.None)
             || (count == 1 && context.Type == tpsDataList.SelectionMode.Single)
             || (count > 1 && context.Type == tpsDataList.SelectionMode.Multi)) return context;
        }
    },

    UpdateRow: function(newdataitem) {
        olddataitem = this.GetRow(this.Options.DataSourceGetKey(newdataitem));
        $.extend(true, olddataitem, newdataitem);
        this.Options.RowBinder(olddataitem);
    },

    InsertRow: function(dataitem, quickinsert) {
        //Clone the item template, add the dataitem reference to the cloned control.
        dataitem.UI = this.RepeaterTemplate.children().clone();
        dataitem.UI.data("dataitem", dataitem).css({ opacity: 0 });

        //If selectionmode is enabled...
        if (this.Options.SelectionMode != tpsDataList.SelectionMode.None) {
            var o = this;
            //If a checkbox is defined to handle the selection:
            if (this.Options.SelectionCheckboxJquery != null) {
                //Prepare the checkbox
                var chkSelect = dataitem.UI.find(this.Options.SelectionCheckboxJquery).data("dataitem", dataitem).click(function(a) { a.stopPropagation(); o.RowSelectionToggle(this); });
                chkSelect.attr("checked", null);
            }

            //Row level selection: Enabled automatically if no checkbox is defined.
            if (this.Options.SelectionCheckboxJquery == null || this.Options.SelectByRowClick) {
                dataitem.UI.click(function() { o.RowSelectionToggle(this); });
            }

            var SelectedItemsStr = $.cookie(this.GetPersonalizedCookieName("selected"));
            var SelectedItems = (SelectedItemsStr == null ? [] : SelectedItemsStr.split(','));
            //if (SelectedItems.indexOf(this.Options.DataSourceGetKey(dataitem)) != -1) this.RowSelectionToggle(dataitem.UI);
        }

        this.Options.RowBinder(dataitem);
        if (this.Options.GestureContexts != null && this.Options.GestureContexts.Menus.length > 0) {
            var o = this;
            tpsContextGesture.AttachGesture(dataitem, function() { return o.ChooseWhichMenu(); }, function() { return o.GetSelectedDataItems(); });
        }
        var sortMode = quickinsert != true ? this.Options.NewItemsAddedTo : tpsDataList.NewItemsPosition.QuickInsert;

        switch (sortMode) {
            case tpsDataList.NewItemsPosition.QuickInsert:
                this.DataSource.push(dataitem); // adds to end of array
                dataitem.UI.insertBefore(this.RepeaterTemplate);
                break;
            case tpsDataList.NewItemsPosition.Bottom:
                this.DataSource.push(dataitem); // adds to end of array
                if (this.DataSource.length % 2 == 0) dataitem.UI.addClass("alt");
                dataitem.UI.insertBefore(this.RepeaterTemplate);
                break;
            case tpsDataList.NewItemsPosition.Top:
                this.DataSource.unshift(dataitem); //adds to beginning of array
                dataitem.UI.insertBefore(this.ListBody.children().eq(0));
                this.CorrectAlternateRows();
                break;
            case tpsDataList.NewItemsPosition.SortOrder:
                if (this.CurrentSorter == null) {
                    //No current sorter? default to Bottom.
                    this.DataSource.push(dataitem); // adds to end of array
                    if (this.DataSource.length % 2 == 0) dataitem.UI.addClass("alt");
                    dataitem.UI.insertBefore(this.RepeaterTemplate);
                    //debugger;
                    break;
                }

                //Use the current sorter to find the position to insert the row:
                var positionToInsertBefore = null;
                for (var i = 0; i < this.DataSource.length; i++) {
                    if (this.CurrentSorterDirection == tpsDataList.SorterDirection.Descending) {
                        if (this.CurrentSorter.Sorter(this.DataSource[i], dataitem) < 1) {
                            positionToInsertBefore = i;
                            //debugger;
                            break;
                        }
                    } else {
                        if (this.CurrentSorter.Sorter(this.DataSource[i], dataitem) > -1) {
                            positionToInsertBefore = i;
                            //debugger;
                            break;
                        }
                    }
                }
                //If we didn't find a position it means this is likely the last element, so we'll default to the bottom.
                if (positionToInsertBefore == null) {
                    this.DataSource.push(dataitem); // adds to end of array
                    if (this.DataSource.length % 2 == 0) dataitem.UI.addClass("alt");
                    dataitem.UI.insertBefore(this.RepeaterTemplate);
                    //debugger;
                    break;
                }
                //Otherwise, we'll insert it where it should be by the sort order.
                this.DataSource = this.DataSource.slice(0, positionToInsertBefore).concat(dataitem, this.DataSource.slice(positionToInsertBefore));
                dataitem.UI.insertBefore(this.ListBody.children().eq(positionToInsertBefore));

                this.CorrectAlternateRows(positionToInsertBefore); //Only correct rows from here down, nothing changed above so save some clockcycles.
                //debugger;
                //$.cookie(this.GetPersonalizedCookieName("selected")).split(',').indexOf(this.Options.DataSourceGetKey(dataitem));
                break;

        }
        dataitem.UI.fadeInForIE(1500);


    },
    InsertBatch: function(newitems) {
        for (var i = 0; i < newitems.length; i++) {
            this.InsertRow(newitems[i], true);
        }
        this.SortList(); //sort with previous sorter
    },

    CorrectAlternateRows: function(startRow) {
        for (var i = startRow ? startRow : 0; i < this.DataSource.length; i++) {
            if ((i % 2 == 1) != this.DataSource[i].UI.hasClass("alt")) this.DataSource[i].UI.toggleClass("alt")
        }
    },


    RowSelectionToggle: function(clickedElement) {
        //TODO: If SelectionMode==Single, then click event should unselect all other checkboxen first.
        clickedElement = $(clickedElement);
        if (clickedElement.data("dataitem") != null) {
            var dataitem = clickedElement.data("dataitem");
            dataitem.UI.toggleClass("selected");
            var chk = dataitem.UI.find(this.Options.SelectionCheckboxJquery);
            if (this.Options.SelectionCheckboxJquery) chk.attr("checked", dataitem.UI.hasClass("selected") ? true : null);

            //update the selected items list.
            //var SelectedItemsStr = $.cookie(this.GetPersonalizedCookieName("selected"));
            //            var SelectedItems = (SelectedItemsStr || SelectedItemsStr == "" ? [] : SelectedItemsStr.split(','))
            //            if (dataitem.UI.hasClass("selected")) {
            //                if (!SelectedItems.indexOf(this.Options.DataSourceGetKey(dataitem)) == -1) SelectedItems.push(this.Options.DataSourceGetKey(dataitem));
            //            } else {
            //                var pos = SelectedItems.indexOf(this.Options.DataSourceGetKey(dataitem))//find item
            //                if (!pos == -1) SelectedItems.splice(pos, 1); //remove
            //            }
            //            $.cookie(this.GetPersonalizedCookieName("selected"), SelectedItems.join(), { expires: 7 });
            //            debugger;
        }
    },

    RemoveRow: function(key) {
        var dataitem;
        for (var i = 0; i < this.DataSource.length; i++) {
            if (this.Options.DataSourceGetKey(this.DataSource[i]) = dataitemkey) {
                dataitem = this.DataSource[i];
                pos = i;
            }
        }
        dataitem.UI.remove();
        this.DataSource = this.DataSource.slice(0, pos - 1).concat(dataitem, this.DataSource.slice(pos));

    },
    RemoveAll: function() {
        var dataitem;
        //debugger;
        for (var i = 0; i < this.DataSource.length; i++) {
            this.DataSource[i].UI.remove();
            //console.log(['removing ', this.DataSource[i].ThreadSubject ? this.DataSource[i].ThreadSubject : this.DataSource[i].DisplayName].join(''));
        }

        this.DataSource = [];
    }
}

//enum:
tpsDataList.SelectionMode = {
    None: 0,
    Single: 1,
    Multi: 2
};
tpsDataList.SorterDirection = {
    Ascending: 0,
    Descending: 1
};
tpsDataList.NewItemsPosition = {
    Top: 0,
    Bottom: 1,
    SortOrder: 2,
    QuickInsert: 4
};        
    