Kendo UI Scheduler: Delete/Edit/Update only specified events - c#

Im working with the Kendo Scheduler and users can create, delete, update, edit events to my local database. But I'm working with different users on this webapp so I only want those users to be able to edit, delete and update events that they personally created. So user 1 can delete events created by user 1 but can't delete events created by user 2 or 3 etc...
I thought I just modify the model/controller to check the userID of the logged in user against the userID in the db of the event.
public virtual JsonResult Meetings_Destroy([DataSourceRequest] DataSourceRequest request, MeetingViewModel meeting)
{
var userid = System.Convert.ToInt32(Session["userID"]);
if (ModelState.IsValid)
{
if (meeting.UserID== userid)
{
meetingService.Delete(meeting, ModelState);
}
else
{
"cant delete"
}
}
return Json(new[] { meeting });
}
But this doesn't seem to work, when click on delete the event dissapears but after reloading you see that it actually isn't really deleted from the db ... That of course isn't a good solution cause the goal is of course that that user just can't delete that event.
Any idea's?
VIEW
$(function () {
$("#scheduler").kendoScheduler({
date: new Date(Date.now()),
startTime: new Date(2013, 5, 13, 9, 0, 0, 0),
height: 800,
timezone: "Etc/UTC",
dataSource: {
transport: {
read: {
url: "#Url.Action("Meetings_Read", "Home")",
dataType: "json",
contentType: "application/json; charset=utf-8",
type: "POST"
},
update: {
url: "#Url.Action("Meetings_Update", "Home")",
dataType: "json",
contentType: "application/json; charset=utf-8",
type: "POST"
},
create: {
url: "#Url.Action("Meetings_Create", "Home")",
dataType: "json",
contentType: "application/json; charset=utf-8",
type: "POST"
},
destroy: {
url: "#Url.Action("Meetings_Destroy", "Home")",
dataType: "json",
contentType: "application/json; charset=utf-8",
type: "POST"
},
parameterMap: function (options, operation) {
if (operation === "read") {
var scheduler = $("#scheduler").data("kendoScheduler");
var result = {
start: scheduler.view().startDate(),
end: scheduler.view().endDate()
}
return kendo.stringify(result);
}
return kendo.stringify(options);
}
},
error: error_handler,
schema: {
model: {
id: "MeetingID",
fields: {
MeetingID: { type: "number" },
title: { from: "Title", type: "string", defaultValue: "No title", validation: { required: true } },
description: { from: "Description", type: "string" },
start: { from: "Start", type: "date" },
startTimezone: { from: "StartTimezone", type: "string" },
end: { from: "End", type: "date" },
endTimezone: { from: "EndTimezone", type: "string" },
recurrenceRule: { from: "RecurrenceRule", type: "string" },
recurrenceId: { from: "RecurrenceID", type: "number", defaultValue: null },
recurrenceException: { from: "RecurrenceException", type: "string" },
isAllDay: { from: "IsAllDay", type: "boolean" },
Timezone: { type: "string" },
RoomID: { type: "number", defaultValue: null },
Attendees: { type: "object" }
}
}
}
},
});
});
JAVASCRIPT
<script type="text/javascript">
function error_handler(e) {
if (e.errors) {
var scheduler = $("#scheduler").data("kendoScheduler");
scheduler.one("dataBinding", function (e) {
e.preventDefault();
for (var error in e.errors) {
alert("can't delete")
}
})
var message = "Errors:\n";
$.each(e.errors, function (key, value) {
if ('errors' in value) {
$.each(value.errors, function () {
message += this + "\n";
});
}
});
alert(message);
}
}
CONTROLLER
public virtual JsonResult Meetings_Destroy([DataSourceRequest] DataSourceRequest request, MeetingViewModel meeting)
{
if (ModelState.IsValid)
{
if(meeting.UserID == System.Convert.ToInt32(Session["userID"]))
{
meetingService.Delete(meeting, ModelState);
}
else
{
ModelState.AddModelError("","cant delete");
}
}
return Json(new[] { meeting });
}

I have a similar situation with my project where I have users that have limited access to do things on the calendar. I have found that you have to prevent things like Adds, Edits, and Deletes, that a user should not do, you can do this with the JavaScript events. Then, in the JavaScript functions, if a condiation is met (or not), then call the e.preventDefault(); method. Here's the demo on Client Events.
View (HTML 5 snippet)
remove: RemoveMe,
edit: EditMe,
add: AddMe,
View (MVC version snippet)
.Events(events =>
{
events.Add("AddMe").Edit("EditMe").Remove("RemoveMe");
})
JavaScript
function AddMe (e) {
if (SomeValue != SomeOtherValue)
e.preventDefault();
};
function EditMe (e) {
if (SomeValue != SomeOtherValue)
e.preventDefault();
};
function RemoveMe (e) {
if (SomeValue != SomeOtherValue)
e.preventDefault();
};
So, the good news is that this prevents the Calendar from showing updates (whether a Delete occurs or not), but it doesn't prevent the Delete Prompt (aka. "Are you sure you want to delete this event?"). Preventing the actions on the Client side is the way to go and you could extend it with alerts or notifications to the screen (since you would have the condition in the JavaScript function anyways), all to inform the user they can't do something.

ModelState.AddModelError("cant delete");
return Json(ModelState.ToDataSourceResult());
in the view
.Read("Grouping_Horizontal_Read", "Scheduler")
.Create("Grouping_Horizontal_Create", "Scheduler")
.Destroy("Grouping_Horizontal_Destroy", "Scheduler")
.Update("Grouping_Horizontal_Update", "Scheduler")
.Events(events => events.Error("error"))
.Events(events => events.Error("error")) is the trick and now importent the funktion for the error
function error(args) {
if (args.errors) {
var scheduler = $("#scheduler").data("kendoScheduler");
scheduler.one("dataBinding", function (e) {
e.preventDefault(); // cancel scheduler rebind if error occurs
for (var error in args.errors) {
alert(error + " args: " + args.errors[error].errors[0])
}
});
args.sender.cancelChanges();
}
}

It depends on how you are creating the meeting too.
Because to remove a task you just need a unique ID.
Right now it's just being deleted in JSON but not in the backend database, as I guess that meeting.id is not matching to the taskID generated when create event was fired.
I recommend to save taskID in ViewBag, SQL Table or somewhere, when task is created in the scheduler. Then pass this id as the parameter in your Meetings_Destroy method, when remove event is fired.
And [as mentioned in previous answer] use JavaScript remove event to validate logged in user, as e.preventDefault() can be used to restrict Meetings_Destroy method being fired if condition is not met. If debug is reached at Controller Method [Meetings_Destroy] it would remove event from scheduler [Either successfully removed in database or not], i-e from front end, but would bring it back on page refresh.

I have a same situation. I took a different approach to resolve it. In the edit function I compare task id and current userId and than I simply hide those button save, cancel and delete. These button will so only a user who created that.
function(e)
{
var customHide13= $(".k-scheduler-update, .k-scheduler-delete, .k-scheduler-cancel");
if (taskId == userId)
customHide13.show();
else
customHide13.hide();
},

Related

asp.net mvc why ajax response after refreshing the page

this method I'm writing database and folder operations. no problem at all
the response is coming but the page refreshes. The problem disappears when I cancel folder operations.I don't want the page refreshed
$(".btnSave").on("click", function (e) {
var model = {
"ProductName": productName, "BrandCategoryId": BrandCategoryId, "TaxRate": taxRate, "WareHouseId": WareHouseId, "ProductId": ProductId, "PurchasePrice": purchasePrice, "Count": count, "Discount": discount
};
e.preventDefault();
e.stopPropagation();
swal({
title: "Lütfen Bekleyin", text: "Stok kayıt yapılıyor..", showConfirmButton: false, allowOutsideClick: false
});
$.ajax({
method: "POST",
url: "/Warehouseworker/JavaScript/NewStock",
data: model,
dataType: "json",
success: function (response) {
if (response.ModelErrors) {
var errors = response.ModelErrors;
swal.close();
showErrors(errors);
}
if (response.errorMsg) {
swal({ title: "Uyarı", text: "hata var", type: "warning" });
}
if (response.success) {
swal({ title: "Stok kaydı gerçekleştirilmiştir", text: "", type: "success" });
}
}
});
});
Are you using a button and it is in a form?
If so, make the button type="button" and it won't refresh the page. The default behavior is to post the form that it is in.
It it's a link then add the href=javascript:;.

Ajax not hitting controller method

I have the following jquery function which is sending data to a controller:
function bound(e) {
var ele = document.getElementsByClassName("e-grid")[0]
ele.addEventListener('click', function (e) {
if (e.target.classList.contains('e-up')) {
var grid = document.getElementById('FlatGrid').ej2_instances[0]; //Grid Instance
var rowObj = grid.getRowObjectFromUID(ej.base.closest(e.target, '.e-row').getAttribute('data-uid'));
var data = rowObj.data;
//alert(JSON.stringify(data));
var code = data.ClientCode;
$.ajax({
type: "POST",
url: "/Client/ShowClient",
data: { "ClientCode": code }, //First item has latest ID
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data.length !== 0) {
console.log(data);
}
},
error: function (data) {
console.log(data);
}
});
}
});
}
And my controller method:
[HttpPost]
public ActionResult ShowClient(string ClientCode)
{
if (ClientCode == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
*action*
}
However I am getting a 500 (Internal Server Error) error for this. Any idea what I am missing cause my method is not being hit at all.
And I can see that var code does have the correct string value.
Remove commas from the parameter name "ClientCode" and contentType and will be work
$.ajax({
type: "POST",
url: "/Client/ShowClient",
data: { ClientCode: code }, //First item has latest ID
success: function (data) {
if (data.length !== 0) {
console.log(data);
}
},
error: function (data) {
console.log(data);
}
});
The comments have provided you with a combination of suggestions that when put together will give you the desired behavior.
First, you can build the URL in teh view using #Url.Action
url: '#(Url.Action("ShowClient","Client"))',
Next, the object model is not being built correctly
data: { ClientCode: code },
Note the last of quotes around the key.
And finally remove the JSON content type.
Which results to portion of code.
$.ajax({
type: "POST",
url: '#(Url.Action("ShowClient","Client"))',
data: { ClientCode: code },
success: function (data) {
if (data.length !== 0) {
console.log(data);
}
},
error: function (data) {
console.log(data);
}
});
Change the url to this:
url: "../Client/ShowClient"

Issue passing value from textbox to jquery function and onto controller function

I came across an issue with passing values to jQuery where I want to pass the value from a textbox.
Here is the .cshtml:
#Html.TextBoxFor(m => m.OrderNumber, new { #class = "form-control", id = "orderNumber" })
My script:
function maintenance_tapped(e) {
var data = $('#orderNumber').val();
$("#SapPopUp")
.dialog({
width: 600,
height: 420,
model: true,
buttons: {
"OK": function() {
$.ajax({
url: '#Url.Action("RetrieveOrder", "Home")',
data: data,
type: "POST"
}).done(function() {
$(this).dialog("close");
});
},
"Cancel": function() {
$(this).dialog("close");
}
}
});
}
Code in my controller:
[HttpPost]
public async Task<ActionResult> RetrieveOrder(string number)
{
if (_sapHelper == null)
{
_sapHelper = new Helper();
}
return View();
}
My issue is that if I put a breakpoint in the RetrieveOrder function and I check the value of number i get null.
Then if go into Chrome->inspect element->Console and type $('#orderNumber').val(), I get the value I entered into the textbox.
It also seems like its not passing the value into the function.
Try this
dataType :'json',
data: {number: data},
Fixed this thanks to Aleksander Matic and Rob Lang.
Soloution
function maintenance_tapped(e) {
//var data = $('#orderNumber').val(); <------- Remove from here
$("#SapPopUp")
.dialog({
width: 600,
height: 420,
model: true,
buttons: {
"OK": function() {
var data = $('#orderNumber').val(); //<--------- Place here
$.ajax({
url: '#Url.Action("RetrieveOrder", "Home")',
data: data,
type: "POST",
dataType :'json', //<-------- Added
data: {number: data}, //<-------- Added
}).done(function() {
$(this).dialog("close");
});
},
"Cancel": function() {
$(this).dialog("close");
}
}
});
}
You can have the value in a javascript object and match the key to your action's parameter name.
This should work.
$.ajax({
url: '#Url.Action("RetrieveOrder", "Home")',
data: {
number: $('#orderNumber').val()
},
type: "POST"
}).done(function() {
$(this).dialog("close");
});
Also, I see you are returning a viewresult from the action method, but not using that in your ajax method call's done/success event handler. What is the point of doing that?

Auto Complete in Razor MVC.NET [duplicate]

I am using ASP.NET MVC3 with EF Code First. I have not worked previously with jQuery. I would like to add autocomplete capability to a dropdownlist that is bound to my model. The dropdownlist stores the ID, and displays the value.
So, how do I wire up the jQuery UI auto complete widget to display the value as the user is typing but store the ID?
I will need multiple auto complete dropdowns in one view too.
I saw this plugin: http://harvesthq.github.com/chosen/ but I am not sure I want to add more "stuff" to my project. Is there a way to do this with jQuery UI?
Update
I just posted a sample project showcasing the jQueryUI autocomplete on a textbox at GitHub
https://github.com/alfalfastrange/jQueryAutocompleteSample
I use it with regular MVC TextBox like
#Html.TextBoxFor(model => model.MainBranch, new {id = "SearchField", #class = "ui-widget TextField_220" })
Here's a clip of my Ajax call
It initially checks its internal cached for the item being searched for, if not found it fires off the Ajax request to my controller action to retrieve matching records
$("#SearchField").autocomplete({
source: function (request, response) {
var term = request.term;
if (term in entityCache) {
response(entityCache[term]);
return;
}
if (entitiesXhr != null) {
entitiesXhr.abort();
}
$.ajax({
url: actionUrl,
data: request,
type: "GET",
contentType: "application/json; charset=utf-8",
timeout: 10000,
dataType: "json",
success: function (data) {
entityCache[term] = term;
response($.map(data, function (item) {
return { label: item.SchoolName, value: item.EntityName, id: item.EntityID, code: item.EntityCode };
}));
}
});
},
minLength: 3,
select: function (event, result) {
var id = result.item.id;
var code = result.item.code;
getEntityXhr(id, code);
}
});
This isn't all the code but you should be able to see here how the cache is search, and then the Ajax call is made, and then what is done with the response. I have a select section so I can do something with the selected value
This is what I did FWIW.
$(document).ready(function () {
$('#CustomerByName').autocomplete(
{
source: function (request, response) {
$.ajax(
{
url: "/Cases/FindByName", type: "GET", dataType: "json",
data: { searchText: request.term, maxResults: 10 },
contentType: "application/json; charset=utf-8",
success: function (data) {
response($.map(data, function (item) {
return {
label: item.CustomerName,
value: item.CustomerName,
id: item.CustomerID
}
})
);
},
});
},
select: function (event, ui) {
$('#CustomerID').val(ui.item.id);
},
minLength: 1
});
});
Works great!
I have seen this issue many times. You can see some of my code that works this out at cascading dropdown loses select items after post
also this link maybe helpful - http://geekswithblogs.net/ranganh/archive/2011/06/14/cascading-dropdownlist-in-asp.net-mvc-3-using-jquery.aspx

Web API 2.2 OData V4 - Kendo Grid - customize Created IHttpActionResult

I have a Kendo UI Grid wired up to odata CRUD services (Web API 2.2 OData V4). The dataSource configuration looks like the following - the baseUrl is the same for all, just the HTTP verb changes.
var dataSource = new kendo.data.DataSource({
type: "odata",
transport: {
read: {
beforeSend: prepareRequest,
url: baseUrl,
type: "GET",
dataType: "json"
},
update: {
beforeSend: prepareRequest,
url: function (data) {
return baseUrl + "(" + data.CategoryId + ")";
},
type: "PUT",
dataType: "json"
},
create: {
beforeSend: prepareRequest,
url: baseUrl,
type: "POST",
dataType: "json"
},
destroy: {
beforeSend: prepareRequest,
url: function (data) {
return baseUrl + "(" + data.CategoryId + ")";
},
type: "DELETE",
dataType: "json"
},
parameterMap: function (data, operation) {
if (operation == "read") {
var paramMap = kendo.data.transports.odata.parameterMap(data);
delete paramMap.$format;
delete paramMap.$inlinecount;
paramMap.$count = true;
return paramMap;
} else if (operation == "create" || operation == "update") {
delete data["__metadata"];
return JSON.stringify(data);
}
}
},
batch: false,
pageSize: 10,
serverPaging: true,
serverSorting: true,
serverFiltering: true,
sort: { field: "CategoryCode", dir: "asc" },
schema: {
data: function (data) { return data.value; },
total: function (data) { return data['##odata.count']; },
model: {
id: "CategoryId",
fields: {
CategoryId: { editable: false, type: "number" },
CategoryCode: { editable: true, type: "string", required: true, validation: { maxlength: 2 } },
Description: { editable: true, type: "string", required: true, validation: { maxlength: 50 } },
Created: { editable: false, type: "date" },
CreatedBy: { editable: false, type: "string" },
Updated: { editable: false, type: "date" },
UpdatedBy: { editable: false, type: "string" }
}
}
},
error: function (e) {
commonNotification.hide();
commonNotification.show(getRequestError(e), "error");
},
change: function (e) {
commonNotification.hide();
if (e.action == "sync") {
commonNotification.show("#SharedResources.Changes_Saved", "success");
}
},
requestStart: function (e) {
if (e.type == "read" && this.hasChanges()) {
if (confirm("#SharedResources.Dirty_Navigation_Confirmation") == false) {
e.preventDefault();
} else {
this.cancelChanges();
}
}
}
});
Generally speaking, everything works great. The prepareRequest() function is used to apply a custom authorization header as well as setting the Accept header to "application/json;odata=verbose".
When reading, the JSON response looks something like the following - this is why the dataSource.schema.data function returns data.value
{
"#odata.context":"https://localhost:44305/odata/$metadata#Categories",
"#odata.count":2,
"value":[
{
"CategoryId":1,
"CategoryCode":"01",
"Description":"Something",
"Created":"2014-08-01T11:03:30.207Z",
"CreatedBy":"DOMAIN\\User",
"Updated":"2014-09-05T14:36:22.6323744-06:00",
"UpdatedBy":"DOMAIN\\User"
},{
"CategoryId":2,
"CategoryCode":"02",
"Description":"Something Else",
"Created":"2014-08-01T11:03:35.61Z",
"CreatedBy":"DOMAIN\\User",
"Updated":"2014-08-26T16:07:29.198241-06:00",
"UpdatedBy":"DOMAIN\\User"
}
]
}
However, when I create or update an entity, the JSON returned looks like this:
{
"#odata.context":"https://localhost:44305/odata/$metadata#Categories/$entity",
"CategoryId":3,
"CategoryCode":"03",
"Description":"Yet Another",
"Created":"2014-09-06T07:55:52.4933275-06:00",
"CreatedBy":"DOMAIN\\User",
"Updated":"2014-09-06T13:55:34.054Z",
"UpdatedBy":""
}
Because this is not wrapped by "value", the Kendo grid is not updating the data source correctly. The controller that does the POST or PUT, currently returns the entity as follows:
return Created(category);
OR
return Updated(category);
I was able to fix the issue by changing the response to a JsonResult as follows:
return Json(new { value = new[] { category } });
With that, everything works as desired...however, my HTTP response is now 200 (it seems that the JsonResult will always respond with 200). In a perfect world, I could return a 201 on create. Should I just accept that I have it working and live with the 200, or, is there a simple way to respond with a 201 and still format my JSON as necessary? It seems Web API 2 allowed a more customized http response, but my web api 2.2 controller actions are returning IHttpActionResult. I really don't want to create a custom class just to have a special return type and I can't seem to return my anonymous object with Created().
In summary, I'm really leaning toward just living with what I have. However, I would be interested in a way to return my anonymous object with a 201, or, a way to accept the non-"value wrapped" json in my kendo dataSource and have it update the data appropriately.
Update Since Kendo now supports ODATA V4 there is no longer any need for tweaks to make it work.
Changing the data set type from
type: 'odata'
to
type: 'odata-v4'
Should do the trick. Example source code is here
This is what I ended up doing in the end - I made my own "CreatedObject" method that would generate a ResponseMessageResult. This would wrap the object with the anonymous "value" object and serialize to json. Then, I could return this with the desired response code.
public ResponseMessageResult CreatedObject(string location, object createdObject)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
string json = serializer.Serialize(new { value = new[] { createdObject } });
// Create the response and add the 201 response code
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Created);
response.Headers.Add("Location", location);
response.Content = new System.Net.Http.StringContent(json);
// return the result
return ResponseMessage(response);
}
This solved the issue for the Kendo dataSource as the client. However, I didn't like the idea of manipulating the odata response for a particular client. So, instead, I modified the client to handle the normal Web API OData response as follows:
schema: {
data: function (data) {
if (data.value) {
return data.value;
} else {
delete data["##odata.context"];
return data;
}
},
total: function (data) { return data['##odata.count']; },
model: {
etc...
Now, the schema.data() function checks to see if the object(s) are wrapped in the "value" or not before returning the appropriate data. When returning the created object, I had to remove the #odata.context attribute as kendo didn't like it.
I like the solution of manipulating this in the client much better.
One thing I had to add is a non null PK as my odata endpoint would not except null id:
parameterMap: function (data, operation) {
if (operation == "read") {
var paramMap = kendo.data.transports.odata.parameterMap(data);
delete paramMap.$format;
delete paramMap.$inlinecount;
paramMap.$count = true;
return paramMap;
} else if (operation == "create" || operation == "update") {
//delete data["__metadata"];
if (data.Id==null) {
data.Id = 1;
}
return JSON.stringify(data);
}
},

Categories