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 *


Monday, July 7, 2014

Running Fluent Migrator from PowerShell

So i'll be testing this out: https://bitbucket.org/cdroulers/nuget.powershell/wiki/FluentMigrator.PowerShell

It's a way to run fluent migrator from the powershell command line.  the hope here is that, pending it works, i should be able to execute fluent migrator *.dlls during a deployment with octopus deploy.

Thursday, January 19, 2012

Replacement for LabelFor in ASP.NET MVC 3

As I've been deep-diving further into ASP.Net MVC 3, I ran into an issue where I needed to arbitrarily show an indicator against a label that would tell me which fields are required.  We were initially doing this by hand, but have been using a ton of inheritance to set [Required] attributes on fields.  In order to overcome this pain, we decided to create a helper method that we now use instead of @Html.LabelFor()... it looks like this (Exactly from *.cs file):

using System.Linq;
using System.Linq.Expressions;

namespace System.Web.Mvc
{
    public static class HtmlHelpers
    {
        public static MvcHtmlString LabelForRequired<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, 
            Expression<Func<TModel, TValue>> expression,
            string id="", bool generatedId=false)
        {
            var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            var htmlFieldName = ExpressionHelper.GetExpressionText(expression);

            if (metadata.IsRequired)
            {
                var labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
                if (string.IsNullOrWhiteSpace(labelText))
                {
                    return MvcHtmlString.Empty;
                }

                var tag = new TagBuilder("label");
                var spanTag = new TagBuilder("span");
                spanTag.AddCssClass("required");
                spanTag.SetInnerText("*");

                if (!string.IsNullOrWhiteSpace(id))
                {
                    tag.Attributes.Add("id", id);
                }
                else if (generatedId)
                {
                    tag.Attributes.Add("id", htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName) + "_Label");
                }


                tag.Attributes.Add("for", htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
                tag.SetInnerText(labelText);

                return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal) + "&nbsp;" + spanTag.ToString(TagRenderMode.Normal));
            }
            else
            {
                return System.Web.Mvc.Html.LabelExtensions.LabelFor(htmlHelper, expression);
            }
        }
    }
}


Hope that this helps someone...

Wednesday, December 7, 2011

Client-Side Model-Aware Validation in MVC3

Just a note so mostly, I remember.

I've been working with Nick Riggs' Foolproof validation in MVC3, trying to get more complex scenarios in place, and needed to write the client validation.  He has a great post for MVC2 at
http://www.nickriggs.com/posts/client-side-model-aware-validation/, but it doesn't cover MVC3.

After some research, etc., here is what I came up with for something very simplistic, where it's just making sure a value is set, etc...

$(function () {

    jQuery.validator.addMethod('listrequirediftrue', function (value, element, param) {
        if (param) {
            return $('#' + $(element).attr('id') + ' option:selected').length > 0;
        }

        return true;
    }, '');
    jQuery.validator.unobtrusive.adapters.add('listrequirediftrue', {}, function (options) {

        if (options.message) {
            options.rules['listrequirediftrue'] = true;
            options.messages['listrequirediftrue'] = options.message;
        }

    });
} (jQuery));

I just need to now figure out for more complex scenarios, like when you have a RequiredIf, but dependent on when another dropdown has a certain value.  Nick has the RequiredIfAttribute, but when you're validating based on an entire model, where the property to be validated is not at the same level as the property whose value is the "RequiredIf" property, you have to use the ModelAware style of validation... will update when I have an answer.

Thursday, November 3, 2011

More quirks with ASP.NET MVC 3

So, another great quirk with ASP.NET MVC 3.  When you implement a controller with two methods, one being for the initial load of the page, and the other being for the subsequent form post to process the data... if your intention is to re-render the page to do another 'Add Item' scenario, you must issue a ModelState.Clear(); command before you render the new view... otherwise, it seems to keep the values from the posted data.

Monday, June 27, 2011

ASP.NET MVC3 has a few quirks

http://aspnet.codeplex.com/workitem/7629

Ack!  Seems like this shouldn't have happened. 

To correct, you have to do the following:

Give your select list the "Required" attribute.
Once the page loads, in jQuery, assign the data-val attribute and the data-val-message attributes.

Microsoft, this is not good!

Tuesday, June 7, 2011

Team Foundation Server 2010 isn't friendly for certain items...

It seems as if this is a normally recurring issue with build automation and Team Foundation Server 2010 specifically.  Utilizing Team Foundation Server's build automation, it takes the *.sln and *.csproj files and utilizes that to create the output binaries as expected.  What is unfortunate is that the .Net SDK doesn't have everything that you need to handle build automation on a build server, so you're forced to do one of 2 things:
  1. Deploy VS.Net 2010 on your build server(s)
  2. Copy the files from c:\{Program Files x86}\MSBuild\Microsoft\VisualStudio\... to your build machine.
Honestly, both solutions break the fundamental rules of using a build automation server, IMHO.  Completely disappointed Microsoft! I think that I'll be writing ScottGU or someone else to see if they have a better solution to handle this case, as it's simply a poor "workaround" solution.

Thursday, June 2, 2011

Textarea Autoexpand Javascript code.

As part of a project for a consulting job I'm on, the need we had was to give a forever expanding text area on the html form.  This is the javascript I wrote to do just that.  I'm sharing it because mainly, I think it's cool!

I should have documented how this works. What you'll do in the Html is to define a text area (could be an asp TextBox with its mode set to Multiple, or a generic TextArea tag... either way, they render out the same.) and you'll add an attribute called expandable and set its value equal to True.  From there, reference jQuery in your header, and this code should work for you as-is.

Have at it, and comment if you see where I could improve in some way.

          function formatPage() {
            $(document).find('*[expandable]').keyup(function () {
                var height = 0;
                var existingHeight = this.style.height;
                var lines = this.value.split('\r');
                this.style.height = '';
                for (x = 0; x < lines.length; x++) {
                    var lengthOverflow = lines[x].length % this.cols;

                    height = height + (14 * (((lines[x].length - lengthOverflow) / this.cols)));
                    if (lengthOverflow > 0) {
                        height = height + 14;
                    }
                }

                if (height == 0) {
                    height = 14;
                }

                if (existingHeight != height) {
                    this.style.height = height + 'px';
                }
            });

            $(document).find('*[expandable]').keyup();
        }

        $(document).ready(function () {
            formatPage();
            Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequestHandler);
        });


        function endRequestHandler(sender, args) {
            formatPage();
        }

Saturday, December 4, 2010

A few changes on the horizon

It's been a while since I've last written a blog post.  Much to my amazement, there has been a few changes in the past n months that are scary and exciting at the same time.

I decided it is time to leave behind an old project and start to look ahead.  I'm starting by taking a 6 month contract position with someone, and be forced back into 8 hour shifts, so I can get my full day stamina back.  The past three years has been rather hectic with family woes and pulling many days on the road, not to mention, many late night coding/testing sessions to support a client base that I never had access to in the beginning.

A business contact discussed a new business venture with me that if all works well, we should be starting in the next six months, right after I finish the contract position with whichever company I choose to work with.  This venture is going to be a culmination of everything that I've done in the past 5 years, utilizing the recent education of learning how to properly deploy an application, how to use the latest and greatest in technology, and to even now be able to use some technology that is still being developed from the folks at Microsoft.  I can't wait to get started... it should be a wild ride.

As we go, the hope is to keep posting occasionally on the progress of the project, and maybe a tidbit or two of what the project is.  Again, I can't wait to get the project going, and to-market... it's going to be a wild ride!

Friday, February 26, 2010

Building a licensing system... thoughts on EULA's

In software development, it seems as if the devil is in the details.  You just get a great whizz-bang product built, spent 100's of hours on getting every last detail to work just the way it needs to in order to work in the market.  Now, it's time to sell the product.

Initial thoughts from potential customers are hopeful, but a few create a backlash with you.  They say that you're asking for too much for the product, and then come back and purchase a license anyhow.  What do you do to control them from distributing your product 100's of times?  Ahh... the need for product licensing just arose.

Now, although there are 1,000's of tools on the market for just this situation, I have decided to roll my own.  My reasoning comes from the not-invented-here attitude, meaning that I do want a bit of the god-like control over how the software system works.  I have special needs that I only know about, and that I don't want anyone to have the intellectual knowledge of how I did it just yet, because I want to avoid having an end user override my licensing scheme until after I have it written.  I have done many different projects, and they all have a custom list of what needs to be controlled.  I also don't like the fact that I've not seen much where the EULA can be created in a single format, and then exported to PDF/RTF/HTML/etc., so that's another sticking point.

The system will be constructed of the following:
  • a WPF application that will allow data entry.
  • a WCF web service to allow license key activation.
  • a website (v2) to allow end-users to purchase and issue their own keys.
  • 1 dll per product to handle licensing.  This will lend to better custom rules needed on a per-product or per-addin scenario, where buying an off-the-shelf product may not be able to handle this as well.
So far, I've adopted the use of Markdown for formatting the EULA's and can enter reseller information.  Adopting Markdown allows the quick and easy transformation of the text into HTML, and can then eventually have a converter written to move it to other formats, or use the HTML and create that into other formats.  Once I can complete the task of entering the rest of the product information, I will publish a few wireframe screenshots to the web for anyone that is interested in my progress as I go.

My hope is that I can get something respectable that will work for my needs, and maybe market this little tool to other software developers as time goes on.

Sunday, January 17, 2010

family woes... or how finances never seem to work out

It seems just when things in life are starting to get back to a better time, something worse starts in that puts you 5 steps behind where you started.  Again, that is the day for me today...

Things are going well, as my mother's surgery has finally turned life around for her.  She is handling the healing process, and is pulling through quite well all in all.  Then the issue of insurances has come around, and when it's time to pay the piper, we can't seem to get the finances in order to handle the incurred costs.

So, now it's time to whore myself out and try to help better support the folks until such a time as they can get back on their feet.  Looks like the road continues, although I thought that they finally got themselves to a point where they could handle things.  Bummer.

Sunday, December 20, 2009

dealprocessor 1.5 => last of the 1.x series

After much deliberation about this, I am finally going to bite the bullet.  Deal Processor v1.5.xxx.yyy is going to be the last of the installable versions of Deal Processor.

After evaluating the benefits and drawbacks of using Silverlight, I can fully attest that for the client base that we are targeting, being able to offer the COR application as a Silverlight-based application, and then providing a local winforms or WPF application for use by the reps is going to be the way to go.

Look in May/June for more posts about V2, and it's benefits.

Saturday, October 10, 2009

$79 Million dollar government project a waste?

I'm an avid user of Facebook.  A friend's mom put a simple post out on the site saying the following:

We're in a recession with people losing their jobs and homes and our country just spent 79 million on a rocket to shoot a hole in the moon?!


My plan was to never use this blog to do anything political, but this is one that I simply cannot leave go, and I really need to vent the frustrations out with this statement... Oui!

Our country spent 79 million dollars creating jobs.  Keep in mind what it costs to do that, and the amount of resources needed, the facilities, etc.  To explain how the 79 million dollar project created many jobs:

You need to purchase things like equipment, raw materials, facilities, etc to reach the goal.  You also need to pay salaries to the administration that is running it, the "Shop Floor" workers, the maintenance guys, the trainers, etc.

Out of that "paid" money, you are now enabling many companies to now keep their staffs employed and paid.  All of those staffs, and the NASA staff are now enabled to go and umm... how do I say this, pay for necessary human services... (Hair Cuts, Dr's appts, Dentist appts, etc.)... they are then also enabled to go to concerts, restaurants, ball-games, dances, ferry rides, amusement parks... All of the lower-rung "SERVICE" based business.

Now, when you understand that underlying the entire economy are service-based businesses (since a good part of the traditional manufacturing is overseas), you'd understand why the 79 million dollars, pending 78.9 million of it wasn't lining the politicians pockets, was a decent investment.

To put another spin on this... Most of the current recession has been more of a correction in the market for bad decisions over the past years... From what I've learned in the past, no point in time up until recently (past 10-20 years) has there ever been so many foolish mistakes in the US economy... think about how easy it was (no more than 2 years ago) for an 18 year old to get a credit card.  I can recall getting 3-10 offers a month.  Every month as well, I got "checks" with my credit card statement that allowed me to "Access my credit line"

No-one ever told the 18-year old that he'd be paying 22-30% interest, and if he only paid the monthly minimum of $10, he'll never get it paid off, but keep racking it up even if he NEVER charged another item on the card again?!?!?!  This has been discussed on many news programs enough that anyone that has a pulse can reference a report on it by now?!

The funny thing with this is that most of the time, again and again, everyone wants to blame the public school system and/or the government... that someone didn't police the credit agencies.  That the credit agencies were allowed to offer sub-prime loans for housing, etc.

In reality, it's up to the individual to READ THE FUCKING FINE PRINT on the application!  I feel like it's quite ridiculous that at 26/27, I know so many people of my own age that are in debt (outside of student loans) to the point that they can't dig themselves out to save their lives.

To go a bit more globally on that subject, us Americans should take a queue from the Irish... they don't purchase anything until after they can pay cold, hard cash for it... it's just not part of their culture, and us Americans need to learn how to adopt that part of their culture.  We have so much to learn.

Credit is such a sham, and it causes drops in the economy that everyone wants to blame the damn government for, instead of themselves.

one other spin... watch the movie "The Aviator" when he goes to court and is asked about the failed projects.  Very insightful view, and also quite applicable to this subject!

Friday, September 18, 2009

Starting work with Silverlight...

I've been amused by the toys out on the web that have been developed in Flash/Flex and Silverlight over the past few years. Even some as big as the Olympics being broadcasted over the world in part by these technologies... and now, with Microsoft finally releasing Silverlight 3 to the web a few months ago, it has enough "brass" available that someone can build a decent LOB application.

I've been pondering converting dealprocessor to be hosted for many reasons, inclusive of the support nightmares I've already had with only two companies running the software.  My hope is that by having the COR hosted on a web server "in the cloud" somewhere, I can relieve at least one headache for the businesses that will start to purchase the software.  Only time will tell... and it's going to be one hell of a Ride... I can't wait to be able to start posting preview links for the app!

Monday, September 14, 2009

POS 2009 - Timeforge Integration

We're in testing... although I don't see many POS 2009 dealers jumping for joy... most of them are not quite willing as of yet to bit the bullet on this new system.  I hope that things improve for SP1, and that more dealers come on board, or Microsoft will lose a pretty sweet opportunity for third party integration.

Tuesday, June 23, 2009

Timeforge Integration - First Steps

So, first real step was taken today... to see how long it would take to get the workstation components into the POS interface, to make a homogenious environment for the POS user to update the required data for Timeforge.

In the POS Bootcamp, this is something that took about 10-15 minutes, after all the discussions... after about 45 minutes or so, I finally got things working. It turns out the manifest file gets to be quite picky. In the manifest file, you define the following (according to the POS 2009 Beta 2 SDK Documentation):


<AddinManifest>
<AddinAssembly
assembly="Name of the developer's add-in .dll file"
implementedViews="Microsoft.Rms.AddInViews.ServiceModel.IPosAddIn"
namedPermissionSet="FullTrust"
exposeUI="true/false"
addInID="b9ac2dda-1520-4a48-b8da-ac187b0ad172"
addInDisplayName="Simple POS Add-in Sample"
addInDescription="Simple POS Add-in sample"
company="AddInDeveloperCompanyName"
version="1.0"
url="AddInDeveloperCompanyURL.com"
enabled="True"
/>
</AddinManifest>
So, I did this, copied it, inserted it into the Timeforge.manifest file, and tried for over 30 minutes to figure out why it wasn't working. Finally, I looked at a few of the other manifest files, and discovered something quite interesting... See if you notice it:


<AddInManifest>
<AddInAssembly
url="http://go.microsoft.com/fwlink/?LinkId=126931"
enabled="True"
separateAppdomain="True"
/>
</AddInManifest>

And in case you are wondering, no... it doesn't have to do with the fact that there aren't as many attributes on the AddInAssembly tag. It's all about the capitalization. The POS Beta 2 SDK documentation states to do it the former way, and the way it needs to really be written (as far as capitalization goes) is the way it looks in the latter.

So, I simply made those edits, and away I go now to start the real work on the POS 2009 implementation for Timeforge.