1

I have the following MVC application, see front-end below:

FinanceMVC screen - front-end

"Invoice Date Created" is a html5 compatable datepicker based on the following method:

http://www.aubrett.com/InformationTechnology/WebDevelopment/MVC/DatePickerMVC5.aspx

My requirement on screen load is the following:

1) On datepicker "onSelect" of date and textbox updates, I need to pass this value e.g. 2014/11/19 and manipulate it to be in the format of yyyymm (e.g. 201411). There's a catch. It needs to be able create such a value for the current period, as well as the previous period.

So, 2014/11/19 should populate "Invoice Number Period" Dropdown list with two options namely:

201411 (for current period)
201410 (for previous period)

Is there a way I can pass this date 2014/11/19 e.g. back to my controller where I can do the manipulation and return 201411 and 201410 back to my dropdown list? Or am I thinking about this the wrong way? I have read plenty of Javascript articles but none do exactly what I need to do. How and what is the best way to go about this?

I am an MVC and Javascript newby, so struggeling to get my head around this one..

Edit - Here is my code:

Finance.cshtml:

@model List<FinanceMVC.Models.FinanceViewModel>
@{
    ViewBag.Title = "Index";
}

@using (Html.BeginForm("Finance", "Finance", FormMethod.Post, new { @class = "form-horizontal" }))
{
    <div class="col-md-1 fade in" style="margin-left: 5%; width: 100%; font-size: 11px; height: auto;">
        <div id="div_Finance_Container" style="width: 100%; height: 80%; overflow: auto; text-align: left;" runat="server" class="scroll-pane">
            <div style="height: 500px; overflow: auto;">
                <div><a href="#" id="addNew" class="add-another-cat smallest">Add New</a></div>
                <table id="dataTable" border="0">
                    <tr>
                        <th>Invoice Type</th>
                        <th>Invoice Number</th>
                        <th>Invoice Number Period</th>
                        <th>Invoice Date Created</th>
                        <th></th>
                    </tr>
                    @if (Model != null && Model.Count > 0)
                    {
                        int j = 0;
                        foreach (var i in Model)
                        {
                        <tr>
                            <td>
                                <div class="col-md-1" style="width: 20%;">
                                    <select name="ddlInvoiceType">
                                        @foreach (var item in Model)
                                        {
                                            foreach(var bItem in item.InvoiceType)
                                            {
                                                <option value="@bItem.Description">@bItem.Description</option>
                                            }
                                        }
                                    </select>
                                </div>
                            </td>
                            <td>@Html.TextBoxFor(a => a[j].InvoiceNumber)</td>
                            <td>@Html.TextBoxFor(a => a[j].InvoiceNumberPeriod)</td>
                            <td>@Html.EditorFor(a => a[j].InvoiceDateCreated)</td>
                            <td>
                                @if (j > 0)
                                {
                                    <a href="#" class="remove">Remove</a>
                                }       
                            </td>
                        </tr>
                                j++;
                        }
                    }
                </table>
                <input type="submit" value="Save Bulk Data" />
            </div>
        </div>
    </div>
} 


@section Scripts{
@Scripts.Render("~/bundles/jqueryval")

<script language="javascript">
    $(function () {
        $(document).on('change', '#InvoiceDateCreated', function () {
            alert($(this).val());
        });
    });
</script>

<script type="text/javascript">
    $(document).ready(function () {
        $('#[0].InvoiceDateCreated').change(function () {
            $('#[0].InvoiceDateCreated').text('sam');
        });
    });
</script>

<script language="javascript">
    $(document).ready(function () {

        //1. Add new row
        $("#addNew").click(function (e) {
            e.preventDefault();
            var $tableBody = $("#dataTable");
            var $trLast = $tableBody.find("tr:last");
            var $trNew = $trLast.clone();

            var suffix = $trNew.find(':input:first').attr('name').match(/\d+/);
            $trNew.find("td:last").html('<a href="#" class="remove">Remove</a>');
            $.each($trNew.find(':input'), function (i, val) {
                // Replaced Name
                var oldN = $(this).attr('name');
                var newN = oldN.replace('[' + suffix + ']', '[' + (parseInt(suffix) + 1) + ']');
                $(this).attr('name', newN);
                //Replaced value
                var type = $(this).attr('type');
                if (type.toLowerCase() == "text") {
                    $(this).attr('value', '');
                }

                // If you have another Type then replace with default value
                $(this).removeClass("input-validation-error");
            });
            $trLast.after($trNew);

            // Re-assign Validation 
            var form = $("form")
                .removeData("validator")
                .removeData("unobtrusiveValidation");
            $.validator.unobtrusive.parse(form);
        });

        // 2. Remove 
        $('a.remove').live("click", function (e) {
            e.preventDefault();
            $(this).parent().parent().remove();
        });

    });
</script>
}

Finance.cs (Model):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace FinanceMVC.Models
{
    //public class Finance
    //{

    //}

    public partial class FinanceViewModel
    {
        /*ID*/
        [Required]
        [Display(Name = "ID")]
        public int ID { get; set; }

        /*BINL_BIND_ID*/
        [Required]
        [Display(Name = "Invoice Type")]
        //public int InvoiceType { get; set; }
        public List<C_InvoiceType> InvoiceType { get; set; }

        /*BINL_Inv_Num_Pointer*/
        [Required]
        [Display(Name = "Invoice Number")]
        public char InvoiceNumber { get; set; }

        /*BINL_Inv_Num_Period*/
        [Required]
        [Display(Name = "Invoice Number Period")]
        public int InvoiceNumberPeriod { get; set; }

        /*BINL_Created*/
        [Display(Name = "Invoice Created Date")]
        [Required]
        [DataType(DataType.DateTime)]
        public DateTime InvoiceDateCreated { get; set; }

        /*BINL_SystemInserted*/
        [Required]
        [Display(Name = "Invoice System Inserted")]
        public bool InvoiceSystemInserted { get; set; }


    }

    public class C_InvoiceType
    {
        public int ID { get; set; }
        public string Description { get; set; }
    }
}

FinanceController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using FinanceMVC.Models;
using System.Threading.Tasks;
using System.Data;
using System.Data.SqlClient;

namespace FinanceMVC.Controllers
{
    public class FinanceController : Controller
    {
        public ActionResult Finance()
        {
            FinanceViewModel FVM = new FinanceViewModel();

            // This is only for show by default one row for insert data to the database
            List<FinanceViewModel> FinanceViewList = new List<FinanceViewModel> 
            { 
                new FinanceViewModel 
                { 
                    ID = 0 , InvoiceType = ReturnInvoiceType(), InvoiceNumber = '\0', InvoiceNumberPeriod = 0, InvoiceDateCreated = DateTime.Now, InvoiceSystemInserted = false 
                } 
            };

            return View(FinanceViewList);
        }

        // 
        // GET: /Finance/ 

        /*Matches file name of Finance.cshtml*/
        [HttpPost]
        public ActionResult Finance(List<FinanceViewModel> model)
        {
            if (ModelState.IsValid)
            {

            }

            return View(model);
        }

        // 
        // GET: /Finance/Welcome/ 

        public string Welcome()
        {
            return "This is the Welcome action method...";
        }

        public static List<C_InvoiceType> ReturnInvoiceType()
        {
            string sQ = ""
                       + " Select ID, BIND_Description \n"
                       + " From Biller_InvoiceNum_DefSet with(nolock) \n"
                       ;
            try
            {
                using (SqlConnection sCon = new SqlConnection(Application_Info.sMOB_Master_Conn()))
                {
                    if (sCon.State != ConnectionState.Open)
                    {
                        sCon.Open();
                    }

                    using (SqlCommand sCmd = new SqlCommand(sQ, sCon))
                    {
                        using (SqlDataReader sRdr = sCmd.ExecuteReader())
                        {
                            List<C_InvoiceType> list_InvoiceType = new List<C_InvoiceType>();
                            while (sRdr.Read())
                            {
                                list_InvoiceType.Add(new C_InvoiceType
                                {
                                    ID = (int)sRdr["ID"],
                                    Description = (string)sRdr["BIND_Description"]
                                });
                            }
                            return list_InvoiceType;
                        }
                    }
                }
            }
            catch
            {

            }

            return ReturnInvoiceType();
        }
    }
}
ekad
  • 14,436
  • 26
  • 44
  • 46
fransHbrink
  • 364
  • 5
  • 13
  • Why not just do it with javascript/jqeury on the client side. You can easily create those 2 options and update the dropdown without needing to call the server. –  Nov 18 '14 at 11:25
  • You have rendered the control for `InvoiceNumberPeriod` as a textbox but your question says its a drop down. Which do you want? –  Nov 18 '14 at 11:28
  • @StephenMuecke, it is still a textbox, will change it to a dropdown list. So, dropdown must be populated. – fransHbrink Nov 18 '14 at 11:31
  • @StephenMuecke, will look into this further. Excuse me like I said, I am a Javascript noob, so quickly seeing a solution is a little tricky. – fransHbrink Nov 18 '14 at 11:35
  • So essentially, every time you select a date, you want 2 options rendered, one for the current month and one for the previous month, in the format "yyyyMM"? –  Nov 18 '14 at 11:36
  • Exactly like that yes. – fransHbrink Nov 18 '14 at 11:37
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/65150/discussion-between-randbyb-and-stephen-muecke). – fransHbrink Nov 18 '14 at 13:47

3 Answers3

2

You can do this client side using jquery without the need to call the server. Since you have multiple rows, you will need to give your controls a class name so they can be selected, for example

@Html.EditorFor(a => a[j].InvoiceDateCreated, new { htmlAttributes = new { @class = "InvoiceDateCreated" }})

and add the following scripts

function padLeft(nr, n, str) {
  return Array(n - String(nr).length + 1).join(str || '0') + nr;
}

$('.InvoiceDateCreated').change(function () {
  var row = $(this).closest('tr');
  var date = new Date($(this).val());
  var currentMonth = date.getFullYear().toString() + padLeft(date.getMonth() + 1, 2);
  date.setMonth(date.getMonth() - 1);
  var previousMonth = date.getFullYear().toString() + padLeft(date.getMonth() + 1, 2);
  var select = row.find('.InvoiceNumberPeriod').empty();
  select.append($('<option></option>').val(currentMonth).text(currentMonth));
  select.append($('<option></option>').val(previousMonth).text(previousMonth));
});
  • Quickly gonna give this a shot. – fransHbrink Nov 18 '14 at 12:23
  • Note also you have a number of problems with the way you are rendering the collection. For example, you have a remove function to remove a row, but if you removed say the 3rd of 5 rows, only the first 2 rows would post back (you don't have an `Index` property so binding fails if the name indexer does not start at zero and are consecutive). There are other issues as well. –  Nov 18 '14 at 12:28
  • Yes, I realized also in non-html5 browsers "adEventListener" is not supported anymore right? Will still get past these issues. Think Javascript side is problem ridden unfortunately. Trying to implement the changes and the function, but getting some problems. Will keep updated. Thanks for all the feedback so far. – fransHbrink Nov 18 '14 at 12:35
  • Note I made a few corrections in he last 2 lines of the script (forgot some quotes) –  Nov 18 '14 at 12:36
  • Thanks, I am just having some trouble getting this to work. It never hits an alert that I place inside the "$('.InvoiceDateCreated').change(function () {" part. – fransHbrink Nov 18 '14 at 13:53
  • Then its not being called which means you have not given the datepickers the classname `InvoiceDateCreated`. Check the html your generating. And use `console.log();` for debugging, not `alert` –  Nov 18 '14 at 20:25
1

If you want to call controller from javascript, you can use ajax post function.

    $.ajax({
        type: "POST",
        url: "Finance/Finance",
        dataType: "json",
        data: {
           // pass in some data
        },
        success: function (results) {
            // do something (or nothing) with the returned data
        },
        error: function (e) {
            alert(e.status + " - " + e.statusText);
        }
    });
Grentley
  • 315
  • 3
  • 6
  • 19
  • Thank you, will quickly try this as well @Grentley. – fransHbrink Nov 18 '14 at 11:34
  • I have tried this approach, and I am afraid, I can't get this implemented? I have come to the point, where on page load it posts back to the url. But, I need something that will do this on date selected. I realize this is my lack of knowledge with Javascript/ajax etc. But I just can't seem to get it right.. – fransHbrink Nov 18 '14 at 11:52
1

EDIT:

For anyone using MVC 5 and not MVC 5.1, keep reading and find workaround below. For anyone using MVC 5.1, please see @StephenMuecke 's answer, as this works for 5.1.

Original Post:

Credits to @StephenMuecke, I have found a solution in case someone else struggles with the same

thing. Here is my complete explanation and code below.

Some posts suggested using TextBoxFor instead, but this breaks the Default html5 datepicker.

Basically, our application is a blank MVC 5 application (Visual Studio 2012 with Web Installer) which

poses the problem that the EditorFor control does not accept htmlattributes as parameters. Please

see link(s) below:

MVC5 and Bootstrap3: Html.EditorFor wrong class? How to change?

Asp.net MVC5 with Bootstrap EditorFor size

Is there a solution for using ASP.NET MVC 5 Html.EditorFor() and Bootstrap 3.0?

From the above, MVC 5.1 has this functionality. Anyone using MVC 5 will not be able to add the @class attribute like below:

@Html.EditorFor(a => a[j].InvoiceDateCreated, new { htmlAttributes = new { @class = "InvoiceDateCreated" }})

Instead I used the following workaround mentioned below:

Change id attribute of an html.editorfor helper in MVC4 with Razor

and this is the result:

@Html.EditorFor(a => a[j].InvoiceDateCreated, null, "ID")

If we look in Chrome (thanks again Stephen), this results in the code:

<input class="form-control datecontrol" data-val="true" data-val-date="The field Invoice Created Date must be a date." data-val-required="The Invoice Created Date field is required." id="myID" name="myID" type="date" value="2014/11/19" />

As we can see this renders the control with an id attribute now which we can use in our jQuery, see below:

<script>
    function padLeft(nr, n, str) {
        return Array(n - String(nr).length + 1).join(str || '0') + nr;
    }

    $('#myID').change(function () {
        var row = $(this).closest('tr');
        var date = new Date($(this).val());
        var currentMonth = date.getFullYear().toString() + padLeft(date.getMonth() + 1, 2);
        date.setMonth(date.getMonth() - 1);
        var previousMonth = date.getFullYear().toString() + padLeft(date.getMonth() + 1, 2);
        var select = row.find('#InvoiceNumberPeriod').empty();
        select.append($('<option></option>').val(currentMonth).text(currentMonth));
        select.append($('<option></option>').val(previousMonth).text(previousMonth));
    });
</script>

And it works perfectly now. Not sure if its the most elegant solution or how far it rates up the "best practice" list, but it works.

Community
  • 1
  • 1
fransHbrink
  • 364
  • 5
  • 13
  • 1
    Or you could just use `@Html.TextBoxFor(a => a[j].InvoiceDateCreated, new { @class = "InvoiceDateCreated", @type = "date" })` which renders the browsers data picker. And since your question rendered this in a loop your generating duplicate id's (invalid html ) and name attributes (so it wont post back), so its definitely **not** _best practice_ :-) –  Nov 21 '14 at 00:38
  • Thanks, made this change. Really helpful, @StephenMuecke, appreciate it. – fransHbrink Nov 21 '14 at 08:05