I am working on an MVC EF site that has a SQL DB with two tables:
Entries, Members.
Moderators of the site can assign tasks to Members which will be logged in Entries.
I'm trying to determine a way to prioritize the Members list so that the Member with the least amount of tasks (as an always-incrementing number) will always OrderBy from the top so that the Moderators can assign based on who has taken the least amount of tasks.
I explored doing this in the DB itself: I can create a View that will check the Entries against the names on Members and provide a total of how many tasks they have been assigned. However; EF/dbContext doesn't work with SQL Views apparently.
I have an EntryController which feeds the Entries Table into the View and a MemberController which simply lists the members. My next thought is to simply call an action in the MemberController that increments a specific Member's count number when called. I'm not sure if this is the best way to do this or how I can even call both the Input-Submit POST from the Html.BeginForm and the Increment function at the same time.
The BeginForm is on a strongly-typed view of the EntryController so I'm also not sure how I could pass a Member back to the member controller so instead I made the function to identify the member based on a string grab .first and increment:
public void incrementCount(string member)
{
Member[] members = null;
members[0] = (repository.Members.Where(p => p.Name == member).First());
members[0].Count = members[0].Count + 1;
}
I am completely lost at this point so any suggestions would be greatly appreciated.
Sounds to me that you're lost on all 3: EF, ASP.NET MVC and Linq.
IMHO you should send a HTTP POST or a HTTP PATCH (depends on both context & interpretation) request with jQuery (or other) back to your server/controller which will then increment the task count for the member.
Your controller would, then, have a method Increment(int memberId) with a route like [Route("/lorem/ipsum/members/increment/{id}")] so you can access it from client-side.
This is an interesting approach rather than a full form post to the server because you can send only relevant data (only the memberId instead of the whole form and receive nothing, instead of a whole new page), reducing server load and increaing performance.
Now, for the link, you can either use the regular syntax as Brendan posted ot the Linq bellow, which should be fine too:
var memberId = repository.Entries
.GroupBy(_entry => _entry.MemberId)
.Select(_group => new { Id: _group.Key, Count: _group.Count() })
.OrderBy(_group => _group.Count)
.First().Id;
Some samples to illustrate possible approaches
First, this will be our controller/method on ASP.NET MVC:
[RoutePrefix("Lorem/Ipsum/Task")]
public class LoremController : Controller
{
[Route("Increment"), HttpPost]
public JsonResult Increment(int id)
{
try
{
// Increment your task count!
return Json(new { Success = true, ErrorMessage = "" });
}
catch(Exception err)
{
return Json(new { Success = false, ErrorMessage = err.Message });
}
}
}
Sample with jQuery
<div class="blabla">
<button id="btnIncrement" class="btn btn-primary" type="button" data-member-id="1">
Increment!
</button>
</div>
<script>
$("#btnIncrement").on("click", function() {
var _this = $(this);
$.ajax({
url: "/lorem/ipsum/task/increment",
data: { MemberId: _this.data("member-id") },
method: "POST",
success: function(json) {
if (json.Success) alert ('Success!');
else alert(json.ErrorMessage);
},
error: function () { alert("error!); }
});
});
</script>
Sample using a simple form (non-jquery/javascript)
<form action="/lorem/ipsum/task/increment" method="POST">
<input type="hidden" name="MemberId" value="1" />
<button type="submit" class="btn btn-primary">Increment!</button>
</form>
Sample with Angular.JS
<div ng-app>
<button ng-controller="lalaController" ng-click="increment(1)" class="btn btn-primary" type="button>
Increment!
</button>
</div>
<script>
angular.controller("lalaController", ["$scope", "$http", function($scope, $http) {
$scope.increment = function(id) {
$http({ url: "/lorem/ipsum/task/increment", data: { MemberId: id }, method: POST })
.success(function(json) {
if (json.Success) alert ("Success!");
else alert(json.ErrorMessage);
})
.error(function() { alert("Error!"); });
}
}]);
</script>
Sure sounds much more interesting on going with pure HTML.
But now, try to disable that button unless it's a prime number. It will be impossible with pure HTML. Also, try to validate your input or parse the response (let's say: udpate your grid on the page with a JSON array of new values)...
You can do this via Linq:
var query = from e in repository.Entries
group e by e.MemberId into g
select new
{
name = g.Key,
count = g.Count()
};
Will return a list of member id's along with the number of Entry records they have.
Related
I am trying to fetch a set of data from controller to view, I have the following;
Index.cshtml
<input type="button" value="submit" class="btn btn-default" id="btnGet" name="btnGet" onclick="displayCalendar()" />
Upon pressing the button, the displayCalendar() function calls the jQuery fullCalendar which populates all the events from the controller.
The problem is that I have a set of integers that I also want to be fetched in that AJAX call or separately.
Controller:
return Json(totalList, JsonRequestBehavior.AllowGet);
Now what have I tried?
I tried using ViewBag, ViewData, TempData. like this;
TempData["countWeekDays"] = countWeekDays;
TempData["countPresents"] = countPresents;
TempData["countAbsence"] = countAbsence;
TempData["countLates"] = countLates;
TempData["countFines"] = countFines;
and then display them in the same Index.cshtml but it did not work. I, then made a list of integers and tried to bind that list along with the already list of objects that was passed in the json like;
List<int> statsView = new List<int>();
statsView.Add(countWeekDays);
statsView.Add(countPresents);
statsView.Add(countAbsence);
statsView.Add(countLates);
statsView.Add(countFines);
IEnumerable<object> completeObjects = totalList.Cast<object>().Concat(statsView.Cast<object>());
return Json(completeObjects, JsonRequestBehavior.AllowGet);
When I tried the above method, I could see the list in the console debug in the browser but the problem is that it doesnt populate the calendar then. if I exclude the list statsView then the calendar is properly populated with the events.
Maybe a function within the displayCalendar() that is when called, it fetches the list from the controller and simply displays it in the view?
UPDATED:
<div id='calendar'></div>
#section scripts{
<script>
function h() {
id = $('#EmpId').val();
s = $("#startDate").val();
e = $("#endDate").val();
$.post("/Controller/method",
{
ENum: id,
StartDate: s,
EndDate: e
},
function (response) {
callCalendar(response);
}
);
}
function displayCalendar(e)
{
$("#calendar").fullCalendar('removeEvents');
$("#calendar").fullCalendar('addEventSource', e);
$("#calendar").fullCalendar('rerenderEvents');
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaDay'
},
}
});
//stats here
}
</script>
<script>
$(document).ready(function () {
displayCalendar('');
});
</script>
}
If your current code is working to correctly populate the calendar and if you want to send some additional data (list of integers), you may return a new annonymous object with 2 properties, one for your original data and second for the additional data.
List<int> statsView = new List<int>();
statsView.Add(countWeekDays);
statsView.Add(countPresents);
statsView.Add(countAbsence);
statsView.Add(countLates);
statsView.Add(countFines);
return Json(new { list = totalList, statusList = statsView },
JsonRequestBehavior.AllowGet);
Now make sure you update the client side code to use the list property of the response object for your callCalendar method call. To get the statusView collections, use the statusList property of the response object.
var d = { ENum: id, StartDate: s, EndDate: e };
$.post("/Controller/method",d ,function (response) {
console.log(response.statusList);
callCalendar(response.list);
});
If you want to display the items in response.statusList in the page, you may loop through them and add it to any existing DOM element. You can use $.each for iterating the array.
$.post("/Controller/method",d ,function (response) {
console.log(response.statusList);
var items="";
$.each(response.statusList,function(a,b){
items+='<p>'+b+'</p>';
});
$("#StatusList").html(items);
callCalendar(response.list);
});
Assuming you have a div with id set to StatusList in your page
<div id="StatusList"></div>
So basically, i'm building a calendar where people can add their meetings by clicking on cells and sending data through an Ajax request.
This part already works well, but I cannot figure out how to pass the right parameters to the Index() View.
EDIT:
My problem now lies in the Ajax function returning the result to the page. I am trying to do in jQuery Ajax the equivalent of the MVC Ajax option UpdateTargetId but I cannot figure an adequate way of doing so.
My current Ajax function:
function AjaxAddRDV() {
$.ajax({
url: "/Agenda/AddRDV",
async: "true",
type: "GET",
data: { "jour": rdvjour.val(), "mois": rdvmois.val(), "annee": rdvannee.val(), "text": rdvtext.val() },
success: function (result) {
$(".cal-container").html(result); // This gives me the right result but my click events do not work anymore
/*if (data && data.successUrl) {
window.location.href = data.successUrl;
}*/ this reloads the page, which I am trying to avoid
}
});
}
Note: My HTML takes data from the ViewModel and creates a table using the Month and Year passed to the model, given the right parameters it never fails.
public ActionResult Index(int annee = 0, int mois = 0)
{
DateTime date = DateTime.Now;
if (annee == 0) annee = date.Year;
if (mois == 0) mois = date.Month;
AgendaViewModel Agendata = CreateAgendaViewModel(annee, mois);
if (dal.GetFirstDay(annee, mois) == 0)
{
return View("Error");
}
return View(Agendata);
}
This method gets fired when Ajax adds a new appointment:
// I edited it from the original question
public ActionResult AddRDV(int jour, int mois, int annee, string text)
{
AgendaViewModel Agendata = new AgendaViewModel();
if (!string.IsNullOrWhiteSpace(text))
{
dal.SetRdv(jour, mois, annee, text);
Agendata = CreateAgendaViewModel(annee, mois);
}
return PartialView("Calendrier", Agendata);
}
The HTML i'm trying to update is the following:
#model DASIWelcome.ViewModels.AgendaViewModel
(...)
<body>
<div id="wrapper">
<div id="prevmonth" class="switchmonth">←</div>
<div class="cal-container">
#Html.Partial("Calendrier",Model)
</div>
<div id="nextmonth" class="switchmonth">→</div>
</div>
</body>
I'm trying to get the #Html.Partial updated every time I send the Ajax request.
The final objective is to have an Ajax function that refreshes the calendar for the current month by default, and that will return the same calendar with custom parameters if the month is changed, if a new appointment is added, etc.
I've started C# MVC quite recently, but I've spent the whole day trying to work around this, and I cannot find a solution that is not very ugly and unrecommended...
Any help will be greatly appreciated!
Things I've tried:
Building a second Index Method with parameters - Loading fails saying is it ambiguous
Finding out which method called index in the first place to fork between actions - Didn't work + was considered an ugly solution everywhere I looked
Crying
The previous issue has been resolved, here are the points where I was struggling and that did not allow me to go forward:
The click function was not working on dynamically created elements, thus only worked once per page loading.
At first I could not get the jQuery Ajax function to refresh my table the same way that the MVC Ajax UpdateTargetId function would.
Here is how I fixed the click event:
$("#wrapper").on("click", ".week td", function(){
What I struggled to understand with the on() function was that the first selector $("#wrapper") has to be static, and the second selector ".week td" is the selector for the dynamically created elements.
Since I was trying to fix the wrong thing, I did manage to find the equivalent between MVC Ajax UpdateTargetId and jQuery Ajax:
jQuery:
$.ajax({
url: "/Agenda/AddRDV",
async: "true",
type: "GET",
data: { "jour": rdvjour.val(), "mois": rdvmois.val(), "annee": rdvannee.val(), "text": rdvtext.val() },
success: function (result) {
$("#cal-container").html(result);
}
});
MVC Ajax:
#using (Ajax.BeginForm("AddRDV", new AjaxOptions {
HttpMethod = "GET",
//InsertionMode = InsertionMode.Replace,
UpdateTargetId = "cal-container",
OnComplete = "rdvdialog.dialog('close')"
}))
{
<label for="rdvannee">Année</label>
<label for="rdvmois">Mois</label>
<label for="rdvjour">Jour</label>
<br />
<input type="text" name="annee" id="rdvannee" maxlength="4" size="4" />
<input type="text" name="mois" id="rdvmois" maxlength="2" size="2" />
<input type="text" name="jour" id="rdvjour" maxlength="2" size="2" />
<br />
<br />
<label for="rdvtext">Titre</label><br />
<input type="text" name="text" id="text" maxlength="250" />
<input type="submit" name="Envoyer" value="Envoyer" />
}
Special thanks to dlght who pointed me in the right direction and offered me a few useful alternatives!
Your "ChangeMonth" method is the same as the Index method, but with parameters.Just reload the Index, when the user change the calendar.I would even suggest you to make a function that takes care of your ViewModel creation so you can reuse the model in other ActionMethods if you need it:
public AgendaViewModel CreateAgendaViewModel(int annee, int mois)
{
AgendaViewModel agendata = new AgendaViewModel();
agendata.ListRDV = dal.GetListRDV(mois, annee); //Loads the list of appointments for the chosen month
agendata.FirstDay = dal.GetFirstDay(annee, mois);
agendata.MonthLength = dal.GetMonthLength(annee, mois);
agendata.MonthName = dal.GetMonthName(mois) + " " + annee.ToString();
agendata.Mois = mois;
agendata.Annee = annee;
return agendata;
}
public ActionResult Index(int annee = 0, int mois = 0)
{
DateTime date = DateTime.Now;
if(annee == 0) annee = date.Year; // if not set initialize with default year
if(mois == 0) mois = date.Month; // if not set initialize with default month
AgendaViewModel Agendata = CreateAgendaViewModel(annee, mois);
if (dal.GetFirstDay(annee, mois) == 0)
{
return View("Error");
}
return View(Agendata);
}
In the add method now you can redirect to the index with the selected month and year:
public ActionResult AddRDV(int jour, int mois, int annee, string text)
{
if (!string.IsNullOrWhiteSpace(text))
{
dal.SetRdv(jour, mois, annee, text);
}
return RedirectToAction("Index", new { annee = annee, mois = mois });
}
Another note - please write your code in english. Your code would be so much more readable if you name your parameters and variables in English not only for you, but for the other people that might look at your code.
Hope this helps ;]
EDIT:
There are two ways to redirect from ajax function:
1) If there is no other functionality on the page you can reload the whole page by passing Json result with the URL route in the success:
public JsonResult AddRDV(int jour, int mois, int annee, string text)
{
if (!string.IsNullOrWhiteSpace(text))
{
dal.SetRdv(jour, mois, annee, text);
}
var successUrl = Url.Action("Index", new { annee = annee, mois = mois });
return Json(new {successUrl = successUrl});
}
Then in your ajax you do redirect to the index page:
$.ajax({ ...,
success: function(data) {
if(data && data.successUrl){
window.location.href = data.successUrl;
}
}
});
2) You can change your logic so that the Calendar is created in partial view loaded inside the page. Then you can reload just the partial view by calling ajax that reloads the wrapper of the calendar - you can check an example of this here
It looks like you try to accomplish several things from the same method (Index).
Option 1
You receive a FormCollection object in your Index when data is posted. So, whatever is posted can be retrieved in the form collection. Then, your code can understand what data is received and dispatch correctly to the right method.
The code would look like this:
[HttpPost]
public ActionResult Index(FormCollection formCollection)
{
var myValue = formCollection["myvalue"];
}
Option 2
The second option is to use and call different methods but use the same View(). The output of the view would look like this:
public ActionResult OtherAction(string param1)
{
var model = this.GenerateModel(param1);
// Use the index View (assuming the controller is called Calendar
return View("Calendar/Index", model );
}
The problem with the second option (that you tried in your question) is that it does too many things. I think the date should be a parameter. You should also build your model in a separate function. That way, it will be easier to send the right parameters to your "model generator" and get the expected output.
Crying won't help fixing your code... but it may ease your pain. ;-)
In View, the user will check 1 or more names from what's displayed ...
<form name=chkAttend method=post onsubmit='return validate(this)'>
<div>
#if (Model.evModel.Participants != null)
{
foreach (var fi in Model.evModel.Participants)
{
<div>
#if (#fi.AttendedFlag != true)
{
<input type="checkbox" id="c_#fi.EnrollmentId" name="MyCheckboxes" value="#fi.EnrollmentId" />
<label for="c_#fi.EnrollmentId" aria-multiselectable="True"></label>
<span></span> #fi.EnrollmentId #fi.ParticipantName
}
</div>
}
}
<input type=submit value="Confirm Attendance">
</div>
</form>
After selecting names, call the function to identify which names checked. The checked names only need to be passed to the controller. The problem - I'm getting the error message: Error 49 The name 'id' does not exist in the current context
function validate(form) {
var id = ""
for (var i = 0; i < document.chkAttend.MyCheckboxes.length; i++)
{
if (document.chkAttend.MyCheckboxes[i].checked)
id += document.chkAttend.MyCheckboxes[i].value + ","
}
if (id == "")
{
alert("Place a check mark next to event's Participant")
}
else
{
#Html.Action("ConfirmAttendance", "Admin", new { eventid = #Model.evModel.Id, enrollid =id })
}
return false;
}
How do I pass ONLY the checked items as parameter for the function in my controller?
You cannot inject a server-side partial view into your code like that. As it stands (if you got around the id variable reference problem) you would literally inject a partial view inline into your Javascript which will create invalid Javascript!
Instead treat the JS as client-side only and feed information into the page that the Javascript will need in a way that is easy to access. You can inject global variables via injected JS code, but I strongly advise against that practice.
Instead your MVC page could inject the controller action URL and the event id as data- attributes like this:
<form name="chkAttend" method="post"
data-action="#Url.Content("~/Admin/ConfirmAttendance")"
data-eventid="#Model.evModel.Id">
Using Url.Content will ensure the URL is always site relative, even when hosted as a virtual application.
Your client-side code can then pick up the values from the form, add the selected id and call the server action using Ajax:
e.g.
function validate(form) {
var action = $(form).data('action');
var eventId = $(form).data('eventid');
The fun begins now because you need to call the server from the client-side, e.g. via Ajax, with your selected option and do something with the result to change the display.
$.ajax({
// Use constructed URL (action + event id + id)
url: action + "?eventid=" + eventId + "&enrollid=" + id,
type: "PUT"
success: function (data){
// Do something with the server result
}
});
You do not show your controller's ConfirmAttendance action so I cannot even guess what you are returning. Make sure it is suitable (could be as simple as a Boolean result or as complex as a partial view to insert into the page).
I'm using MVC 5, C# and I'm trying to build a search filter that will filter through upon each key stroke. It works as so, but the textbox erases after submitting. Now this is probably not the best approach to it either. Is there a way to make so when it posts it doesn't erase the textbox, or better yet, is there a better alternative?
#using (Html.BeginForm("Index", "Directory", FormMethod.Post, new { id = "form" }))
{
<p>
Search Employee: <input type="text" name="userName" onkeyup="filterTerm(this.value);" />
</p>
}
<script>
function filterTerm(value) {
$("#form").submit();
event.preventDefault();
}
</script>
I agree with the comments on your question. Posting on every key stroke would be a frustrating user experience.
So, two answers, use ajax to perform the search (which will then keep the value since the whole page will not post) or have a submit button and name the input the same as the controller action parameter.
Controller code (used with your existing code):
public class DirectoryController : Controller
{
[HttpPost()]
public ActionResult Index(string userName)
{
// make the input argument match your form field name.
//TODO: Your search code here.
// Assuming you have a partial view for displaying results.
return PartialView("SearchResults");
}
}
View Code (to replace your code with Ajax):
<p>
Search Employee:#Html.TextBox("userName", new { id = "user-name-input" })
</p>
<div id="results-output"></div>
<script type="text/javascript">
$("#user-name-input").change(function(e) {
$.ajax({
url: '#Url.Action("Index", "Directory")'
, cache: false
, type: "post"
, data: {userName: $("#user-name-input").val() }
}).done(function (responseData) {
if (responseData != undefined && responseData != null) {
// make sure we got data back
$("#results-output").html(responseData);
} else {
console.log("No data returned.");
alert("An error occurred while loading data.");
} // end if/else
}).fail(function (data) {
console.log(data);
alert("BOOOM");
});
}
</script>
A better way is to ditch your Html.BeginForm (unless you actually need it for something else) and use a pure ajax method of getting the data.
So your modified html would be:
<p>
Search Employee:
<input type="text" name="userName" onkeyup="filterTerm(this.value);" />
</p>
<script>
function filterTerm(value) {
$.ajax({
url: '#Url.Action("Index", "Directory")',
data: {
searchTerm: value
},
cache: false,
success: function (result) {
//do something with your result,
//like replacing DOM elements
}
});
}
</script>
You also need to change the action that ajax will be calling (and I have no idea why you are calling the "Index" action).
public ActionResult Index(string searchTerm)
{
//lookup and do your filtering
//you have 2 options, return a partial view with your model
return PartialView(model);
//or return Json
return Json(model);
}
The best thing about this ajax is there is no posting and it's async, so you don't have to worry about losing your data.
I have a list of Payees in a drop down box on my form. I would like to populate a different drop down, based on the selected item of the Payee drop down, without post backs and all that.
So, I created a method in my controller that does the work:
private JsonResult GetCategories(int payeeId)
{
List<CategoryDto> cats = Services.CategoryServices.GetCategoriesByPayeeId(payeeId);
List<SelectListItem> items = new List<SelectListItem>();
foreach(var cat in cats)
{
items.Add(new SelectListItem {Text = cat.Description, Value = cat.CategoryId.ToString()});
}
return Json(items);
}
Now, I am unsure what to add to my view to get this to work.
At the moment, all I have is this:
<% using (Html.BeginForm())
{%>
<p>
<%=Html.DropDownList("SelectedAccountId", Model.Accounts, "Select One..", null) %>
</p>
<p>
<%=Html.DropDownList("SelectedPayeeId", Model.Payees, "Select One...", null) %>
</p>
<input type="submit" value="Save" />
<%
}%>
they populate fine... so when the user selects the SelectedPayeeId drop down, it should then populate a new (Yet to be created?) drop down which holds categories, based on the SelectedPayeeId.
So, I think I need to create a JQuery function (Never done JQuery.. so not even sure where it goes) which monitors the Payee drop down for an onChange event? And then call the method I created above. Does this sound right, and if so, can you guide me in how to achieve this?
Your reasoning so far is totally sound. First you are going to want to include the jquery library in your View / Master. You can download a copy of jquery from http://jquery.com/. Add the file to you project and include a <script src="/path/to/jquery.js"> to the <head> of your document. You are going to want to add another dropdown to your View (and probably another property to your model). We'll call this 'SelectedCategoryId:'
<%=Html.DropDownList("SelectedCategoryId", null, "Select One...", new { style = "display:none;"}) %>
We've set the style of this Drop Down to not be visible initially because there is nothing to select inside of it. We'll show it later after we generate some content for it. Now, somewhere on your page you will want to include a <script> block that will look something like this:
$(document).ready(function() { $('#SelectedPayeeId').change(function() {
$.ajax({
type: 'POST',
url: urlToYourControllerAction,
data: { payeeId: $(this).val() },
success: function(data) {
var markup = '';
for (var x = 0; x < data.length; x++ ) {
markup += '<option value="' + data[x].Value + '">'+data[x].Text+'</option>';
}
$('#SelectedCategoryId').html(markup).show();
}
}); }); });
This code binds the anonymous function written above to the DOM element with the ID of 'SelectedPayeeId' (in this case your dropdown). The function performs an AJAX call to the url of your method. When it receives the results of the request (your JSON you returned) we iterate over the array and build a string of the html we want to inject into our document. Finally we insert the html into the 'SelectedCategoryId' element, and change the style of the element so it is visible to the user.
Note that I haven't run this code, but it should be (almost) what you need. jQuery's documentation is available at http://docs.jquery.com/Main_Page and the functions I used above are referenced here:
.ready()
.change()
jQuery.ajax()
.html()
.show()
You'd need to make the GetCategories as a public method as it would correspond to an action handler in your controller.
Your jquery code can look like:
<script type="text/javascript">
$(function() {
$('#SelectedPayeeId').change(function() {
$.get('<%= Url.Action("GetCategories", "YourControllerName") %>',
{payeeId: $(this).val()},
function(data) {
populateSelectWith($("#Category"), data);
});
});
//Place populateSelectWith method here
});
</script>
The populateSelectWith can fill your dropdown with data like:
function populateSelectWith($select, data) {
$select.html('');
$select.append($('<option></option>').val('').html("MYDEFAULT VALUE"));
for (var index = 0; index < data.length; index++) {
var option = data[index];
$select.append($('<option></option>').html(option));
}
}
I have not tested this code, but I am hoping it runs okay.
You can find syntax for the jquery ajax get here
Since you are not posting any data to the server, you can might as well decorate your controller action with a [HttpGet] attribute