Oct 30, 2012, 6:11 PM
SOLUTION for Ext.NET v1: Calendar with both edit window and form fully localized and customized!
Hello,
This is not a question. I've asked many questions in the forum and I thought I should give something back to help the community, instead. This solution is scattered in more than a dozen thread posts plus you will not find a CRUD sample like this with proper add/update/delete of events in database. Pay particular attention at how the ID (EventId) of the newly added database record is returned to the client so as to update the eventstore.
The following sample implements the requirements below:
The Event class:
Default.aspx
Screenshot shows the execution stack when user clicks the Add button in the EventEditWindow
Notice how the client side record is updated with the EventId value
Hope you like it. I've spent two weeks to make things work. Enjoy.
Cheers,
Dimitris
This is not a question. I've asked many questions in the forum and I thought I should give something back to help the community, instead. This solution is scattered in more than a dozen thread posts plus you will not find a CRUD sample like this with proper add/update/delete of events in database. Pay particular attention at how the ID (EventId) of the newly added database record is returned to the client so as to update the eventstore.
The following sample implements the requirements below:
- CSS for the calendar groups
- Overriden Event object with custom field
- Fully localized event edit window
- Fully localized and customized event edit form
- CRUD operations PER EVENT RECORD using web service to access the database (i.e, the database is updated as soon as a new record is added / updated / deleted). NO SUBMIT BUTTON NEEDED!
The Event class:
- CalendarId
- EndDate
- EventId (Primary Key)
- IsAllDay
- IsNew
- Location
- Notes
- Reminder
- StartDate
- Title
- Url
- MyCustomField1 (extra custom field)
Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>
<%@ Register assembly="Ext.Net" namespace="Ext.Net" tagprefix="ext" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Ext.Net Full Custom and Local Calendar</title>
<style type="text/css">
.ext-color-4,
.ext-ie .ext-color-4-ad,
.ext-opera .ext-color-4-ad {
color: #663300;
}
.ext-cal-day-col .ext-color-4,
.ext-dd-drag-proxy .ext-color-4,
.ext-color-4-ad,
.ext-color-4-ad .ext-cal-evm,
.ext-color-4 .ext-cal-picker-icon,
.ext-color-4-x dl,
.ext-color-4-x .ext-cal-evb {
background: #663300;
}
.ext-color-4-x .ext-cal-evb,
.ext-color-4-x dl {
border-color: #7C3939;
}
.ext-color-5,
.ext-ie .ext-color-5-ad,
.ext-opera .ext-color-5-ad {
color: #660066;
}
.ext-cal-day-col .ext-color-5,
.ext-dd-drag-proxy .ext-color-5,
.ext-color-5-ad,
.ext-color-5-ad .ext-cal-evm,
.ext-color-5 .ext-cal-picker-icon,
.ext-color-5-x dl,
.ext-color-5-x .ext-cal-evb {
background: #660066;
}
.ext-color-5-x .ext-cal-evb,
.ext-color-5-x dl {
border-color: #660066;
}
.ext-color-6,
.ext-ie .ext-color-6-ad,
.ext-opera .ext-color-6-ad {
color: #7F0000;
}
.ext-cal-day-col .ext-color-6,
.ext-dd-drag-proxy .ext-color-6,
.ext-color-6-ad,
.ext-color-6-ad .ext-cal-evm,
.ext-color-6 .ext-cal-picker-icon,
.ext-color-6-x dl,
.ext-color-6-x .ext-cal-evb {
background: #7F0000;
}
.ext-color-6-x .ext-cal-evb,
.ext-color-6-x dl {
border-color: #7C3939;
}
.ext-color-7,
.ext-ie .ext-color-7-ad,
.ext-opera .ext-color-7-ad {
color: #000000;
}
.ext-cal-day-col .ext-color-7,
.ext-dd-drag-proxy .ext-color-7,
.ext-color-7-ad,
.ext-color-7-ad .ext-cal-evm,
.ext-color-7 .ext-cal-picker-icon,
.ext-color-7-x dl,
.ext-color-7-x .ext-cal-evb {
background: #000000;
}
.ext-color-7-x .ext-cal-evb,
.ext-color-7-x dl {
border-color: #7C3939;
}
</style>
<ext:ResourcePlaceHolder ID="ResourcePlaceHolder1" runat="server" />
<script src="MyEventRecord.js" type="text/javascript"></script>
<script type="text/javascript">
var CompanyX = {
getCalendar: function () { return CompanyX.CalendarPanel1; },
getStore: function () { return CompanyX.EventStore1; },
getWindow: function () { return CompanyX.EventEditWindow1; },
customWindow: function () {
// event edit window localization
var win = this.getWindow();
var form = win.formPanel;
var titleItem = form.get('title');
dateRangeItem = form.get('date-range');
calendarItem = form.get('calendar');
detailsLnk = win.fbar.get(0);
saveBtn = win.fbar.get(2);
deleteBtn = win.fbar.get(3);
cancelBtn = win.fbar.get(4);
titleItem.fieldLabel = 'Περιγραφή'; // Title
dateRangeItem.fieldLabel = 'Από'; // When
dateRangeItem.toText = 'έως'; // to
dateRangeItem.allDayText = 'Ολοήμερο'; // Is all day
calendarItem.fieldLabel = 'Λόγος'; // Calendar group
detailsLnk.text = '<a href="#" id="tblink">Περισσότερα στοιχεία...</a>'; // Edit Details
saveBtn.text = 'Αποθήκευση'; // Save button
deleteBtn.text = 'Διαγραφή'; // Delete button
cancelBtn.text = 'Άκυρο'; // Cancel button
// date-range date format
win.get(0).get(1).on('render', function () {
this.startDate.format = 'd/m/Y';
this.endDate.format = 'd/m/Y';
});
},
updateTitle: function (startDt, endDt) {
var msg = '';
if (startDt.clearTime().getTime() == endDt.clearTime().getTime()) {
msg = startDt.format('F j, Y');
}
else if (startDt.getFullYear() == endDt.getFullYear()) {
if (startDt.getMonth() == endDt.getMonth()) {
msg = startDt.format('F j') + ' - ' + endDt.format('j, Y');
}
else {
msg = startDt.format('F j') + ' - ' + endDt.format('F j, Y');
}
}
else {
msg = startDt.format('F j, Y') + ' - ' + endDt.format('F j, Y');
}
this.Panel1.setTitle(msg);
},
setStartDate: function (picker, date) {
this.getCalendar().setStartDate(date);
},
rangeSelect: function (cal, dates, callback) {
this.record.show(cal, dates);
this.getWindow().on('hide', callback, cal, { single: true });
},
dayClick: function (cal, dt, allDay, el) {
this.record.show.call(this, cal, {
StartDate: dt, IsAllDay: allDay
}, el);
},
record: {
add: function (win, rec) {
win.hide();
rec.data.IsNew = false;
// save the event to database and update the event store
CompanyX.record.save(rec);
CompanyX.ShowMsg('Event ' + rec.data.Title + ' was added');
},
update: function (win, rec) {
win.hide();
rec.commit();
// update the event in the database
CompanyX.record.upd(rec);
CompanyX.ShowMsg('Event ' + rec.data.Title + ' was updated');
},
remove: function (win, rec) {
CompanyX.record.del(rec);
// remove the event from the database
this.getStore().remove(rec);
win.hide();
CompanyX.ShowMsg('Event ' + rec.data.Title + ' was deleted');
},
editFormAdd: function (win, rec) {
// called from the eventeditform EventAdd
// save the event to database and update the event store
CompanyX.record.save(rec);
},
editFormUpdate: function (win, rec) {
// called from the eventeditform EventUpdate
// update the event in the database
CompanyX.record.upd(rec);
},
editFormDelete: function (win, rec) {
// called from the eventeditform EventDelete
// delete the event from database
CompanyX.record.del(rec);
},
edit: function (win, rec) {
win.hide();
CompanyX.getCalendar().showEditForm(rec);
},
resize: function (cal, rec) {
rec.commit();
CompanyX.ShowMsg('Event ' + rec.data.Title + ' was updated');
},
move: function (cal, rec) {
rec.commit();
CompanyX.ShowMsg('Event ' + rec.data.Title + ' was moved to ' + rec.data.StartDate.format('F jS' + (rec.data.IsAllDay ? '' : ' \\a\\t g:i a')));
},
show: function (cal, rec, el) {
CompanyX.getWindow().show(rec, el);
},
save: function (rec) {
// save the event data to database
Ext.net.DirectMethod.request({
url: "RemoteService.asmx/Save",
json: true,
cleanRequest: true,
params: {
e: rec.data
},
success: function (result) {
// update record with db-assigned id
rec.data.EventId = result.EventId;
// add to store
CompanyX.getStore().add(rec);
}
});
},
upd: function (rec) {
// update event data in the database
// event data row is found in the database using EventId
Ext.net.DirectEvent.request({
url: "RemoteService.asmx/Update",
json: true,
cleanRequest: true,
extraParams: {
e: rec.data
}
});
},
del: function (rec) {
Ext.net.DirectEvent.request({
url: "RemoteService.asmx/Delete",
json: true,
cleanRequest: true,
extraParams: { e: rec.data }
});
}
}
};
var calendar_beforeRender = function (calendar, store) {
// localize event edit form
var form = calendar.get(calendar.id + '-edit');
form.titleTextAdd = 'Νέο Ραντεβού'; // form title when add
form.titleTextEdit = 'Επεξεργασία Ραντεβού'; // form title when edit
// left col
var lcol = form.get('left-col');
lcol.get(0).fieldLabel = 'Περιγραφή'; // Title
dateRangeItem = lcol.get(1);
dateRangeItem.fieldLabel = 'Από'; // When
dateRangeItem.toText = 'έως'; // to
dateRangeItem.allDayText = 'ολοήμερο'; // Is all day
// date-range date format
dateRangeItem.on('render', function () {
this.startDate.format = 'd/m/Y';
this.endDate.format = 'd/m/Y';
});
lcol.get(2).fieldLabel = 'Λόγος'; // Calendar group
lcol.get(3).fieldLabel = 'Υπενθύμιση'; // Reminder
// right col
var rcol = form.get('right-col');
rcol.get(0).fieldLabel = 'Σημειώσεις'; // Notes
rcol.get(1).fieldLabel = 'Τόπος'; // Location
rcol.get(2).fieldLabel = 'Άλλο'; // Url
// footer
form.fbar.get(0).text = 'Αποθήκευση'; // Save
form.fbar.get(1).text = 'Διαγραφή'; // Delete
form.fbar.get(2).text = 'Άκυρο'; // Cancel
// add new fields
// here a data-bound combo is added to the form
// The "customStore" store is decleared in markup and databound in code behind
var cmb = new Ext.form.ComboBox({
id: 'ComboBox1',
fieldLabel: 'CustomField',
store: store,
dataIndex: 'MyCustomField1',
mode: 'local',
displayField: 'lastname',
valueField: 'id',
anchor: '90%',
triggerAction: 'all',
selectOnFocus: true,
typeAhead: true
});
lcol.add(cmb);
};
</script>
</head>
<body>
<form id="Form1" runat="server">
<ext:ResourceManager ID="ResourceManager1" runat="server" Namespace="CompanyX" Locale="el-GR" />
<ext:Store ID="CustomStore" runat="server">
<Reader>
<ext:JsonReader IDProperty="id">
<Fields>
<ext:RecordField Name="id" Type="Int" />
<ext:RecordField Name="lastname" />
</Fields>
</ext:JsonReader>
</Reader>
</ext:Store>
<ext:Viewport ID="Viewport1" runat="server" Layout="fit">
<Items>
<ext:CalendarPanel ID="CalendarPanel1" runat="server" Height="500">
<EventStore ID="EventStore1"
runat="server"
IgnoreExtraFields="false">
</EventStore>
<GroupStore ID="GroupStore1" runat="server">
<Groups>
<ext:Group CalendarId="1" Title="Group 1" />
<ext:Group CalendarId="2" Title="Group 2" />
<ext:Group CalendarId="3" Title="Group 3" />
<ext:Group CalendarId="4" Title="Group 4" />
<ext:Group CalendarId="5" Title="Group 5" />
<ext:Group CalendarId="6" Title="Group 6" />
<ext:Group CalendarId="7" Title="Group 7" />
</Groups>
</GroupStore>
<Listeners>
<EventClick Fn="CompanyX.record.show" Scope="CompanyX" />
<DayClick Fn="CompanyX.dayClick" Scope="CompanyX" />
<RangeSelect Fn="CompanyX.rangeSelect" Scope="CompanyX" />
<EventMove Fn="CompanyX.record.move" Scope="CompanyX" />
<EventResize Fn="CompanyX.record.resize" Scope="CompanyX" />
<BeforeRender Handler="calendar_beforeRender(this, #{CustomStore});" />
<EventAdd Fn="CompanyX.record.editFormAdd" Scope="CompanyX" />
<EventUpdate Fn="CompanyX.record.editFormUpdate" Scope="CompanyX" />
<EventDelete Fn="CompanyX.record.editFormDelete" Scope="CompanyX" />
</Listeners>
</ext:CalendarPanel>
</Items>
</ext:Viewport>
<ext:EventEditWindow
ID="EventEditWindow1"
runat="server"
Hidden="true"
GroupStoreID="GroupStore1"
TitleTextAdd="New Event"
TitleTextEdit="Edit Event">
<Listeners>
<EventAdd Fn="CompanyX.record.add" Scope="CompanyX" />
<EventUpdate Fn="CompanyX.record.update" Scope="CompanyX" />
<EditDetails Fn="CompanyX.record.edit" Scope="CompanyX" />
<EventDelete Fn="CompanyX.record.remove" Scope="CompanyX" />
<Render Fn="CompanyX.customWindow" Scope="CompanyX" />
</Listeners>
</ext:EventEditWindow>
</form>
</body>
</html>
MyEventRecord.jsExt.calendar.EventMappings = {
EventId: {
name: 'EventId',
mapping: 'id',
type: 'int'
},
CalendarId: {
name: 'CalendarId',
mapping: 'cid',
type: 'int'
},
Title: {
name: 'Title',
mapping: 'title',
type: 'string'
},
StartDate: {
name: 'StartDate',
mapping: 'start',
type: 'date',
dateFormat: 'c'
},
EndDate: {
name: 'EndDate',
mapping: 'end',
type: 'date',
dateFormat: 'c'
},
Location: {
name: 'Location',
mapping: 'loc',
type: 'string'
},
Notes: {
name: 'Notes',
mapping: 'notes',
type: 'string'
},
Url: {
name: 'Url',
mapping: 'url',
type: 'string'
},
IsAllDay: {
name: 'IsAllDay',
mapping: 'ad',
type: 'boolean'
},
Reminder: {
name: 'Reminder',
mapping: 'rem',
type: 'string'
},
IsNew: {
name: 'IsNew',
mapping: 'n',
type: 'boolean'
},
MyCustomField1: {
name: 'MyCustomField1',
mapping: 'MyCustomField1',
type: 'int'
}
};
(function () {
var M = Ext.calendar.EventMappings;
Ext.calendar.EventRecord = Ext.data.Record.create([
M.EventId,
M.CalendarId,
M.Title,
M.StartDate,
M.EndDate,
M.Location,
M.Notes,
M.Url,
M.IsAllDay,
M.Reminder,
M.IsNew,
M.MyCustomField1
]);
Ext.calendar.EventRecord.reconfigure = function () {
Ext.calendar.EventRecord = Ext.data.Record.create([
M.EventId,
M.CalendarId,
M.Title,
M.StartDate,
M.EndDate,
M.Location,
M.Notes,
M.Url,
M.IsAllDay,
M.Reminder,
M.IsNew,
M.MyCustomField1
]);
};
})();
Default.aspx.csusing Ext.Net;
namespace WebApplication1
{
public partial class _Default : System.Web.UI.Page
{
[DirectMethod(Namespace = "CompanyX")]
public void ShowMsg(string msg)
{
X.Msg.Notify("Message", msg).Show();
}
protected void Page_Load(object sender, EventArgs e)
{
// config event store with standard plus custom fields
this.CalendarPanel1.EventStore.AddStandardFields();
// custom fields must also be declared in MyEventRecord.js
this.CalendarPanel1.EventStore.Reader[0].Fields.Add(new RecordField("MyCustomField1", RecordFieldType.Int));
if (!X.IsAjaxRequest)
{
// load combo with sample custom data
List<CustomField> data = new List<CustomField> {
new CustomField
{
id = 1,
lastname = "value 1"
},
new CustomField
{
id = 2,
lastname = "value 2"
}
};
CustomStore.DataSource = data;
CustomStore.DataBind();
// bind events to event store
RemoteService service = new RemoteService();
CalendarPanel1.EventStore.DataSource = service.GetEvents(null, null);
CalendarPanel1.EventStore.DataBind();
}
}
}
}
RemoteService.asmxnamespace WebApplication1
{
/// <summary>
/// Summary description for RemoteService
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
[System.Web.Script.Services.ScriptService]
public class RemoteService : System.Web.Services.WebService
{
[WebMethod]
public IEnumerable<Event> GetEvents(DateTime? start, DateTime? end)
{
DateTime now = DateTime.Now;
return new List<Event>
{
new Event
{
EventId = 1001,
CalendarId = 1,
Title = "Vacation",
StartDate = now.AddDays(-20).AddHours(10),
EndDate = now.AddDays(-10).AddHours(15),
IsAllDay = false,
Notes = "Have fun",
MyCustomField1 = 1
}
};
}
[WebMethod]
public Event Save(Event e)
{
// save to database
// ...
// update EventId with real db-assigned id
// e.g e.EventId = 1;
return e;
}
[WebMethod]
public void Update(Event e)
{
// update database
// ...
}
[WebMethod]
public void Delete(Event e)
{
// delete from database
// ...
}
}
}
Event.csnamespace WebApplication1
{
public class Event
{
public int EventId { get; set; }
public int CalendarId { get; set; }
public DateTime EndDate { get; set; }
public DateTime StartDate { get; set; }
public bool IsAllDay { get; set; }
public bool IsNew { get; set; }
public string Location { get; set; }
public string Notes { get; set; }
public string Reminder { get; set; }
public string Title { get; set; }
public string Url { get; set; }
public int MyCustomField1 { get; set; }
}
}
CustomField.csnamespace WebApplication1
{
public class CustomField
{
public int id { get; set; }
public string lastname { get; set; }
}
}
Screenshots show the localized and customized edit window and form.Screenshot shows the execution stack when user clicks the Add button in the EventEditWindow
Notice how the client side record is updated with the EventId value
Hope you like it. I've spent two weeks to make things work. Enjoy.
Cheers,
Dimitris
Last edited by Daniil; Dec 07, 2012 at 11:25 AM.
Reason: Added Ext.NET v1 in the title