Friday, April 24, 2015

DRY principles - Finding entities via Attributes.

With the new contracting gig I'm in right now...

var types = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
    from type in assembly.GetTypes()
    where Attribute.IsDefined(type, typeof(FCBids.Domain.AuditEntityAttribute), false) && !type.Namespace.Equals("System.Data.Entity.DynamicProxies")
    select type).ToList();

foreach (var type in types)
{
    /// do what you need to here.
    /// ....
}

ChecklistBox MVC 3 stuff




/// <reference path="./jquery-1.10.2-vsdoc.js" />
/// <reference path="./jquery-ui-1.10.3.js" />
/// <reference path="./jquery.jqGrid.src.js" />

window.jqGridSettings = {};


function bindConfiguration(key, options) {

    var p = window.jqGridSettings[options.idPrefix];

    if (p === null || p === undefined) {
        // do init work here.
        p = $.extend(true, {
            cols: [
            ],
            gridTitle: "",
            idPrefix: "grid",
            containerId: "gridContainer",
            exportUrl: "/Export/Excel",
            oDataEndPoint: "/odata/States",
            useVirtualScrolling: 1,
            gridHeight: 500,
            windowResizeDelay: -1, // windowResizeDelay is a marker to be able to clear the setTimeout function in case the window is still being resized.
            quoteFilters: [],

            container: null,
            form: null,
            filenameField: null,
            oDataField: null,
            filterField: null,
            orderByField: null,

            list: null,
            table: null,
            pager: null,

            multiSelect: false,
            onRowSelection: undefined,
            onPageChange: undefined,
            onGridReload: undefined,
            onAllSelection: undefined,
            onSortColumnClick: undefined
        }, options || {});

        p.quoteFilters = getQuoteFlags(p.cols);

        // this is the container on the html page where the grid will be rendered.
        p.container = $("#" + p.containerId);


        // this form (and the four elements below) are what allow us to do the server-side rendering of the excel file.
        p.form = document.createElement("form");
        p.form.id = p.idPrefix + "-form";
        p.form.method = "POST";
        p.form.action = p.exportUrl;
        document.body.appendChild(p.form);


        // filename
        p.filenameField = document.createElement("input");
        p.filenameField.type = "hidden";
        p.filenameField.id = p.idPrefix + "-filename";
        p.filenameField.name = "Filename";
        p.filenameField.value = "";
        p.form.appendChild(p.filenameField);

        // ODataUrl
        p.oDataField = document.createElement("input");
        p.oDataField.type = "hidden";
        p.oDataField.id = p.idPrefix + "-ODataUrl";
        p.oDataField.name = "ODataUrl";
        p.oDataField.value = p.oDataEndPoint;
        p.form.appendChild(p.oDataField);

        // Filter
        p.filterField = document.createElement("input");
        p.filterField.type = "hidden";
        p.filterField.id = p.idPrefix + "-Filter";
        p.filterField.name = "Filter";
        p.filterField.value = "";
        p.form.appendChild(p.filterField);

        // OrderBy
        p.orderByField = document.createElement("input");
        p.orderByField.type = "hidden";
        p.orderByField.id = p.idPrefix + "-OrderBy";
        p.orderByField.name = "OrderBy";
        p.orderByField.value = "";
        p.form.appendChild(p.orderByField);

        // The grid definition.  It takes 3 components:
        //        a div for the "list"
        p.list = document.createElement("div");
        p.list.id = p.idPrefix + "-list";
        p.container.append(p.list);

        p.table = document.createElement("table");
        p.table.id = p.idPrefix + "-table";
        p.container.append(p.table);

        p.pager = document.createElement("div");
        p.pager.id = p.idPrefix + "-pager";
        p.container.append(p.pager);

        window.jqGridSettings[p.idPrefix] = p;
    }

    return p;
}


function generateGrid(options) {

    var oKey = options.idPrefix || "grid";

    // our default options... the expectation is that the end developer sets all of these in the *.html code.
    var p = bindConfiguration(oKey, options);

    // building the grid here.
    var grid = $(p.table).jqGrid({
        url: p.oDataEndPoint,
        datatype: "json",
        height: p.gridHeight,
        autowidth: true, // allows the grid to expand to the max. width given its parent container.
        pager: "#" + p.pager.id,
        viewrecords: true,
        caption: p.gridTitle,
        gridview: true,
        // allows the user to sort by multiple columns.  the catch here is that the column ordering in the grid sets the prescedence for the
        // hierarchy of the sort.. so if you have id first, then date, then name, it's going to sort by id, date, and name.  to sort by date,
        // then name, the user will need to drag the "name" field to the first position, then the "date" field to the second position, then
        // click the columns to get the ordering (asc/desc) that they wish.
        multiSort: true,
        sortable: true, // allows the user to sort the ordering of the columns.
        colNames: getHeaders(p.cols),
        colModel: getColumDefinitions(p.cols),
        rowNum: 50,
        rowList: [10, 25, 50, 75, 100],
        multiselect: p.multiSelect,
        scroll: p.useVirtualScrolling, // turns on/off scrolling vs. paging.
        onSelectRow: p.onRowSelection, // Added by Sarthak Joshi : Allows event to be triggered after a particular row is selected [Note : onSelectRow is original jQGrid event]
        onPaging: p.onPageChange,
        gridComplete: p.onGridReload, // Added by Sarthak Joshi : Allows event to be triggered after grid is reloaded when paging is enabled [Note : loadComplete is original jQGrid event]
        onSelectAll: p.onAllSelection, // Added by Sarthak Joshi : Allows event to be triggered after select all checkbox is checked when MultiSelect is true [Note : onSelectAll is original jQGrid event]
        onSortCol: p.onSortColumnClick, // Added by Sarthak Joshi : Allows event to be triggered when sortable column header is clicked [Note : onSortCol is original jQGrid event]
        ajaxGridOptions: {
            contentType: "application/json charset=utf-8"
        },
        serializeGridData: function (postData) { return setupWebServiceData(postData, p); },
        beforeProcessing: function (data, textStatus, jqXHR) {
            // builds out the total page count for the data returned.
            var rows = parseInt($(this).jqGrid("getGridParam", "rowNum"), 10);
            data.total = Math.ceil(data["Count"] / rows); // change to odata.count if using Odata API
        },
        jsonReader: {
            root: "Items", // the root node of the Json, change to value for OData api.
            repeatitems: false, // tells the grid to find the data by property name.
            records: "Count" // the path to get the record count., change to OData.count for OData api.
        },
        loadError: function (jqXHR, textStatus, errorThrown) {
            alert('HTTP status code: ' + jqXHR.status, +'\n' +
                'textStatus: ' + textStatus + '\n' +
                'errorThrown: ' + errorThrown);
        }
    });

    buildNavigation(grid);





    var parentContainer = $("#" + p.containerId);
    $("#sidenav-flyout-btn").on("click", function () {
        setTimeout(function () {
            p.windowResizeDelay = beginResize(grid, parentContainer, p.windowResizeDelay);
        }, 200);
    });

    $(window).resize(function (event, ui) {
        p.windowResizeDelay = beginResize(grid, parentContainer, p.windowResizeDelay);
    });

    return grid;
};

function setupWebServiceData(postData, p) {
    // basic posting parameters to the OData service.
    var params = {
        $top: postData.rows,
        $skip: (parseInt(postData.page, 10) - 1) * postData.rows,
        $inlinecount: "allpages"
    };

    // if we have an order-by clause to use, then we build it.
    if (postData.sidx) {

        // two columns have the following data:
        // postData.sidx = "{ColumnName} {order}, {ColumnName} "
        // postData.sord = "{order}"
        // we need to split sidx by the ", " and see if there are multiple columns.  If there are, we need to go through
        // each column and get its parts, then parse that for the appropriate columns to build for the sort.

        var splitColumnOrdering = (postData.sidx + postData.sord).split(", ");

        if (splitColumnOrdering.length == 1) {
            params.$orderby = buildColumnSort(splitColumnOrdering[0], quoteFilters);
        } else {
            var colOrdering = $.map(splitColumnOrdering, function (element, idx) {
                return buildColumnSort(element, quoteFilters);
            });
            params.$orderby = colOrdering.join(", ");
        }
    }

    // if we want to support "in" clauses, we need to follow this stackoverflow article:
    //http://stackoverflow.com/questions/7745231/odata-where-id-in-list-query/7745321#7745321
    // this is for basic searching, with a single term.
    if (postData.searchField) {
        var quoteFilter = findQuoteFilter(postData.searchField, quoteFilters);
        params.$filter = ODataExpression(postData.searchOper, postData.searchField, postData.searchString, quoteFilters);
    }

    // complex searching, with a groupOp.  This is for if we enable the form for multiple selection criteria.
    if (postData.filters) {
        var filterGroup = $.parseJSON(postData.filters);
        params.$filter = parseFilterGroup(filterGroup, p.quoteFilters);
    }

    // sets the form elements with the filter/group parameters, so that the user can
    // export the data to excel if they so choose.
    $("#" + p.idPrefix + "-Filter").val(params.$filter);
    $("#" + p.idPrefix + "-OrderBy").val(params.$orderby);

    return params;
}

function buildNavigation(grid) {


    var key = grid.context.id.split("-")[0];

    var p = bindConfiguration(key, { idPrefix: key });

    grid.navGrid("#" + p.pager.id, { search: true, edit: false, add: false, del: false },
        {}, // default settings for edit
        {}, // default settings for add
        {}, // delete
        {closeOnEscape: true, multipleSearch: true, closeAfterSearch: true, multipleGroup: true} // search options.
    );
    // adds a little space between buttons
    grid.navSeparatorAdd("#" + p.pager.id, { sepclass: "", sepcontent: " " })
    // creates the "Save" button.
    grid.jqGrid("navButtonAdd", "#" + p.pager.id, {
        caption: "Save Filter",
        buttonicon: "none",
        onClickButton: function () { showSaveFilterUI(p); },
        position: "last",
        title: "Save Filter",
        cursor: "pointer"
    });
    // adds a little space between buttons
    grid.navSeparatorAdd("#" + p.pager.id, { sepclass: "", sepcontent: " " });
    // creates the "Save" button.
    grid.jqGrid("navButtonAdd", "#" + p.pager.id, {
        caption: "Load Filter",
        buttonicon: "none",
        onClickButton: function () { showLoadFilterUI(p, p.pager); },
        position: "last",
        title: "Load Filter",
        cursor: "pointer"
    });
    // adds a little space between buttons
    grid.navSeparatorAdd("#" + p.pager.id, { sepclass: "", sepcontent: " " });
    // Creates the "Excel" button.
    grid.jqGrid("navButtonAdd", "#" + p.pager.id, {
        caption: "Excel",
        buttonicon: "none",
        onClickButton: function () { showExportExcelUI(p); },
        position: "last",
        title: "Export to Excel",
        cursor: "pointer"
    });
};

// sets up resizing the grid in the event that the user shows/hides navigation, or
// resizes the window.
function beginResize(grid, container, delay) {
    if (delay !== -1) {
        clearTimeout(delay);
        delay = -1;
    }

    delay = setTimeout(function () {
        var newWidth = container.width();
        grid.setGridWidth(newWidth, true);
        delay = -1;
    }, 100);

    return delay;
}


// tools to load the settings
function showLoadFilterUI(p, pager) {

    $.get("/GridFilters/Load/?prefix=" + p.idPrefix)
        .then(function (htmlPartial) {

            var loadFilterDialog = $("<div>" + htmlPartial + "</div>").dialog({
                title: "Load Saved Filter...",
                height: 150,
                width: 300,
                modal: true,
                buttons: [{
                    text: "Ok",
                    click: function () {


                        var grid = $("#" + p.idPrefix + "-table");
                        var dd = $("#gridFilterSelection");

                        $.get("/api/GridFilters/" + dd.val())
                        .then(function (data) {

                            loadFilterDialog.dialog("close"); // should get rid of dailog here.

                            //$("#" + p.table.id).jqGrid("getGridParam", "postData"); gives the search/sort params.
                            //$("#" + p.table.id).jqGrid("getGridParam", "colModel"); do a grep/each and take the index for saving.
                            //$("#" + p.table.id).jqGrid("getGridParam", "colNames"); titles of each column.
                            //$("#" + p.table.id).jqGrid("remapColumns", newOrder, true, true); reorders the columns (and headers)

                            var options = $.parseJSON(data);

                            // reorder the columns.
                            var gridCols = grid.jqGrid("getGridParam", "colModel");



                            var newOrder = $.map(options.colOrder, function (arg, idx) {

                                var colObj = $.grep(gridCols, function (col, colIdx) {
                                    return col.index === arg;
                                })[0];

                                var columnIndex = gridCols.indexOf(colObj);
                                return columnIndex;
                            });

                            // set the post data.
                            var gridPostData = grid.jqGrid("getGridParam", "postData");
                            gridPostData.filters = options.postData.filters;
                            gridPostData.rows = options.postData.rows;
                            gridPostData.page = options.postData.page;
                            gridPostData.sidx = options.postData.sidx;
                            gridPostData.sord = options.postData.sord;

                            grid.jqGrid("remapColumns", newOrder, true, false);

                            grid.trigger("reloadGrid");
                            //grid.jqGrid("remapColumns", newOrder, true, true);
                        }, function (data) {
                            alert("An error occurred?");
                        });

                    }
                }, {
                    text: "Cancel",
                    click: function () {
                        $(this).dialog("close");
                    }
                }],
                close: function (event, ui) {
                    $(this).dialog("destroy");
                    $(this).remove();
                }
            });
        });
};

function showExportExcelUI(p) {

    // the dialog to prompt the user for the respective file name.
    $("<div></div>").dialog({
        title: "Save As...",
        height: 150,
        width: 300,
        modal: true,
        buttons: [{
            text: "Ok",
            click: function () {

                // copy the value from the dialog's text box to the form to be sumbitted's field to hold the file name.
                var formFileField = $("#" + p.idPrefix + "-filename");
                var dialogValue = $("#" + p.idPrefix + "-dlgFilename");
                formFileField.val(dialogValue.val());

                if (formFileField.val()) {
                    p.form.submit();
                    $(this).dialog("close");
                } else {
                    alert("File name was not provided!");
                }
            }
        }, {
            text: "Cancel",
            click: function () {
                // closes the file.
                $(this).dialog("close");
            }
        }],
        close: function (event, ui) {
            // desconstructs the Dailog and removes the html elements that are added to the page.
            $(this).dialog("destroy");
            $(this).remove();
        }
    }).html("<label for=\"" + p.idPrefix + "-dlgFilename\">Filename:</label>" +
                "<input type=\"text\" id=\"" + p.idPrefix + "-dlgFilename\" style=\"float: right; width: 175px;\" />");
}
// builds the grid search/filter export settings ui.
function showSaveFilterUI(p) {

    $.get("/GridFilters/Save?prefix=" + p.idPrefix).then(function (data) {
        var dialog = $("<div id='settingsExportDialog'>" + data + "</div>").dialog({
            title: "Export Grid Settings...",
            modal: true,
            width: 500,
            height: 300,
            close: function () {
                $(this).dialog("destroy");
                $(this).remove();
            },
            buttons: [
                {
                    text: "Save",
                    click: function (event, ui) {
                        var form = $($("#gridFilterSaveForm")[0]);
                        var settings = $("#gridFilterSaveForm input[name=Settings]");

                        var colIds = $.map($("#" + p.table.id).jqGrid("getGridParam", "colModel"), function (element, index) {
                            return element.index;
                        });

                        var options = {
                            postData: $("#" + p.table.id).jqGrid("getGridParam", "postData"),
                            colOrder: colIds
                        };

                        settings.val(JSON.stringify(options));

                        $.post(form.context.action, form.serialize(), function (data) {
                            dialog.html(data);
                        });
                    }
                },
                {
                    text: "Close",
                    click: function (event, ui) {
                        $(this).dialog("close");
                    }
                }
            ]
        });
    });
}

// builds the column headers array from the Json we pass to the
// main grid builder class.
function getHeaders(cols) {
    return $.map(cols, function (element, idx) {
        return element.headerTitle;
    });
}

// builds the jqGrid column definition from the data we pass.  We need
// to alter for dates that I can think of, and need to discuss other data
// types to decide what we'd want to do for each (specifically the search options.)
function getColumDefinitions(cols) {
    return $.map(cols, function (element, idx) {

        var ele = $.extend(true, {
            allowSearch: true
        }, element || {});

        var col = {
            name: ele.fieldName,
            index: ele.fieldName,
            search: ele.allowSearch
        };

        if (ele.formatter) {
            col = $.extend(true, { formatter: ele.formatter }, col || {});
        }
        if (ele.width) {
            col = $.extend(true, { width: ele.width }, col || {});
        }

        switch (ele.dataType) {
            case "hidden":
                col = $.extend(true, {
                    sortable: false,
                    hidden: true,
                    searchoptions: { sopt: ['eq', 'ne', 'lt', 'le', 'gt', 'ge', 'bw', 'bn', 'ew', 'en', 'cn', 'nc', 'nu', 'nn'], searchhidden: true }
                }, col || {});
                break;
            case "hidden-number":
                col = $.extend(true, {
                    sortable: false,
                    hidden: true,
                    searchoptions: { sopt: ['eq', 'ne', 'lt', 'le', 'gt', 'ge'], searchhidden: true }
                }, col || {});
                break;
            case "number":
                col = $.extend(true, {
                    sortable: true,
                    search: ele.allowSearch,
                    searchoptions: { sopt: ['eq', 'ne', 'lt', 'le', 'gt', 'ge'] },
                    cellattr: function (rowId, tv, rawObject, cm, rdata) {
                        return 'style="vertical-align: middle;"';
                    }
                }, col || {});
                break;
            case "link":
                col = $.extend(true, {
                    sortable: false,
                    formatter: ele.linkFormatter,
                    cellattr: function (rowId, tv, rawObject, cm, rdata) {
                        return 'style="vertical-align: middle;"';
                    }
                }, col || {});
                col.search = false;
                break;
            case "boolean":
                col = $.extend(true, {
                    sortable: true,
                    cellattr: function (rowId, tv, rawObject, cm, rdata) {
                        return 'style="vertical-align: middle;"';
                    },
                    stype: "select",
                    formatter: function (cellvalue, options, rowObject) {
                        return cellvalue ? "Enabled" : "Disabled";
                    },
                    searchoptions: { sopt: ['eq', 'ne'], value: "bool-true:Enabled;bool-false:Disabled" }
                }, col || {});
                break;
            case "list-number":
            case "list":
                var searchops = { sopt: ["eq"] };

                if (ele.searchItems !== undefined) {
                    searchops = $.extend(true, { value: ele.searchItems }, searchops || {});
                };

                col = $.extend(true, {
                    sortable: false,
                    cellattr: function (rowId, tv, rawObject, cm, rdata) {
                        return 'style="white-space: normal; vertical-align: middle;"';
                    },
                    stype: "select",
                    searchoptions: searchops
                }, col || {});
                break;
            case "lookup":
                var searchOps = { sopt: ["eq"] };

                if (ele.searchItems !== undefined) {
                    searchOps = $.extend(true, { value: ele.searchItems }, searchOps || {});
                };

                col = $.extend(true, {
                    sortable: true,
                    cellattr: function (rowId, tv, rawObject, cm, rdata) {
                        return 'style="white-space: normal; vertical-align: middle;"';
                    },
                    stype: "select",
                    searchoptions: searchOps
                }, col || {});
                break;
            default:
                col = $.extend(true, {
                    sortable: true,
                    searchoptions: { sopt: ['eq', 'ne', 'lt', 'le', 'gt', 'ge', 'bw', 'bn', 'ew', 'en', 'cn', 'nc', 'nu', 'nn'] },
                    searchrules: {},
                    cellattr: function (rowId, tv, rawObject, cm, rdata) {
                        return 'style="white-space: normal; vertical-align: middle;"';
                    }
                }, col || {});
                break;
        }

        return col;
    });
}

// when dealing with the advanced query dialog, this parses the encapsulating Json object
// which we will then build the advanced OData expression from.
function parseFilterGroup(filterGroup, filters) {

    var filterText = "";

    if (filterGroup.groups) {
        if (filterGroup.groups.length) {
            for (var i = 0; i < filterGroup.groups.length; i++) {
                filterText += "(" + parseFilterGroup(filterGroup.groups[i]) + ")";

                if (i < filterGroup.groups.length - 1) {
                    filterText += " " + filterGroup.groupOp.toLowerCase() + " ";
                }
            }

            if (filterGroup.rules && filterGroup.rules.length) {
                filterText += " " + filterGroup.groupOp.toLowerCase() + " ";
            }
        }
    }

    if (filterGroup.rules.length) {

        // fields that are considered as a list should get built as a single
        // odata expression.
        var listFields = $.grep(filterGroup.rules, function (rule, idx) {
            var foundFilter = findQuoteFilter(rule.field, filters);
            if (foundFilter.isList !== undefined) {
                return foundFilter.isList;
            }
            return false;
        });

        var allListNames = $.map(listFields, function (rule, idx) {
            return rule.field;
        });

        var distinctFieldNames = $.unique(allListNames);

        $.each(distinctFieldNames, function (idx, fieldName) {

            var fieldValues = $.grep(listFields, function (fieldValue, idx) {
                return fieldValue.field === fieldName;
            });

            var fieldDataValues = $.map(fieldValues, function (rule, idx) {
                return rule.data;
            });

            var foundFilter = findQuoteFilter(fieldName, filters);

            filterText += ODataListExpression(filterGroup.groupOp.toLowerCase(), fieldName, fieldDataValues, foundFilter);
        });

        var elementFields = $.grep(filterGroup.rules, function (rule, idx) {
            var foundFilter = findQuoteFilter(rule.field, filters);
            if (foundFilter.isList !== undefined) {
                return !foundFilter.isList;
            }
            return true;
        });

        for (var i = 0; i < elementFields.length; i++) {
            var rule = filterGroup.rules[i];

            var filter = findQuoteFilter(rule.field, filters);
            filterText += ODataExpression(rule.op, rule.field, rule.data, filter);

            if (i < filterGroup.rules.length - 1) {
                filterText += " " + filterGroup.groupOp.toLowerCase() + " ";
            }
        }
    }

    return filterText;
}

// comparer should be a value of 'and' or 'or'.
// quoteFlags = { col: element.fieldName, quoteValue: false, isList: true, baseQuery: element.baseQuery, baseQueryParam: element.baseQueryParam };
function ODataListExpression(comparer, field, dataItems, filter) {

    var quoteData = $.grep(filter, function (element, idx) {
        return element.col === field;
    });

    var params = $.map(dataItems, function (element, idx) {
        var param = quoteDataVal(element, filter);
        return filter.baseQueryParam.replace("{0}", param);
    });

    var paramstring = params.join(" " + comparer + " ");
    return filter.baseQuery.replace("{0}", paramstring);
}

// builds out OData expressions... the condition.
function ODataExpression(op, field, data, filter) {

    var dataVal = quoteDataVal(data, filter);

    // lists are a unique concern.  with lists, we have to provide an xml/json path
    // for OData to query against.  The best way to handle this is to define the base
    // path query within each Index() page, and use that here with some sort of string.replace
    // or string.format javascript function.

    switch (op) {
        case "cn":
            return "substringof(" + dataVal + ", " + field + ") eq true";
        case "nc": // does not contain.
            return "substringof(" + dataVal + ", " + field + ") eq false";
        case "bw":
            return "startswith(" + field + ", " + dataVal + ") eq true";
        case "bn": // does not begin with
            return "startswith(" + field + ", " + dataVal + ") eq false";
        case "ew":
            return "endswith(" + field + ", " + dataVal + ") eq true";
        case "en": // does not end with.
            return "endswith(" + field + ", " + dataVal + ") eq false";
        case "nu":
            return field + " eq null";
        case "nn":
            return field + " ne null";
        default:
            return field + " " + op + " " + dataVal;
    }
};

/// cols is an array.
function getQuoteFlags(cols) {
    return $.map(cols, function (element, idx) {
        //sortCols
        var quoteFlags;

        switch (element.dataType) {
            case "hidden":
                quoteFlags = { col: element.fieldName, quoteValue: true, isList: false };
                break;
            case "hidden-number":
                quoteFlags = { col: element.fieldName, quoteValue: false, isList: false };
                break;
            case "number":
                quoteFlags = { col: element.fieldName, quoteValue: false, isList: false };
                break;
            case "link":
                quoteFlags = { col: element.fieldName, quoteValue: false, isList: false };
                break;
            case "boolean":
                quoteFlags = { col: element.fieldName, quoteValue: false, isList: false };
                break;
            case "list-number":
                quoteFlags = { col: element.fieldName, quoteValue: false, isList: true, baseQuery: element.baseQuery, baseQueryParam: element.baseQueryParam };
                break;
            case "list":
                quoteFlags = { col: element.fieldName, quoteValue: true, isList: true, baseQuery: element.baseQuery, baseQueryParam: element.baseQueryParam };
                break;
            default:
                quoteFlags = { col: element.fieldName, quoteValue: true, isList: false };
                break;
        };

        if (element.sortCols !== undefined) {
            quoteFlags = $.extend(true, {
                sortCols: element.sortCols
            }, quoteFlags || {});
        };

        return quoteFlags;
    });
};

// primarily used for the bid text code, the idea here is that we
// can do the sorting for numerics and codes so that all of the 13's
// are grouped together for the UI.
function buildColumnSort(gridColCommand, filters) {
    var parts = gridColCommand.split(" ");

    if (parts.length !== 2) {
        throw new Error("Cannot build a sort command without the column name and the direction.");
    }

    var col = parts[0];
    var direction = parts[1];

    if (col === "" || direction === "") {
        throw new Error("We need to know both the column name and the sort direction.");
    }

    var quoteData = $.grep(filters, function (element, idx) {
        return element.col === col;
    });

    if (quoteData.length === 0) {
        // if we don't have a definition for the field, then we can't filter/search for it.
        return "";
    };

    quoteData = quoteData[0];

    if (quoteData.sortCols !== undefined) {
        var colSorts = [];

        for (var i = 0; i < quoteData.sortCols.length; i++) {
            colSorts.push(quoteData.sortCols[i] + " " + direction);
        };

        return colSorts.join(", ");
    }

    return col + " " + direction;
}

function quoteDataVal(data, filter) {

    if (filter.quoteValue) {
        return "'" + data + "'";
    }

    return data;
}

function findQuoteFilter(field, filters) {
    var quoteFilter = $.grep(filters, function (element, idx) {
        return element.col === field;
    });

    if (quoteFilter.length === 0) {
        throw new Error("Cannot find appropriate quote filter for field: " + field);
    };

    return quoteFilter[0];
}

Clients Need to Assume Ownership of Their Projects

It seems as if there are many intelligent, well meaning people in this world that have a great concept or idea they want to bring to market.  It's always the same pitch:

We have an idea for Whiz-Bang 1.0 that will completely revolutionize the current market.  We are the experts in our market, and we know what we need.  Once you develop the product, we can make millions, millions I say


So ok, we as developers buy into this revolutionary concept and try to bring the concept into reality.  Now keep in mind, these people that approach us as developers are usually in a sales role, or they may have enough technical knowledge to be dangerous, but they never can quite quantify that the person that they just hired on has absolutely zero concept or clue as to exactly what is required to deliver.

So, as a well meaning software development professional, the first step of the SDLC is to always define a set of requirements or some sort of contract (albeit nothing enforceable by a court of law, but still...) that sets down on paper the goals of the project, in as much business detail as possible.

Now on multiple projects back-to-back, I feel as if I need to come up with a better mechanism to handle this.
Next time I need to setup a connection to Hyper-V:

http://thetechnologychronicle.blogspot.com/2013/11/hyper-v-server-2012-remote-management.html

To list all connections on the computer:
Get-NetConnectionProfile

To turn public network interfaces into private network interfaces:
Set-NetConnectionProfile -InterfaceIndex {the index} -NetworkCategory Private

Enable-NetFirewallRule -DisplayGroup *