A Web Diary System For jQuery And C# MVC

In this article you will learn about a Web Diary System for jQuery and C# MVC.

Using FullCalendar, jQuery, and MVC to give your web-app ready-to-go appointment diary functionality.

Introduction

This article describes using the very fine open source JQuery plugin “FullCalendar” by Adam Shaw to develop an appointment booking system. I will demonstrate numerous ways to use the plugin and show how it can integrate with a SQL Backend with Entity-Framework. The aim of this article is give you almost everything you need, that you can tweak immediately, to bring diary/appointment functionality to your MVC application. This is a complete walk-through including setup of a linked EF database in SQL. I am going to use the Twitter Bootstrap framework to help speed things along. I am also going into a lot of detail to help those wishing to use the plugin to get started as fast as possible. The attached project is written in C# MVC 4.

project

Background

I was recently looking for a good reliable web based diary plugin that would allow me to give solid appointment management functionality. Apart from some commercial offerings the most suitable solution open source plugin I found was FullCalendar. Get it here. The demo scenario will have the following functionality:

  • For each day, manage appointments
  • In month view see overview summary information on all appointment events

Gotchas

Some of the gotchas included date format (this plugin uses a Unix timestamp), and having to work with multiple plugin data sources as I wanted different kinds of data queried depending on the view of the calendar being seen (day, week, month..). I also had some issues with trying to get the events rendering exactly how I wanted - the DOH! moment came when I realised I was referencing some CSS in the wrong order/sequence. For drag/drop functionality the plugin depends on jQueryUI to be included, and finally, in order to stop the plugin triggering before I wanted it to, I had to control the flow of loading of data carefully.

Setting things up

To get started:

  1. Create a new C# MVC project and clear out the default view templates.
  2. Download FullCalendar from here.
  3. Download Twitter Bootstrap (I am using the stable version 2.3.2 until v3 is out of RC).
  4. For both libraries, unpack and place the CSS file in your /Content folder, and the JS scripts in the /Scripts folder.
  5. In your MVC project, add the bootstrap files to your script bundles. This is in BundleConfig.cs and is located in your App_Start folder.

    solution

    code

    You will also note I have added the JQuery-UI JS file to my bundle - this is needed to support drag/drop..

  6. We will do our work in the default index.cshml page, so in there, place a DIV to hold the plugin:

    Hide
    Copy Code
    1. <div id='calendar' style="width:65%"></div>  
    note that the inline style is simply to force the width for screenshots inside the CodeProject publishing size guidelines!) 

7. Finally, to prove its at least loading and rendering correctly we will add some basic JS when the document is ready and loaded:

code

The initialization settings tell the plugin what to show in the header, the default view (in this case "agenda day" ..familiar to most), and to have a default time slot of 15 minutes.

When we run the project, as expected, the plugin appears:

project

OK, so let's get busy!

Using the code

Rather than use dummy client-side data, I wanted to show how we might store and manipulate data in a working environment, so I put together an SQL database to store data and linked to it using Entity Framework. (The SQL source is attached to this article).

The SQL table is called "AppointmentDiary" and contains these fields:

AppointmentDiary

The ID is an auto-increment, Title get displayed within the Calendar control as an event, SomeImportantKey represents a referential link to some other data table, DateTimescheduled is the diary date/time, Appointment length is an integer that represents the number of minutes the appointment lasts, and finally StatusENUM is an integer that links to an ENUM value to say if the appointment is an enquiry, a booking, confirmed etc.

We add a new Entity Data Model to the project and point it at our new database and table. This creates an EDMX file that in our case we call "Diary" and reference as "DiaryContainer".

As this example is date based, I didn't want to pre-populate it with data, because any events I stored would be out of date the day after publishing! For this reason I put together a quick method that initialises the database. The method is called by pressing a button in the browser that sends an Ajax call to the application (lets minimise full server round-trips when we can!).

The button starts life as a link:

Hide Copy Code

  1. <a href="#" id="btnInit"class="btnbtn-secondary">Initialise database!</a>  

but through the wonder of Bootstrap, the addition of the class "btn" turns the link into a button, and "btn-secondary" gives it a gray colour.

button

The script to call the initialise code is basic enough:

Hide Copy Code
  1. $('#btnInit').click(function () {  
  2.  $.ajax({  
  3. type: 'POST',  
  4. url: "/Home/Init",  
  5. success: function (response) {  
  6. if (response == 'True') {  
  7.  $('#calendar').fullCalendar('refetchEvents');  
  8. alert('Database populated! ');  
  9.  }  
  10. else {  
  11. alert('Error, could not populate database!');  
  12.  }  
  13.  }  
  14.  });   
  15. });   

Server-side, we have a controller "/Home/Init" that calls a method in a shared file called Utils called "InitialiseDiary". The objective of this method is to generate a series of test diary appointments that centre around the current date. It creates some items for the current date, and others before and after the date.

Hide Shrink  Copy Code

  1. publicstaticbool InitialiseDiary() {   
  2. // init connection to database  
  3.  DiaryContainer ent = new DiaryContainer();  
  4. try  
  5.  {   
  6. for(int i= 0; i<30; i++){  
  7.  AppointmentDiary item = new AppointmentDiary();  
  8. // record ID is auto generated  
  9.  item.Title = "Appt: " + i.ToString();  
  10.  item.SomeImportantKey = i;  
  11.  item.StatusENUM = GetRandomValue(0,3); // random is exclusive - we have three status enums  
  12. if (i <= 5) // create ten appointments for todays date  
  13.  {  
  14.  item.DateTimeScheduled = GetRandomAppointmentTime(falsetrue);  
  15.  }  
  16. else { // rest of appointments on previous and future dates  
  17. if (i % 2 == 0)  
  18.  item.DateTimeScheduled = GetRandomAppointmentTime(truefalse);  
  19. // flip/flop between date ahead of today and behind today  
  20. else item.DateTimeScheduled = GetRandomAppointmentTime(falsefalse);  
  21.  }  
  22.  item.AppointmentLength = GetRandomValue(1,5) * 15;  
  23. // appoiment length always less than an hour in this demo in blocks of fifteen minutes  
  24.    
  25.  ent.AppointmentDiary.Add(item);  
  26.  ent.SaveChanges();  
  27.  }  
  28.  }  
  29. catch (Exception)  
  30.  {  
  31. returnfalse;  
  32.  }  
  33.    
  34. return ent.AppointmentDiary.Count() >0;   
  35. }   

This method calls two other supporting methods, one which generates a random number, the other that generates a random date/time.

Hide Copy Code

  1. ///<summary>  
  2. /// sends back a date/time +/- 15 days from todays date  
  3. ///</summary>  
  4. publicstatic DateTime GetRandomAppointmentTime(bool GoBackwards, bool Today) {  
  5.  Random rnd = new Random(Environment.TickCount); // set a new random seed each call  
  6. var baseDate = DateTime.Today;  
  7. if (Today)  
  8. returnnew DateTime(baseDate.Year, baseDate.Month,   
  9.  baseDate.Day, rnd.Next(9, 18), rnd.Next(1, 6)*5, 0);  
  10. else  
  11.  {  
  12. int rndDays = rnd.Next(1, 15);  
  13. if (GoBackwards)  
  14.  rndDays = rndDays * -1; // make into negative number  
  15. returnnew DateTime(baseDate.Year, baseDate.Month,   
  16.  baseDate.Day, rnd.Next(9, 18), rnd.Next(1, 6)*5, 0).AddDays(rndDays);   
  17.  }  
  18. }   

Now we have that, we can generate sample data by running the application and clicking the button (made delicious and wonderful by the magic of the twittering bootstrap....) however, before we click that object of wonder, lets put in a controller and method to send our sample data back to the plugin....

Full calendar can create diary "events" to render in a number of different ways. One of the more common is to send in data as a JSON list.

"Events" need at a minimum the following information:

  • ID: a unique identifier for the diary item
  • Title: Some text to render on the screen
  • Start: Starting date/time of the event
  • End: Ending date/time of the event

You can also send some other information back such as the color you would like the event to be on the screen, a CSS class-name if you wish to render the event in a particular way. You can also send back any other information you might need to handle client-side, for example, key fields to related data tables etc.

To hook into the data table, I created a model called DiaryEvent, and gave it some fields that map to the Entity model.

Hide Copy Code

  1. publicclass DiaryEvent  
  2. {  
  3. publicint ID;  
  4. publicstring Title;  
  5. publicint SomeImportantKeyID;   
  6. publicstring StartDateString;  
  7. publicstring EndDateString;  
  8. publicstring StatusString;  
  9. publicstring StatusColor;  
  10. publicstring ClassName;  
  11.  ...  
  12. }  

In addition I added some methods to extract information and save it back. The first we are interested in takes as parameters, a start and end date, and returns a list of DiaryEvents:

Hide Shrink Copy Code

  1. publicstatic List<DiaryEvent> LoadAllAppointmentsInDateRange(double start, double end)  
  2. {  
  3. var fromDate = ConvertFromUnixTimestamp(start);  
  4. var toDate = ConvertFromUnixTimestamp(end);  
  5. using (DiaryContainer ent = new DiaryContainer())  
  6.  {  
  7. var rslt = ent.AppointmentDiary.Where(s => s.DateTimeScheduled >=   
  8.  fromDate && System.Data.Objects.EntityFunctions.AddMinutes(  
  9.  s.DateTimeScheduled, s.AppointmentLength) <= toDate);  
  10.  List<DiaryEvent> result = new List<DiaryEvent>();  
  11. foreach (var item in rslt)  
  12.  {  
  13.  DiaryEvent rec = new DiaryEvent();  
  14.  rec.ID = item.ID;  
  15.  rec.SomeImportantKeyID = item.SomeImportantKey;  
  16.  rec.StartDateString = item.DateTimeScheduled.ToString("s");  
  17. // "s" is a preset format that outputs as: "2009-02-27T12:12:22"  
  18.    
  19.  rec.EndDateString = item.DateTimeScheduled.AddMinutes(item.AppointmentLength).ToString("s");  
  20. // field AppointmentLength is in minutes  
  21.    
  22.  rec.Title = item.Title + " - " + item.AppointmentLength.ToString() + " mins";  
  23.  rec.StatusString = Enums.GetName<AppointmentStatus>((AppointmentStatus)item.StatusENUM);  
  24.  rec.StatusColor = Enums.GetEnumDescription<AppointmentStatus>(rec.StatusString);  
  25. string ColorCode = rec.StatusColor.Substring(0, rec.StatusColor.IndexOf(":"));  
  26.  rec.ClassName = rec.StatusColor.Substring(rec.StatusColor.IndexOf(":")+1,   
  27.  rec.StatusColor.Length - ColorCode.Length-1);  
  28.  rec.StatusColor = ColorCode;   
  29.  result.Add(rec);  
  30.  }  
  31. return result;  
  32.  }  
  33. }  

 

The code is quite simple:

  1. We connect to the database using Entity Framework, run a LINQ query that extracts all appointment events between the start and end dates (using "EntityFunctions.AddMinutes" to create an end-date on the fly from the StartDateTime + AppointmentLength (in minutes).

  2. For each event returned, we create a DiaryEvent item and add the data table record information to it ready to send back.

Some things to note - first, FullCalander deals in dates in a UNIX format, therefore we had to run a conversion for this before querying. Second, I stored the color attribute of the "Staus" of the event in the "DESCRIPTION ANNOTATION" of the StatusENUM, and then used a method to extract the description, color code etc. The color code etc includes a css "class name" we will use later in the article to make the event item look a wee bit fancier.

CSS:

Hide Copy Code

  1. .ENQUIRY {  
  2.    
  3. background-color: #FF9933;  
  4. border-color: #C0C0C0;  
  5. color: White;  
  6. background-position: 1px 1px;  
  7. background-repeat: no-repeat;  
  8. background-image: url('Bubble.png');  
  9. padding-left: 50px;  
  10. }  
  11.    
  12. .BOOKED {  
  13. background-color: #33CCFF;  
  14. border-color: #C0C0C0;  
  15. color: White;  
  16. background-position: 1px 1px;  
  17. background-repeat: no-repeat;  
  18. background-image: url('ok.png');  
  19. padding-left: 50px;  
  20. ... etc...  

 

StatusENUM:

code

Method to extract CSS class/color from Description attribute of StatusENUM:

Hide Copy Code

  1. publicstaticstring GetEnumDescription<T>(stringvalue)  
  2. {  
  3.  Type type = typeof(T);  
  4. var name = Enum.GetNames(type).Where(f => f.Equals(value,   
  5.  StringComparison.CurrentCultureIgnoreCase)).Select(d => d).FirstOrDefault();  
  6. if (name == null)  
  7.  {  
  8. returnstring.Empty;  
  9.  }  
  10. var field = type.GetField(name);  
  11. var customAttribute = field.GetCustomAttributes(typeof(DescriptionAttribute), false);  
  12. return customAttribute.Length >0 ? ((DescriptionAttribute)customAttribute[0]).Description : name;  
  13. }  

 

Ok, almost there, we need to take the list and put that into JSON format to send back to the Plugin ... this is done in a controller.

Hide Copy Code

  1. public JsonResult GetDiaryEvents(double start, double end)  
  2. {  
  3. var ApptListForDate = DiaryEvent.LoadAllAppointmentsInDateRange(start, end);  
  4. var eventList = from e in ApptListForDate  
  5. selectnew  
  6.  {  
  7.  id = e.ID,  
  8.  title = e.Title,  
  9.  start = e.StartDateString,  
  10.  end = e.EndDateString,  
  11.  color = e.StatusColor,  
  12.  someKey = e.SomeImportantKeyID,  
  13.  allDay = false  
  14.  };  
  15. var rows = eventList.ToArray();  
  16. return Json(rows, JsonRequestBehavior.AllowGet);   
  17. }   

 

Finally (!) ... lets go back to our INDEX page, and add one line that tells the FullCalendar plugin where to go to get its JSON data...

data

Now when we run the application, we can click our INIT button, which will populate the database, and see the data coming through...

data


weehoo
...

project

You recall earlier I talked about color, and ClassName - the plugin allows us to pass in a CSS class name, and it will use this to help render the event. In the CSS I showed earlier I added an icon image to the CSS decoration to make the event look visually better. To make this pass through, we add in the ClassName string...

code

And this is how it renders....

project

Thats superb, now lets look at client-side user functionality - how can the user interact with our diary?

Typically, one would expect to be able to select an event and get information on it, edit it, move it around, resize it, etc. Lets see how to could make that happen.

First, lets examine the information we sent along with the event. We can do this by adding adding a callback function when we initialise the plugin:

Hide Copy Code

  1. eventClick: function (calEvent, jsEvent, view) {  
  2. alert('You clicked on event id: ' + calEvent.id  
  3.  + "\nSpecial ID: " + calEvent.someKey  
  4.  + "\nAnd the title is: " + calEvent.title);   

The important param here is the first one, calEvent - from it we can access any of the data we sent through when we sent back the JSON records.

Heres how it looks:

message

Using this information you can hook in and use it to popup an edit form, redirect to a details window, etc. - your code chops are your magic wand.

To move an event around the plugin, we hook eventDrop.

Hide Copy Code

  1. eventDrop: function (event, dayDelta, minuteDelta, allDay, revertFunc) {  
  2. if (confirm("Confirm move?")) {  
  3. UpdateEvent(event.id, event.start);  
  4.  }  
  5. else {  
  6. revertFunc();  
  7.  }   
  8. }  

 

The parameters dayDelta and minuteDelta are useful, as they tell us the amount of days or minutes the event has been moved by. We could use this to adjust the backend database, but there is another method I used.

I decided in this case to take the event object and use its information. When we create the event initially, it is set with the start/end date and time we give it. When the item is moved/dropped, that date and time change, however, the ID and the extra information we saved along with the event do not. Therefore my strategy in this case was to take the ID and the new start and end time and use these to update the database.

FullCalendar provides a "revertFunc" method that resets the event move to its previous state if the user decides not to confirm the event move.

On event move, I call a local script function "UpdateEvent". This takes the relevant data, and sends it by ajax to a controller back on the server:

Hide Copy Code

  1. functionUpdateEvent(EventID, EventStart, EventEnd) {  
  2. vardataRow = {  
  3. 'ID': EventID,  
  4. 'NewEventStart': EventStart,  
  5. 'NewEventEnd': EventEnd  
  6.  }  
  7.  $.ajax({  
  8. type: 'POST',  
  9. url: "/Home/UpdateEvent",  
  10. dataType: "json",  
  11. contentType: "application/json",  
  12. data: JSON.stringify(dataRow)  
  13.  });   
  14. }  

 

Controller:

Hide Copy Code

  1. publicvoid UpdateEvent(int id, string NewEventStart, string NewEventEnd)  
  2. {  
  3.  DiaryEvent.UpdateDiaryEvent(id, NewEventStart, NewEventEnd);   
  4. }   

 

Method called from controller:

Hide Copy Code

  1. publicstaticvoid UpdateDiaryEvent(int id, string NewEventStart, string NewEventEnd)   
  2. {  
  3. // EventStart comes ISO 8601 format, eg: "2000-01-10T10:00:00Z" - need to convert to DateTime  
  4. using (DiaryContainer ent = new DiaryContainer()) {  
  5. var rec = ent.AppointmentDiary.FirstOrDefault(s => s.ID == id);  
  6. if (rec != null)  
  7.  {  
  8.  DateTime DateTimeStart = DateTime.Parse(NewEventStart, null,   
  9.  DateTimeStyles.RoundtripKind).ToLocalTime(); // and convert offset to localtime  
  10.  rec.DateTimeScheduled = DateTimeStart;  
  11. if (!String.IsNullOrEmpty(NewEventEnd)) {   
  12.  TimeSpan span = DateTime.Parse(NewEventEnd, null,   
  13.  DateTimeStyles.RoundtripKind).ToLocalTime() - DateTimeStart;  
  14.  rec.AppointmentLength = Convert.ToInt32(span.TotalMinutes);  
  15.  }  
  16.  ent.SaveChanges();  
  17.  }  
  18.  }  
  19. }   

 

The important thing to note here is that the date format is sent in IS8601 format so we need to convert it. I also use a Timespan to calculate the new appointment length if any.

Event resizing, and updating the database appointment length server-side is done in a similar fashion. First, hook the event:

Hide Copy Code

  1. eventResize: function (event, dayDelta, minuteDelta, revertFunc) {  
  2. if (confirm("Confirm change appointment length?")) {  
  3. UpdateEvent(event.id, event.start, event.end);  
  4.  }  
  5. else {  
  6. revertFunc();  
  7.  }   
  8. },   

 

You will notice that I have used the same controller and method to update the database - all in an effort to reuse code wherever possible! What happens is that if the update method sees that a new end date/time has been sent in, it assumes a RESIZE has happened and adjusts the "AppointmentLength" value accordingly, else it just updates the new start/date time.

Hide Copy Code

  1. if (!String.IsNullOrEmpty(NewEventEnd)) {   
  2.  TimeSpan span = DateTime.Parse(NewEventEnd, null, DateTimeStyles.RoundtripKind).ToLocalTime() - DateTimeStart;  
  3.  rec.AppointmentLength = Convert.ToInt32(span.TotalMinutes);   
  4. }  

Now, thats great, and we have a lovely warm fuzzy feeling about making it happen, until we turn to look at the MONTH view....

view

Oh dear .... it renders every single event, and makes our calendar look rather messy... it would be better, for month view, if the control just showed a summary of appointment/diary events for each date. There is an elegant solution to this issue in "data sources".

Full Calendar can store an array of event SOURCES. All we need to do is hook into the "viewRender" event, query what kind of view is active (day, week, month..), and based on this, change the source and tell the plugin to refresh the data.

First lets go back to our index.cshtml plugin init section and remove the reference to the data path as we will replace it with something else...

view

To facilitate the new functionality we require, I created two variables to hold the URL path of each view.

Hide Copy Code

  1. varsourceSummaryView = { url: '/Home/GetDiarySummary/' };  
  2. varsourceFullView = { url: '/Home/GetDiaryEvents/' };   

 

What we are aiming to do is hook into the Click/change event of the view button group...

view

The way to do this is to drop in code that examines when the user clicks the buttons to change the view - this is "viewRender". Two parameters are passed, the first is "view" and that tells us what was just clicked:

render

So depending on which view was clicked, we remove any data sources from the source array (by name), remove any events from the plugin, and finally assign one of the string variables we created earlier as the new data source which then gets immediately loaded.

As our summary view holds slightly different information, we need to create a different method to extract data server side. This is almost the same as our previous query, with the exception that we need to extend our LINQ query to use GROUP and COUNT. Our objective is to group by date, and get a count of diary events for each day. Lets have a quick look at that method.

Hide Shrink  Copy Code

  1. publicstatic List<DiaryEvent> LoadAppointmentSummaryInDateRange(double start, double end)  
  2. {  
  3. var fromDate = ConvertFromUnixTimestamp(start);  
  4. var toDate = ConvertFromUnixTimestamp(end);  
  5. using (DiaryContainer ent = new DiaryContainer())  
  6.  {  
  7. var rslt = ent.AppointmentDiary.Where(  
  8.  s => s.DateTimeScheduled >= fromDate &&  
  9.  System.Data.Objects.EntityFunctions.AddMinutes(s.DateTimeScheduled, s.AppointmentLength) <= toDate)  
  10.  .GroupBy(s => System.Data.Objects.EntityFunctions.TruncateTime(s.DateTimeScheduled))  
  11.  .Select(x =>new { DateTimeScheduled = x.Key, Count = x.Count() });  
  12.  List<DiaryEvent> result = new List<DiaryEvent>();  
  13. int i = 0;  
  14. foreach (var item in rslt)  
  15.  {  
  16.  DiaryEvent rec = new DiaryEvent();  
  17.  rec.ID = i; //we dont link this back to anything as its a group summary  
  18. // but the fullcalendar needs unique IDs for each event item (unless its a repeating event)  
  19.    
  20.  rec.SomeImportantKeyID = -1;   
  21. string StringDate = string.Format("{0:yyyy-MM-dd}", item.DateTimeScheduled);  
  22.  rec.StartDateString = StringDate + "T00:00:00"//ISO 8601 format  
  23.  rec.EndDateString = StringDate +"T23:59:59";  
  24.  rec.Title = "Booked: " + item.Count.ToString();  
  25.  result.Add(rec);  
  26.  i++;  
  27.  }  
  28. return result;  
  29.  }  
  30. }  

 

As before, we need to convert the incoming unix format date. Next, because we are grouping by date, but we are storing a dateTIME, we need to remove the time part from the query - for this, we use theEntityFunctions.TruncateTime method, and chain the result of this into a sub select which gives us back a key field of date and a value of count.

Note that as this is summary information we don't have a key-id-field to link back to, but this is a problem for FullCalendar as it needs a unique ID for keeping track of events, so we assign an arbitrary one in this case of a simple counting variable ("i"). The other thing we need to do is because we stripped out the time part of the result to enable the grouping, we need to put it back in using ISO format.

Once the code is implemented, everything looks far better!

look

The last thing we are going to do is add a new event when a blank space on the calendar is clicked.

We will construct a quick bootstrap modal popup to capture an event title, date/time and appointment length:

Hide Copy Code

  1. <divid="popupEventForm"class="modal hide"style="display: none;">  
  2. <divclass="modal-header"><h3>Add new event</h3></div>  
  3. <divclass="modal-body">  
  4. <formid="EventForm"class="well">  
  5. <inputtype="hidden"id="eventID">  
  6. <label>Event title</label>  
  7. <inputtype="text"id="eventTitle"placeholder="Title here"><br/>  
  8. <label>Scheduled date</label>  
  9. <inputtype="text"id="eventDate"><br/>  
  10. <label>Scheduled time</label>  
  11. <inputtype="text"id="eventTime"><br/>  
  12. <label>Appointment length (minutes)</label>  
  13. <inputtype="text"id="eventDuration"placeholder="15"><br/>  
  14. </form>  
  15. </div>  
  16. <divclass="modal-footer">  
  17. <buttontype="button"id="btnPopupCancel"data-dismiss="modal"class="btn">Cancel</button>  
  18. <buttontype="button"id="btnPopupSave"data-dismiss="modal"class="btnbtn-primary">Save event</button>  
  19. </div>  
  20. </div>  

 

Next we will add a hook to our friendly plugin to show this modal popup when a day slot is clicked.

Hide Copy Code

  1. dayClick: function (date, allDay, jsEvent, view) {  
  2.  $('#eventTitle').val("");  
  3.  $('#eventDate').val($.fullCalendar.formatDate(date, 'dd/MM/yyyy'));  
  4.  $('#eventTime').val($.fullCalendar.formatDate(date, 'HH:mm'));  
  5. ShowEventPopup(date);   
  6. },  

 

A small script method initialises the popup form, first clearing any input values hanging around and then setting focus to the first input box.

Hide Copy Code

event

Finally we have script attached to the "save event" button to send the data back to the server via Ajax,

Hide Copy Code
  1. $('#btnPopupSave').click(function () {  
  2.     $('#popupEventForm').hide();  
  3. vardataRow = {  
  4. 'Title':$('#eventTitle').val(),  
  5. 'NewEventDate': $('#eventDate').val(),  
  6. 'NewEventTime': $('#eventTime').val(),  
  7. 'NewEventDuration': $('#eventDuration').val()  
  8.     }  
  9. ClearPopupFormValues();  
  10.     $.ajax({  
  11. type: 'POST',  
  12. url: "/Home/SaveEvent",  
  13. data: dataRow,  
  14. success: function (response) {  
  15. if (response == 'True') {  
  16.                 $('#calendar').fullCalendar('refetchEvents');  
  17. alert('New event saved!');  
  18.             }  
  19. else {  
  20. alert('Error, could not save event!');  
  21.             }  
  22.         }  
  23.     });  
  24. });    

If the server saves the record successfully it sends back 'True' and we tell the plugin to refetch its events.

The very last thing I want to address is double loading of data. When we are changing data sources using the viewRendercallback, what happens is that on initial page load, the viewRender gets kicked off twice. To fix this, I added in a variable var CalLoading = true; at the top of the main script before the plugin is initialised, examine that when rendering the view, and reset it after the document is ready.

code

So, that's it! ... There is enough detail in this article to allow you, with very little effort, to include quite useful diary functionality into your MVC application. Full source code is included with the article.

Read more articles on MVC: