I will describe how we can load data from the server through an AJAX callback and send it back to the server once editing is done.
The technologies involved are:
-
ASP .Net
-
jQuery
-
JSON, with
this utility for client-side Javascript processing and
JSON .Net for server-side
-
ASP .Net Web services
First we need to add a reference to the Newtonsoft.Json dll in our project.
The page that we are using must contain references to the javascript libraries and our custom functions (which are in Content.js):
<script src="JS/JSON.js" type="text/javascript"></script>
<script src="JS/jquery-1.2.5.js" type="text/javascript"></script>
<script src="JS/Content.js" type="text/javascript"></script>
Also, we are adding a div where error messages should appear in case there is a problem, a dropdown which selects the content to be edited based on its title, an editor which edits the current content (I was using a Telerik RadEditor in this case, but it could be anything including a simple textbox or a multiline textbox), and a button for saving the changes:
<div class="text" id="dcmErrorMessages" style="display:none; border-color:Red;">
</div>
<asp:DropDownList ID="ddlContent" runat="server" onchange="EditContent(this.value);">
</asp:DropDownList>
<telerik:RadEditor ID="edtContent" runat="server" Width="100%" Height="600px" EnableAjaxSkinRendering="false"
EnableResize="false" EnableTheming="false" EnableViewState="false">
</telerik:RadEditor>
<asp:Image ID="btnSave" runat="server" CssClass="input-button" ImageUrl="~/images/btn-submit.gif"
AlternateText="Save Changes" onclick="SaveContent()" />
The page is initialised like this:
private IContentRepository _contentRepository = IoC.Resolve<IContentRepository> ( );
protected void Page_Load ( object sender, EventArgs e )
{
if (!Page.IsPostBack)
Bind ( );
}
private void Bind ( )
{
IList<Content> list = _contentRepository.GetAll ( ).ToList<Content> ( );
ddlContent.DataSource = list;
ddlContent.DataTextField = "Title";
ddlContent.DataValueField = "ContentId";
ddlContent.DataBind ( );
edtContent.Content = list[0].Value;
}
IContentRepository provides services for content entities loading and saving. The IoC class offers an interface to the
dependency injection framework. These represent good practices, but they are not essential for our example and they can be replaced with simpler solutions.
When we change the dropdown selection, the EditContent method from Content.js is called:
function EditContent(id)
{
var dataForPost = {
ContentId : id
};
$.ajax({
type: "POST",
url : "/Services/ContentService.asmx/GetContentValue",
contentType: "application/json; charset=utf-8",
dataType : "json",
data : JSON.stringify(dataForPost),
success : function(res){
var objReturned = eval("("+res.d+")");
if(objReturned.errorMessages.length === 0){
SetContent(objReturned.value);
}else{
displayMessages('dcmErrorMessages',objReturned.errorMessages);
}
},
error: function(res){
displayMessage('dcmErrorMessages',res.responseText);
}
});
return false;
}
This calls the GetContentValue method of the ContentService.asmx web service, with the id of the content that needs to be edited. The method loads the content, serialises it into a JSON object, and sends the object back to the client:
[ScriptService]
public class ContentService : System.Web.Services.WebService
{
[WebMethod]
public string GetContentValue(int contentId)
{
IContentRepository _contentRepository = IoC.Resolve<IContentRepository>();
Content c = _contentRepository.GetById(contentId);
var objectToSend = new { value = (c.Value.IsEmpty() ? "" : c.Value), errorMessages =
ValidationMessagesRepository.GetCurrentMessages() };
return JsonConverter.Serialize(objectToSend);
}
}
ValidationMessagesRepository is a general application mechanism we use for storing error messages when they appear, but again any mechanism for obtaining them should be fine.
The Serialise method of the JsonConverter class does a simple call to the JSON .Net serialisation method:
public static string Serialize (
object entity)
{
return JavaScriptConvert.SerializeObject(entity);
}
BaseResponse looks something like this:
public class BaseResponse
{
public string htmlValue { get; set; }
public IList<string> errorMessages { get; set; }
}
After the client receives the response, if no error messages appeared it sets the new content into the editor through SetContent(objReturned.value):
function SetContent(value)
{
var editor = $find("<%=edtContent.ClientID%>");
editor.set_html(value);
}
If error messages have appeared, it displays them with a simple displayMessages function.
function displayMessages(divId,messages){
if(!divId) return ;
if(!messages) return ;
hideMessages(divId);
divId = "#" + divId;
if(!$(divId)) return ;
var ulList = '<ul>';
for(var i = 0 ; i < messages.length; i++){
ulList += '<li>' + messages[i] +'</li>';
}
ulList += '</ul>';
$(divId).prepend(ulList);
$(divId).show();
var divOffset = $(divId).offset().top;
$('html, body').animate({scrollTop:divOffset - 20}, 'fast');
}
Considering that no errors have appeared, we now have the content in our editor. When we finish editing and click on the submit button, the SaveContent function is called:
var ddl = "select[id*=ddlContent]";
function SaveContent()
{
var id = $(ddl)[0].value;
var value = GetContent();
var dataForPost = {
ContentId : id,
value: value
};
$.ajax({
type: "POST",
url : "/Services/ContentService.asmx/SaveContent",
contentType: "application/json; charset=utf-8",
dataType : "json",
data : JSON.stringify(dataForPost),
success : function(res){
var objReturned = eval("("+res.d+")");
if(objReturned.errorMessages.length === 0){
displayMessage('dcmErrorMessages',"Content saved successfully.");
}
else{
displayMessages('dcmErrorMessages',objReturned.errorMessages);
}
},
error: function(res){
displayMessage('dcmErrorMessages',res.responseText);
}
});
return false;
}
function GetContent()
{
var editor = $find("<%=edtContent.ClientID%>");
return editor.get_html();
}
The method gets the content from the editor, prepares an object with the content id and the new content text, and calls the SaveContent method of the ContentService.asmx web service:
[WebMethod]
public string SaveContent(int contentId, string value)
{
IContentRepository _contentRepository = IoC.Resolve<IContentRepository>();
_contentRepository.SaveContent(contentId, value);
var objectToSend = new { value = "", errorMessages = ValidationMessagesRepository.GetCurrentMessages() };
return JsonConverter<BaseResponse>.Serialize(objectToSend);
}
If everything went well a success message will appear in the client browser, otherwise the error message will be shown.
The current example involves a few steps, but with some work be transformed into a small framework which would save us from a lot of the repetitive work.