Sunday, November 27, 2011

Flexigrid CRUD(inline form) with ASP.NET MVC

In this post, I'll show you the supporting code for placing a form within flexigrid.


First, declare a variable for the html string of your form:

<script type="text/javascript">
    var formHtml = "";
    

    function canRender() {
        return _canRender;
    }

    $(function () {
        _canRender = false;
    });

</script>


We use canRender so we can defer the execution of javascript of the form's html string

<div id="editor" style="visibility: hidden">
    @using (Html.BeginForm(null, null, FormMethod.Post, new { id = "theForm" }))
    {
        <fieldset>
            <legend>Person</legend>

            @Html.HiddenFor(x => x.PersonId)
            @Html.HiddenFor(x => x.RowVersion)

            <div class="editor-label">
                @Html.LabelFor(x => x.Username)
            </div>
            <div class="editor-field">
                @Html.EditorFor(x => x.Username)
                @Html.ValidationMessageFor(x => x.Username)
            </div>


            <div class="editor-label">
                @Html.LabelFor(x => x.Firstname)
            </div>
            <div class="editor-field">
                @Html.EditorFor(x => x.Firstname)
                @Html.ValidationMessageFor(x => x.Firstname)
            </div>

            <div class="editor-label">
                @Html.LabelFor(x => x.Lastname)
            </div>
            <div class="editor-field">
                @Html.EditorFor(x => x.Lastname)
                @Html.ValidationMessageFor(x => x.Lastname)
            </div>


            <div class="editor-label">
                @Html.LabelFor(x => x.Country.CountryId)
            </div>
            <div class="editor-field">

                <table style="border-style: none">
                <tr>
                <td style="border-style: none">
                @Html.AjaxComboBoxFor(x => x.Country.CountryId, "/Countries/Management/Lookup/", "/Countries/Management/Caption/",
                    new { }, new { sub_info = true, can_render_callback = "canRender", textbox_width = 200 })    
                </td>
                <td style="border-style: none">
                @Html.ValidationMessageFor(x => x.Country.CountryId)
                </td>
                </tr>
                </table>                     
            </div>
            

            <div class="editor-label">
                @Html.LabelFor(x => x.FavoriteNumber)
            </div>
            <div class="editor-field">
                @Html.EditorFor(x => x.FavoriteNumber)
                @Html.ValidationMessageFor(x => x.FavoriteNumber)                
            </div>

            <p>
                <input type="submit" value="Save" />
                <input type="button" id="Closer" value="Close" />
            </p>


            
        </fieldset>
        
        <div style="max-width: 500px; width: 500px;">
            @Html.JsAccessibleValidationSummary(excludePropertyErrors: true)            
        </div>
    }
<script type="text/javascript">

    $(function () {


        if (!canRender()) return;


        var scope = $('#theForm');
        parseDynamicContent(scope);




        $('#Closer', scope).click(function (e) {
            closeForm($(scope));
        });

        $('input[type=submit]', scope).click(function (e) {
            try {


                e.preventDefault();

                if (!scope.valid()) {
                    // alert('has invalid');
                    return;
                }

                save(scope);

                // closeForm(scope);
            } catch (e) {
                alert("Error " + e);
            }

        });

        $(scope).attr('id', guid());
    });

    function save(scope) {        

        $.ajax({
            url: '/People/Management/SaveViaAjax',
            type: 'POST',
            data: $(scope).serialize(),
            success: function (result) {

                var isOk = $(scope).modelValidation(result);


                if (isOk) {

                    var isNew = $('#PersonId', scope).val() == '';

                    if (isNew) {
                        $('#PersonId', scope).val(result.PersonId);
                    }

                    $('#RowVersion', scope).val(result.RowVersion);


                    if (isNew) {
                        $(scope).closest('table').flexReload();
                    }
                    else {


                        setFgEditText(scope, 'Username', $('#Username', scope).val());
                        setFgEditText(scope, 'Firstname', $('#Firstname', scope).val());
                        setFgEditText(scope, 'Lastname', $('#Lastname', scope).val());
                        setFgEditText(scope, 'FavoriteNumber', $('#FavoriteNumber', scope).val());

                        setFgEditText(scope, 'Country', $('#Country_CountryId', scope).ajc().getText());

                        closeForm(scope);
                    }

                }


                // $('#firstTable').flexReload();



            },
            error: function (a, b, c) {
                alert(a.statusText);
                alert(b);
                alert(c);
            }
        });                       //ajax
    }//save

</script>
</div>


Here's the flexigrid setup and getting the form's html string:

<script type="text/javascript">


    $(function () {
        // main..
        setupForm();

        setupFirstTable();
        

        
        // ..main
    });


    function setupForm() {
        formHtml = $('#editor').html();
        $('#editor').remove();        
    }

    function setupFirstTable() {
        $('#firstTable').flexigrid({
            url: '/People/Management/List',
            dataType: 'json',
            colModel: [
                    { display: 'User', name: 'Username', width: 150, sortable: true, align: 'left' },
                    { display: 'Firstname', name: 'Firstname', width: 150, sortable: true, align: 'left' },
                    { display: 'Lastname', name: 'Lastname', width: 150, sortable: true, align: 'left' },
                    { display: 'Favorite#', name: 'FavoriteNumber', width: 150, sortable: true, align: 'left' },
                    { display: 'Country', name: 'Country', width: 150, sortable: true, align: 'left' },
                    { display: 'RowVersion', name: 'RowVersion', width: 150, sortable: true, align: 'left', hide: true }
                ],

            buttons: [
                    { name: 'Add', bclass: 'add', onpress: add },
                    { separator: true },
                    { name: 'Edit', bclass: 'edit', onpress: edit },
                    { separator: true },
                    { name: 'Delete', bclass: 'delete', onpress: del }
                ],

            singleSelect: true,
            sortname: 'Lastname',
            sortorder: 'asc',
            usepager: true,
            title: 'Persons',
            useRp: true,
            rp: 5,
            rpOptions: [5, 10, 15, 20, 25, 40],
            showTableToggleBtn: true,
            width: 900,
            height: 'auto',
            preProcess: function (data) {
                var rp = getFgRowsPerPage($('#firstTable'));
                for (i = data.rows.length; i < rp; ++i) {
                    data.rows.push({ 'id': '', 'cell': ['', '', '', '', '', ''] });
                }                
                return data;
            }
        });    // flexigrid

        setupGrid($('#firstTable'));

    } //setupFirstTable



    function add(com, grid) {
        try {            
            closeFormByGrid(grid);            
            showAddFormByGrid(grid, formHtml);            
        } catch (e) {
            alert('error ' + e);
        }
    }


    function edit(com, grid) {

        closeFormByGrid(grid);


        var items = $('.trSelected', grid);
        var item = items[0];
        var pk = item.id.substr(3);

        if (pk.length == 0) return;

        

        $.ajax({
            url: '/People/Management/GetUpdated/' + pk,
            type: 'POST',
            success: function (data) {



                showEditForm(item, formHtml, function () {

                    var form = $('form', grid);


                    $('#PersonId', form).val(data.Record.PersonId);
                    $('#Username', form).val(data.Record.Username);
                    $('#Firstname', form).val(data.Record.Firstname);
                    $('#Lastname', form).val(data.Record.Lastname);


                    $('input[id=Country_CountryId]', form).val(data.Record.CountryId);
                    $('#FavoriteNumber', form).val(data.Record.FavoriteNumber);
                    $('#RowVersion', form).val(data.Record.RowVersion);

                    
                    $('#Country_CountryId', form).ajc().showCaption();
                    

                    setFgEditText(grid, 'Username', data.Record.Username);
                    setFgEditText(grid, 'Firstname', data.Record.Firstname);
                    setFgEditText(grid, 'Lastname', data.Record.Lastname);                    
                    setFgEditText(grid, 'FavoriteNumber', data.Record.FavoriteNumber);


                }); //showEditForm

            } //success
        }); //ajax
    }//edit

    function del(com, grid) {

        var deleteIt = confirm('Do you want to delete the selected record?');

        if (!deleteIt) return;

        var pk = getCurrentRowPk(grid);
        var version = getFgGridColumnText(grid, 'RowVersion');

        // alert(pk + " " + version + " " + encodeURIComponent(version));


        $.ajax({
            url: '/People/Management/Delete',
            type: 'POST',
            data: 'pk=' + pk + '&version=' + encodeURIComponent(version),
            success: function (result) {
                if (result.IsOk) {
                    $('#firstTable').flexReload();
                }
            }
        });

    }


</script>


Lastly we put this at the end of the html:
<script>
    $(function () {        
        _canRender = true;
    });

</script>


Here's the Flexigrid form-inliner helper functions:

function showEditForm(selectedTr, html, assignerFunc) {

    $(selectedTr).after('<tr class="fgEdit" editId=' + selectedTr.id + '><td width="1" colspan="20" style="border-width: thin; border-top: thick; border-color: #EEE; white-space: normal;"><span></span></td></tr>');
    

    var content = $('td > span', $(selectedTr).next());

    // var form = $(content).hide().html(html);
    var form = $(content).html(html);
    
    assignerFunc();
    
    $(content).show();
    
}


function showAddFormByGrid(grid, formHtml) {

    var tbl = $('.bDiv table', grid);
    showAddForm(tbl, formHtml);    
}

function showAddForm(tbl, formHtml) {
    var tbody = $('tbody', tbl);
    if (tbody.length == 0) {
        $(tbl).append($('<tbody/>'));
        tbody = $('tbody', tbl);
    }
    

    $(tbody).prepend('<tr class="fgEdit"><td width="1" colspan="20" style="border-width: thin; border-top: thick; border-color: #EEE; white-space: normal"><span></span></td></tr>');
    var content = $('tr td span', tbody);
        
    $(content).html(formHtml);


}





Flexigrid CRUD(inline form) example with ASP.NET MVC, complete with model validation:
http://code.google.com/p/flexigrid-crud-example/downloads/list


Known issues:

1. Not with flexigrid per se; but when inside of flexigrid, the jQuery Ajax ComboBox's result area is far from its textbox when using Firefox or IE, have made a work-around in Chrome though. If you could lend a help in correcting that problem of jQuery Ajax ComboBox, I'll be more than glad to accept a patch.

2. If the flexigrid has no rows, the inline form is not in full width. Accepting patch for flexigrid code or helper code.



Sample output


jQuery Ajax ComboBox's detached result screenshot(IE and Firefox problem)

$.post returns incorrect empty string

You'll receive [object XMLDocument] in your jQuery $.post's data ...

var $xhr2 = $.post( 
 options.init_src,
 {
  'lookupId': theValue,
  'field': options.field,
  'primary_key': options.primary_key,
  'db_table': options.db_table
 },
 function (data) {
  $input.val(data); // textbox receives [object XMLDocument]  
 }
);


..., when you return empty string from your controller:

public string Caption(string lookupId)
{
 return "";
}


In order to prevent that problem, just add "text" parameter to your $.post method return type


var $xhr2 = $.post( 
 options.init_src,
 {
  'lookupId': theValue,
  'field': options.field,
  'primary_key': options.primary_key,
  'db_table': options.db_table
 },
 function (data) {
  $input.val(data); // receives "" string now.  
 },
 "text"
);