I have a Kendo Grid on the start page of a .NET Core 2.0 MVC web app. The data source is populated via ViewData on page load:
public IActionResult Index()
{
var allForms = formsDB.FormHeader.Select(x => x);
ViewData["AllForms"] = allForms;
<...>
return View();
}
The data source object (FormHeader) contains a column with an employee code. One of the columns in my Kendo grid is currently displaying the employee code, but I'd like to display the full name via .ClientTemplate:
#(Html.Kendo().Grid<Forms.Models.FormHeader>()
.Name("HeadersMasterGrid")
.BindTo((IQueryable<Forms.Models.FormHeader>)ViewData["AllForms"])
.Pageable()
.Columns(columns =>
{
columns.Bound(p => p.EmployeeCode).Title("Employee")
/*.ClientTemplate("#=getEmployeeName(EmployeeCode)#")*/;
<..>
I'd like to display the employee's first and last name instead of the code. I set up an IAction result in my Form Controller to receive an employee code and return the full name (via ~/Form/GetEmployee/):
public ActionResult GetEmployee(string id)
{
var employee = theDB.EmployeeMasters.FirstOrDefault(x => x.EmployeeCode.Trim() == id.ToUpper());
BasicEmployee basicEmployee = new BasicEmployee(employee.FirstName, employee.LastName, id.Trim());
//return Json(basicEmployee);
return Json(basicEmployee.FullName);
}
Lastly, I added some jQuery script to my view that I was hoping would open the URL, retrieve the full name, and return that into the .ClientTemplate of the Kendo Grid column. You can see that I have tried returning just the full name, but also returning the entire object and then parsing/returning the full name from the object.
The second function in the block below was my most recent attempt after receiving some instruction about promises/callbacks (which are still pretty fuzzy).
#*function getEmployeeName(empCode) {
$.getJSON('#Url.Action("GetEmployee", "Form")' + "/" + empCode,
function(data) {
//var response = $.parseJSON(data);
//return response.FullName.toString();
return $.parseJSON(data);
}, 'json');
}*#
function getEmployeeName(empCode) {
var promise = $.getJSON('#Url.Action("GetEmployee", "Form")' + "/" + empCode);
$.when(promise).done(function(data) {
return JSON.stringify(data);
});
}
I've spent quite a bit of time with the Kendo docs, Google, and two Slack workspaces for developers, but I'm not finding anything that works. I have no experience with either Javascript or jQuery before this project and am still very much in the learning phase. I understand that I am most likely going about this incorrectly (or missing an easy solution), but I'd like to know how I can accomplish this.
Related
called by selectbox go into function 'getDepAndMan()',
there is a value taken from the selectbox (works)
calls functions in the controller 'GetDepartmentAndManager' (works)
controller returns value (works)
{Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable<<>f__AnonymousType6<'string, string>>}
Result View: [0] { UserDepartament = "there is value here / string", UserManager = "there is value here / string" }
should go back to ajax and call 'succes: function (employee data)' (works)
should assign values to the fields (doesn't work)
show an alert (work)
show alert with values (doesn't work, show an alert with: undefined undefined)
View:
#(Html
.DevExtreme()
.SelectBox()
.DataSource(d => d
.Mvc()
)
.OnValueChanged("getDepAndMan")
)
#(Html.DevExtreme().TextBox()
.ID("Id_department")
.ReadOnly(true)
)
#(Html.DevExtreme().TextBox()
.ID("Id_manager")
.ReadOnly(true)
)
<script type="text/javascript">
function getDepAndMan() {
var userId = {
nazwaValueId: $("#idName").dxSelectBox("instance").option("value")
};
$.ajax({
url: "#Url.Action("GetDepartmentAndManager", "Uzytkownicy")",
type: "POST",
dataType: "json",
data: {"userId": JSON.stringify(userId)},
cache: false,
success: function (danePracownika) {
$("#Id_department")
.dxTextBox("instance")
.option("value", danePracownika.UserDepartament);
$("#Id_manager")
.dxTextBox("instance")
.option("value", danePracownika.UserManager);
alert(danePracownika.UserDepartament + " " + danePracownika.UserManager);
},
failure: function (error) {
alert(error);
},
error: function (error) {
alert(error);
}
});
}
</script>
Controller:
[HttpPost]
public ActionResult GetDepartmentAndManager(string userId)
{
dynamic serializer = JsonConvert.DeserializeObject<IDictionary>(userId);
var IdCzlowieka = serializer["nazwaValueId"];
int IntIdCzlowieka = Convert.ToInt32(IdCzlowieka);
var danePracownika = _uzytkownicyContext.Uzytkownicy.Where(x => x.Id == IntIdCzlowieka).Select(s => new
{
UserDepartament = s.Departament,
UserManager = s.ManagerLogin
});
return Json(danePracownika);
}
return : //
[0] { UserDepartament = "there is value here / string", UserManager = "there is value here / string" }
EDIT
The question is, what's wrong with the code, why it doesn't work for me?
.
I see that in Your GetDepartmentAndManager You are not using Your passed parameter userID:
var danePracownika = ... .Where(x => x.Id == IntIdCzlowieka)...
should be Where(x => x.Id == userId) instead.
The next thing that came to me is the value You are acctualy getting inside the controller action; based on the JS code I would say that this is not the ID of the employee what You are passing but the stringified object { "nazwaValueId": ... } that in the best case would be handled by the server and You will get the raw string as a value of userId (unless You have defined a IModelBinder class that would handle conversion from stringified { "nazwaValueId": ... } to the value of that field - more on that You can find here).
Oh any by the way - please try to avoid mixing languages. I have a friend in the company which was forced to work with the german project and all their code was written in German - You would DEFINETLY won't be happy working with it. But if this a project made only by PL for PL, that is some kind of acceptable approach I assume.
Also I highly advice You to not use HTTP POST method for getting data. To make long story short there is a convention that GET requests are for getting the data and You can call it as many times You like without affecting the state (było takie mądre słowo na to, ale nie pamiętam ;)) and POST is for saving/modifing data and should always redirect to GET method on return. You can read more about it here.
EDIT:
Ok, for some reason I have found that the call in the current form is sending data not as a body but as a form. I don't know, I don't use jQuery. But here is the reqest:
so I changed the signature of the action to
public ActionResult GetDepartmentAndManager([FromForm]string userId)
to get is started working. Maybe on Your side it is just working fine, I don't know. But what I have found is that while sending the responce to the client we end up with... this:
so as You can see either Ajax or server changed the JSON keys to be kebabCase not PascalCase and that's why You are getting undefined values. Because properties You arereading do not exists. Just check it out: alert(danePracownika.userDepartament + " " + danePracownika.userManager);
UPDATE:
I checked it, it was not server's fault:
I am trying to send my toolbar's 'search textbox' value through to a WEB API action. I have searched various online solutions. However, none seem to work.
On the client-side, I have a 'KeyUp' event on the 'search textbox'. Once completed, I need to append the value to the READ.
I HAVE TRIED THE FOLLOWING (CLIENT-SIDE):
// This Fails
var value = dictionary.elements.txtDeviceSearch.val();
var url = "api/devicedataitem/search?text='" + value + "'";
dictionary.instances.gridDevices.dataSource.options.transport.read.url = url;
dictionary.instances.gridDevices.dataSource.read();
// This Fails
dictionary.instances.gridDevices.dataSource.options.transport.read.data = { text: value };
dictionary.instances.gridDevices.dataSource.read();
// This Fails
dictionary.instances.gridDevices.dataSource.read({ text: 'Work Dammit' });
THE INITIAL CALL WORKS AS EXPECTED:
This is working as expected...
#(Html.Kendo().Grid<DeviceDataItem>()
.Name("gridDevices")
.DataSource(dataSource => dataSource
.WebApi()
.Model(model =>
{
model.Id(m => m.DeviceId);
model.Field(m => m.DeviceName);
model.Field(m => m.CommunicationTechnicianId);
model.Field(m => m.CommunicationTechnicianFullName);
model.Field(m => m.MeasurementTechnicianId);
model.Field(m => m.MeasurementTechnicianFullName);
})
.Read(read => read.Url("api/devicedataitem/search?text=''").Type(HttpVerbs.Post))
)
.ToolBar(toolbar =>
{
toolbar.Template(#<text>
<div class="input-group pull-right" role="toolbar">
<span class="input-group-addon">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
</span>
<input type="text" class="form-control" id='txtDeviceSearch' placeholder="Search for..." />
</div>
</text>);
})
.Deferred(true))
Now we're getting somewhere, this question is better than the other one. What I was about to say back then, before giving up is that there is a parameter called parameterMap in the JavaScript widget where you can manipulate the data which will be sent in the request to API. As stated in the docs you could try:
parameterMap: function (data, type) {
return kendo.stringify($.extend({ "text": $("#txtDeviceText").text() }, data));
}
But I don't know how to add it to the razor helper because their docs sucks too much and I can't test it. So you can try:
Adding the parameter in the initialization(which I'm not sure if works):
DataSource(dataSource => dataSource.ParameterMap("jsFunctionNameHere")
Or something like that;
Setting it in your grid after initialization:
$("#grid").data("kendoGrid").dataSource.transport.parameterMap = function() {
return { text: $("#txtDeviceText").text() };
};
Demo
I bet on 2nd option, it should work.
Give this a try:
dictionary.instances.gridDevices.dataSource.type = "aspnetmvc-ajax";
We do the same thing within our application, sending parameters to the controller action in addition to the DataSourceRequest object. With that type setting removed, I get the same behaviour you do with the parameters received with null values.
The datasource is defined with js akin to this:
this.results = new kendo.data.DataSource({
type: "aspnetmvc-ajax",
transport: {
read: {
url: "Controller/Search",
data: function () {
return {
value: "abc"
};
}
}
}
});
When read() is triggered on it, it hits the controller action like this:
public JsonResult Search([DataSourceRequest] DataSourceRequest request, string value)
So nothing out of the ordinary there but with that type removed, the value is not received. Note that this is a System.Web.MVC.Controller, not a System.Web.Http.ApiController, although I can't see how that would make a difference. It may also be that the datasource type can only effectively be set at the time it is set up (so it would go in the definition, not altered after the fact as I originally suggested) but that's just another guess. I feel your pain - this sort of thing should just work.
Well, I finally figured this one out & it was difficult. This particular answer has many facets:
It uses a GET
As such, the GET requires use of the "Model Binder Attribute"
A minor POST example is now included below...
GET - WEB API:
This works because the "Model Binder Attribute" grabs the REQUEST object & hydrates it for you. Without it, the REQUEST will always be null.
// GET: /api/DeviceDataItem/Search
[HttpGet]
public DataSourceResult Search([ModelBinder(typeof(WebApiDataSourceRequestModelBinder))] DataSourceRequest request, string search)
{
var application = (MyApplication)Application;
var provider = (DeviceDataItemProvider)application.DeviceDataItemProvider;
IQueryable<DeviceDataItem> query = provider.Query();
// No 'Where' needed
if (string.IsNullOrWhiteSpace(search))
return query.ToDataSourceResult(request);
// Where
...your WHERE logic goes here...
return query.ToDataSourceResult(request);
}
GET - MVC.Read:
Notice I am referring to a custom Page Controller object that is publicly available through the window.
.Read(read => read.Url(Url.HttpRouteUrl("DefaultApi", new { controller = "DeviceDataItem", action="Search"}))
.Type(HttpVerbs.Get)
.Data("window.pageController.on.read.gridDevices"))
GET - JavaScript:
I happen to cache my objects in a Page Controller - you will have to reference your objects (however they are built).
this.on = {
read: {
gridDevices: function () {
return {
search: dictionary.elements.txtDeviceSearch.val()
}
}
},
search: {
gridDevices: function (e) {
lazyInitGridDevices();
// Clear
dictionary.instances.gridDevices.dataSource.data([]);
dictionary.instances.gridDevices.refresh();
// Read
dictionary.instances.gridDevices.dataSource.read();
}
}
};
POST:
According to Telerik, the same approach is used for passing parameters to POST requests. However, with Web API, the second parameter cannot be added to the method signature. One possible way to address this scenario is to use a JObject to get all the data from the request and then construct a new model object using this data.
For example:
public HttpResponseMessage Post(JObject jsonData)
{
ProductViewModel product = new ProductViewModel() {
ProductName = (string)jsonData["ProductName"],
UnitPrice = (decimal)jsonData["ProductID"],
UnitsInStock = (int)jsonData["ProductName"],
Discontinued = (bool)jsonData["ProductID"]
};
string searchTerm = (string)jsonData["search"];
.............
}
i am creating a web app with the help of angularjs,
on one of my button click i am calling a web service through angularjs
[WebMethod]
[ScriptMethod(UseHttpGet = true)]
public void getuprec(string id)
{
List<object> selectrecd = new List<object>();
SqlCommand cmd = new SqlCommand("select * from erp_admin.CompanySonVinUnitVenueRpt where comsonvinid in (select id from companysonvinunitvenue where id='"+id+"')",con);
con.Open();
SqlDataReader dr = cmd.ExecuteReader();
while(dr.Read())
{
selectrecd.Add(new
{
attendee1 = dr["comattendee1"].ToString(),
attendee2 = dr["comattendee2"].ToString(),
totalmale = dr["attendeetotalmale"].ToString(),
totalfemale = dr["attendeetotalfemale"].ToString(),
unit1 = dr["unitattendee1"].ToString(),
unitd1 = dr["unitattendee1desig"].ToString(),
unit2 = dr["unitattendee2"].ToString(),
unitd2 = dr["unitattendee2desig"].ToString(),
unit3 = dr["unitattendee3"].ToString(),
unitd3 = dr["unitattendee3desig"].ToString(),
unit4 = dr["unitattendee4"].ToString(),
unitd4 = dr["unitattendee4desig"].ToString(),
unit5 = dr["unitattendee5"].ToString(),
unitd5 = dr["unitattendee5desig"].ToString(),
remarks = dr["remarks"].ToString()
});
}
con.Close();
var json = js.Serialize(selectrecd);
Context.Response.Write("{" + '"' + "selectrecd" + '"' + ":" + json + "}");
}
this is my webservice which is working fine (i tested) and my angularjs file
$scope.updatefunction = function (param) {
$http.get('/frmattendencerptqed.asmx/getuprec', {
params: {
id: $scope.updateparam.comsonvinid
}
})
.then()
{
}
}
this is my angularjs
this is my input field
<input type="text" ng-model="remarks" style="width:100%;" />
now i need to take the previous value from my database and bind the value on my textbox, and if user edit the textbox then the value must be updated on button click
how i need to do this
this is my input field
<input type="text" ng-model="remarks" style="width:100%;" />
now i need to take the previous value from my database and bind the
value on my textbox, and if user edit the textbox then the value must
be updated on button click
Which one remarks property ?
In your webservice you returns a json object which contains a array of structured objects which each one may contain a remark property.
Can you post the resulted json of webservice call in order to give us more details ?
About how to retrieve and bind the data in your angular js controller, here is my answer.
From https://docs.angularjs.org/api/ng/service/$http
you can retrieve response from data property in returned object.
data – {string|Object} – The response body transformed with the
transform functions.
So you can do something like that:
$scope.updatefunction = function (param) {
$http.get('/frmattendencerptqed.asmx/getuprec', {
params: {
id: $scope.updateparam.comsonvinid
}
})
.then(
// handling if ok
function(response){
$scope.remarks=response.data.selectrecd[indexYourWish].remarks
},
function(errResponse){
// handling if error
return $q.reject(errResponse);
}
);
}
As explains previously, I refer to an array to retrieve the information in response.data with an index you should define or not use at all index if you don't return a array.
remarks should be a variable in the scope of your controller.
The more relevant syntax to use for controller depends on how you declare things in Angular (Personally, I avoid $scope.xxx syntax).
Post more of your JS code if you want a more precise answer.
It's just how angularjs normally works, the two way databinding follow this tutorial:
http://www.w3schools.com/angular/angular_databinding.asp
you'll see that what you only need is to call the update function from your controller and pass it the variable you bind to the input.
Of course this variable assigned to $scope or to this in the controller (there are two ways).
I'm trying to convert my basic crud operations into an API that multiple components of my application can use.
I have successfully converted all methods, except the update one because it calls for each property on the object to be declared before the put request can be executed.
controller
$scope.update = function(testimonial, id) {
var data = {
name: testimonial.name,
message: testimonial.message
};
dataService.update(uri, data, $scope.id).then(function(response) {
console.log('Successfully updated!');
},
function(error) {
console.log('Error updating.');
});
}
dataService
dataService.update = function(uri, data, id) {
var rest = Restangular.one(uri, id);
angular.forEach(data, function(value, key) {
// needs to be in the format below
// rest.key = data.key
});
// needs to output something like this, depending on what the data is passed
// rest.name = data.name;
// rest.message = data.message;
return rest.put();
}
I tried to describe the problem in the codes comments, but to reiterate I cannot figure out how to generate something like rest.name = data.name; without specifying the name property because the update function shouldn't need to know the object properties.
Here is what the update method looked like before I started trying to make it usable by any of my components (this works)
Testimonial.update = function(testimonial, id) {
var rest = Restangular.one('testimonials', id);
rest.name = testimonial.name;
rest.message = testimonial.message;
return rest.put();
}
How can I recreate this without any specific properties parameters hard-coded in?
Also, my project has included lo-dash, if that helps, I don't know where to start with this problem. Thanks a ton for any advice!
Try like
angular.extend(rest,testimonial)
https://docs.angularjs.org/api/ng/function/angular.extend
I have a Single Page Application that is working pretty well so far but I have run into an issue I am unable to figure out. I am using breeze to populate a list of projects to be displayed in a table. There is way more info than what I actually need so I am doing a projection on the data. I want to add a knockout computed onto the entity. So to accomplish this I registered and entity constructor like so...
metadataStore.registerEntityTypeCtor(entityNames.project, function () { this.isPartial = false; }, initializeProject);
The initializeProject function uses some of the values in the project to determine what the values should be for the computed. For example if the Project.Type == "P" then the rowClass should = "Red".
The problem I am having is that all the properties of Project are null except for the ProjNum which happens to be the key. I believe the issue is because I am doing the projection because I have registered other initializers for other types and they work just fine. Is there a way to make this work?
EDIT: I thought I would just add a little more detail for clarification. The values of all the properties are set to knockout observables, when I interrogate the properties using the javascript debugger in Chrome the _latestValue of any of the properties is null. The only property that is set is the ProjNum which is also the entity key.
EDIT2: Here is the client side code that does the projection
var getProjectPartials = function (projectObservable, username, forceRemote) {
var p1 = new breeze.Predicate("ProjManager", "==", username);
var p2 = new breeze.Predicate("ApprovalStatus", "!=", "X");
var p3 = new breeze.Predicate("ApprovalStatus", "!=", "C");
var select = 'ProjNum,Title,Type,ApprovalStatus,CurrentStep,StartDate,ProjTargetDate,CurTargDate';
var isQaUser = cookies.getCookie("IsQaUser");
if (isQaUser == "True") {
p1 = new breeze.Predicate("QAManager", "==", username);
select = select + ',QAManager';
} else {
select = select + ',ProjManager';
}
var query = entityQuery
.from('Projects')
.where(p1.and(p2).and(p3))
.select(select);
if (!forceRemote) {
var p = getLocal(query);
if (p.length > 1) {
projectObservable(p);
return Q.resolve();
}
}
return manager.executeQuery(query).then(querySucceeded).fail(queryFailed);
function querySucceeded(data) {
var list = partialMapper.mapDtosToEntities(
manager,
data.results,
model.entityNames.project,
'ProjNum'
);
if (projectObservable) {
projectObservable(list);
}
log('Retrieved projects using breeze', data, true);
}
};
and the code for the partialMapper.mapDtosToEntities function.
var defaultExtension = { isPartial: true };
function mapDtosToEntities(manager,dtos,entityName,keyName,extendWith) {
return dtos.map(dtoToEntityMapper);
function dtoToEntityMapper(dto) {
var keyValue = dto[keyName];
var entity = manager.getEntityByKey(entityName, keyValue);
if (!entity) {
extendWith = $.extend({}, extendWith || defaultExtension);
extendWith[keyName] = keyValue;
entity = manager.createEntity(entityName, extendWith);
}
mapToEntity(entity, dto);
entity.entityAspect.setUnchanged();
return entity;
}
function mapToEntity(entity, dto) {
for (var prop in dto) {
if (dto.hasOwnProperty(prop)) {
entity[prop](dto[prop]);
}
}
return entity;
}
}
EDIT3: Looks like it was my mistake. I found the error when I looked closer at initializeProject. Below is what the function looked like before i fixed it.
function initializeProject(project) {
project.rowClass = ko.computed(function() {
if (project.Type == "R") {
return "project-list-item info";
} else if (project.Type == "P") {
return "project-list-item error";
}
return "project-list-item";
});
}
the issue was with project.Type I should have used project.Type() since it is an observable. It is a silly mistake that I have made too many times since starting this project.
EDIT4: Inside initializeProject some parts are working and others aren't. When I try to access project.ProjTargetDate() I get null, same with project.StartDate(). Because of the Null value I get an error thrown from the moment library as I am working with these dates to determine when a project is late. I tried removing the select from the client query and the call to the partial entity mapper and when I did that everything worked fine.
You seem to be getting closer. I think a few more guard clauses in your initializeProject method would help and, when working with Knockout, one is constantly battling the issue of parentheses.
Btw, I highly recommend the Knockout Context Debugger plugin for Chrome for diagnosing binding problems.
Try toType()
You're working very hard with your DTO mapping, following along with John's code from his course. Since then there's a new way to get projection data into an entity: add toType(...) to the end of the query like this:
var query = entityQuery
.from('Projects')
.where(p1.and(p2).and(p3))
.select(select)
.toType('Project'); // cast to Project
It won't solve everything but you may be able to do away with the dto mapping.
Consider DTOs on the server
I should have pointed this out first. If you're always cutting this data down to size, why not define the client-facing model to suit your client. Create DTO classes of the right shape(s) and project into them on the server before sending data over the wire.
You can also build metadata to match those DTOs so that Project on the client has exactly the properties it should have there ... and no more.
I'm writing about this now. Should have a page on it in a week or so.