Related
I have this function that returns a list of pokemons from a JSON URL of pokeapi.
I need to filter this function so that it shows those of a certain type that I bring with the variable dataPoke.
The variable is returned with the correct value, but I don't know how to give it filtered output and that it only shows the pokemons of the dataPoke type.
I get data from https://pokeapi.co/api/v2/pokemon https://pokeapi.co/api/v2/pokemon/3
listadoPokemonFiltrado(salida = 0,dataPoke) {
console.log("datos recibidos: ", dataPoke);
return this.http.get(`${this.baseUrl}/pokemon?salida=${salida}`)
.pipe(
map(result => { return result['results'];}),
map(pokemon => {
return pokemon.map((arreglo, index) => {
arreglo.image = this.obtenerImagen(salida + index + 1);
arreglo.pokeIndex = salida + index + 1;
arreglo.type = this.obtenerTypo(dataPoke);
arreglo.pokeIndex = arreglo.type;
return arreglo;
});
})
);
}
obtenerImagen(index) {
return `${this.imageUrl}${index}.png`;
}
obtenerTypo(index) {
return `${this.baseUrlTypes}/${index}`;
}
Reading code not in English is certainly a challenge, but I assume that what you're looking for is filtering your array?
listadoPokemonFiltrado(salida = 0, dataPoke) {
console.log('datos recibidos: ', dataPoke);
return this.http.get(`${this.baseUrl}/pokemon?salida=${salida}`).pipe(
map(result => result['results']),
map(pokemons => {
return pokemons
.filter(pokemon => pokemon.type === dataPoke)
.map((arreglo, index) => {
arreglo.image = this.obtenerImagen(salida + index + 1);
arreglo.pokeIndex = salida + index + 1;
arreglo.type = this.obtenerTypo(dataPoke);
arreglo.pokeIndex = arreglo.type;
return arreglo;
});
})
);
}
obtenerImagen(index) {
return `${this.imageUrl}${index}.png`;
}
obtenerTypo(index) {
return `${this.baseUrlTypes}/${index}`;
}
I have added .filter(pokemon => pokemon.type === dataPoke) to filter results array.
I am trying to use the group by function on a JSON array using the inner JSON value as a key as shown below. But unable to read the inner JSON value. Here is my JSON array.
NotificationData = [
{
"eventId":"90989",
"eventTime":"2019-12-11T11:20:53+04:00",
"eventType":"yyyy",
"event":{
"ServiceOrder":{
"externalId":"2434",
"priority":"1"
}
}
},
{
"eventId":"6576",
"eventTime":"2019-12-11T11:20:53+04:00",
"eventType":"yyyy",
"event":{
"ServiceOrder":{
"externalId":"78657",
"priority":"1"
}
}
}
]
GroupBy Logic:
const groupBy = (array, key) => {
return array.reduce((result, currentValue) => {
(result[currentValue[key]] = result[currentValue[key]] || []).push(
currentValue
);
return result;
}, {});
};
const serviceOrdersGroupedByExternalId = groupBy(this.NotificationData, 'event.ServiceOrder.externalId');
//this line of code is not working as
// it is unable to locate the external id value.
Desired output
{ "2434":[{
"eventId":"90989",
"eventTime":"2019-12-11T11:20:53+04:00",
"eventType":"yyyy",
"event":{
"ServiceOrder":{ "priority":"1" }
}
}],
"78657":[{
"eventId":"6576",
"eventTime":"2019-12-11T11:20:53+04:00",
"eventType":"yyyy",
"event":{
"ServiceOrder":{ "priority":"1" }
}
}]
}
Does this solves your purpose?
let group = NotificationData.reduce((r, a) => {
let d = r[a.event.ServiceOrder.externalId] = [...r[a.event.ServiceOrder.externalId] || [], a];
return r;
}, {});
console.log(group);
Try like this:
result = {};
constructor() {
let externalIds = this.NotificationData.flatMap(item => item.event.ServiceOrder.externalId);
externalIds.forEach(id => {
var eventData = this.NotificationData.filter(
x => x.event.ServiceOrder.externalId == id
).map(function(item) {
delete item.event.ServiceOrder.externalId;
return item;
});
this.result[id] = eventData;
});
}
Working Demo
On my view, I am using a Viewmodel, and I have a form that has only one textbox that accepts dates (not a part of the viewmodel) and 3 tables. By default on page load.. the tables are populated with data based on today's date (you can see that in the controller code below), but if a user selects a date and clicks the search button then I want the tables data to be changed without a page refresh based on the date they selected.
#using (Html.BeginForm())
{
<div class="form-group mb-3 mt-3" style="margin-right: -1.3%;">
<div class="input-group col-md-3 offset-md-9">
#Html.TextBox("detailsDate", null, new { id = "Details-Date", #class = "form-control datetimepicker" })
<div class="input-group-append">
<button id="Details-Date-Btn" type="submit" class="btn btn-outline-primary"><span class="fa fa-search"></span></button>
</div>
</div>
</div>
}
What I am trying to do is if a user selects and date and hits the search button.. I would like the page to not refresh and the tables data have been changed based on the date. As of right now I am getting:
A circular reference was detected while serializing an object of type 'System.Data.Entity.DynamicProxies.tbl_WeighAssc_8AA7AB5F9DAB261D5142F1D5F5BA6705A588A5AAD2D369FBD4B4BC1BBE0487D4'.
Viewmodel
public class PersonnelDetailsVm
{
private static ConnectionString db = new ConnectionString();
public PersonnelDetailsVm()
{
CurrentWeekDates = new List<DateTime>();
WeighAssociations = new List<tbl_WeighAssc>();
ArrestAssociations = new List<tbl_TEUArrestAssc>();
InspectionAssociations = new List<tblTEUInspectionAssc>();
}
public string IBM { get; set; }
[Display(Name = "Name")]
public string UserName { get; set; }
public bool Active { get; set; }
public List<DateTime> CurrentWeekDates { get; set; }
public List<tbl_WeighAssc> WeighAssociations { get; set; }
public List<tbl_TEUArrestAssc> ArrestAssociations { get; set; }
public List<tblTEUInspectionAssc> InspectionAssociations { get; set; }
public List<code_WeighLocation> WeighLocations => db.code_WeighLocation.ToList();
public List<code_ArrestType> ArrestTypes => db.code_ArrestType.ToList();
public List<code_InspectionLevel> InspectionLevels => db.code_InspectionLevel.ToList();
}
Ajax:
// Submission
//var redirectUrl = '#Url.Action("Index", "Personnels")';
var settings = {};
settings.baseUri = '#Request.ApplicationPath';
var infoGetUrl = "";
if (settings.baseUri === "/AppName") {
infoGetUrl = settings.baseUri + "/Personnels/Details/";
} else {
infoGetUrl = settings.baseUri + "Personnels/Details/";
}
$("#Details-Date-Btn").click(function() {
$.ajax({
url: infoGetUrl,
method: "POST",
data: $("form").serialize(),
success: function(response) {
console.log("success");
$("body").html(response);
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
}
});
});
Controller:
public ActionResult Details(string id, string detailsDate)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
tblPersonnel tblPersonnel = db.tblPersonnels.Find(id);
if (tblPersonnel == null)
{
return HttpNotFound();
}
Mapper.Initialize(config => config.CreateMap<tblPersonnel, PersonnelDetailsVm>());
PersonnelDetailsVm person = Mapper.Map<tblPersonnel, PersonnelDetailsVm>(tblPersonnel);
var employeeData = EmployeeData.GetEmployee(person.IBM);
person.UserName =
$"{ConvertRankAbbr.Conversion(employeeData.Rank_Position)} {employeeData.FirstName} {employeeData.LastName}";
if (string.IsNullOrWhiteSpace(detailsDate))
{
var startOfWeek = DateTime.Today.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -
(int)DateTime.Today.DayOfWeek);
person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList();
var teuFormIds = db.tbl_TEUForm
.Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();
person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.InspectionAssociations =
db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
return View(person);
}
else
{
var paramDate = DateTime.ParseExact(detailsDate, "MM/dd/yyyy", CultureInfo.CurrentCulture);
var startOfWeek = paramDate.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -
(int)paramDate.DayOfWeek);
person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList();
var teuFormIds = db.tbl_TEUForm
.Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();
person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.InspectionAssociations =
db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
return Json(person, JsonRequestBehavior.AllowGet);
}
}
So, if the actionresult's paramets detailsDate is not null, then it goes into the else statement which returns a JSON object. When debugging this goes through and when the view is returned I am receiving the error I posted above.
Is there a way to replace the model in the view with what I'm returning from the ajax call so the tables can be based on the right date without a page refresh?
Any help is greatly appreciated.
UPDATE
Based on answer's below I have edited the else statement in my controller method to:
Controller
else
{
var paramDate = DateTime.ParseExact(detailsDate, "MM/dd/yyyy", CultureInfo.CurrentCulture);
var startOfWeek = paramDate.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -
(int)paramDate.DayOfWeek);
person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList();
var teuFormIds = db.tbl_TEUForm
.Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();
person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.InspectionAssociations =
db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
{
PreserveReferencesHandling = PreserveReferencesHandling.All,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var jsonStr = JsonConvert.SerializeObject(person);
return Json(jsonStr, "text/plain");
}
My jQuery/Ajax is still the same:
$("#Details-Date-Btn").click(function() {
$.ajax({
url: infoGetUrl,
data: $("form").serialize(),
success: function(response) {
console.log("success");
console.log(response);
$("body").html(response);
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
}
});
});
But now, when the date is selected I am being returned to a page that shows the Json like a plain text file and losing the HTML and CSS like a normal view.
Here is what I am being returned when a date is selected and the button is clicked.
Also, when I check the console when I select a date and click the button for that date to be sent to the controller I am seeing this:
UPDATE 2
Here is one of my tables.. the other ones are the same setup:
<table class="table table-bordered">
<thead>
<tr>
<th></th>
#foreach (var date in Model.CurrentWeekDates)
{
<th class="text-center">#date.ToString("ddd") <br /> #date.ToShortDateString()</th>
}
<th class="text-center table-success">Total For Week</th>
</tr>
</thead>
<tbody>
#foreach (var weighLocation in Model.WeighLocations)
{
<tr class="text-center">
<td class="table-dark">#weighLocation.Weigh_Location</td>
#foreach (var date in Model.CurrentWeekDates)
{
if (Model.WeighAssociations.Any(x => x.tbl_TEUForm.EventDate == date && x.WeighLocationId == weighLocation.ID))
{
<td>#Model.WeighAssociations.Single(x => x.tbl_TEUForm.EventDate == date && x.WeighLocationId == weighLocation.ID).OccurenceCount</td>
}
else
{
<td>0</td>
}
}
<td class="table-success font-weight-bold">#Model.WeighAssociations.Where(x => x.WeighLocationId == weighLocation.ID).Sum(x => x.OccurenceCount)</td>
</tr>
}
</tbody>
</table>
As far as I can see from your problem, to fix it you can do following steps:
1- View
Add a partial view (_Detail.cshtml)
You need a partial view like _Detail that includes your table like this:
#model PersonnelDetailsVm
<table class="table table-bordered">
<thead>
<tr>
<th></th>
#foreach (var date in Model.CurrentWeekDates)
{
<th class="text-center">#date.ToString("ddd") <br /> #date.ToShortDateString()</th>
}
<th class="text-center table-success">Total For Week</th>
</tr>
</thead>
<tbody>
#foreach (var weighLocation in Model.WeighLocations)
{
<tr class="text-center">
<td class="table-dark">#weighLocation.Weigh_Location</td>
#foreach (var date in Model.CurrentWeekDates)
{
if (Model.WeighAssociations.Any(x => x.tbl_TEUForm.EventDate == date && x.WeighLocationId == weighLocation.ID))
{
<td>#Model.WeighAssociations.Single(x => x.tbl_TEUForm.EventDate == date && x.WeighLocationId == weighLocation.ID).OccurenceCount</td>
}
else
{
<td>0</td>
}
}
<td class="table-success font-weight-bold">#Model.WeighAssociations.Where(x => x.WeighLocationId == weighLocation.ID).Sum(x => x.OccurenceCount)</td>
</tr>
}
</tbody>
</table>
2- Controller
Return the partial view
You should fill the model in your controller and pass it to the partial view.
public ActionResult Details(string id, string detailsDate)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
tblPersonnel tblPersonnel = db.tblPersonnels.Find(id);
if (tblPersonnel == null)
{
return HttpNotFound();
}
Mapper.Initialize(config => config.CreateMap<tblPersonnel, PersonnelDetailsVm>());
PersonnelDetailsVm person = Mapper.Map<tblPersonnel, PersonnelDetailsVm>(tblPersonnel);
var employeeData = EmployeeData.GetEmployee(person.IBM);
person.UserName =
$"{ConvertRankAbbr.Conversion(employeeData.Rank_Position)} {employeeData.FirstName} {employeeData.LastName}";
if (string.IsNullOrWhiteSpace(detailsDate))
{
var startOfWeek = DateTime.Today.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -
(int)DateTime.Today.DayOfWeek);
person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList();
var teuFormIds = db.tbl_TEUForm
.Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();
person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.InspectionAssociations =
db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
// return View(person);
}
else
{
var paramDate = DateTime.ParseExact(detailsDate, "MM/dd/yyyy", CultureInfo.CurrentCulture);
var startOfWeek = paramDate.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -
(int)paramDate.DayOfWeek);
person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList();
var teuFormIds = db.tbl_TEUForm
.Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();
person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.InspectionAssociations =
db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
// return Json(person, JsonRequestBehavior.AllowGet);
}
// return PartialView with the person model
return PartialView("_Detail", person);
}
As you can see in the above code, you should comment the two lines in your code:
// return View(person);
// return Json(person, JsonRequestBehavior.AllowGet);
3- Ajax call
Get the partial view and fill form by it
You don't any changes in ajax call and you can do it like this:
$("#Details-Date-Btn").click(function() {
$.ajax({
url: infoGetUrl,
method: "POST",
data: $("form").serialize(),
success: function(response) {
console.log("success");
$("body").html(response);
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
}
});
});
response in this way is a html that comes from partial view and it has all classes as you designed it.
That error message means that one of your child properties refers back to the parent and JSON serialization causes a circular loop.
To fix, replace this:
return Json(person, JsonRequestBehavior.AllowGet);
with this:
return Content(JsonConvert.SerializeObject(person,
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}), "application/json");
You will have to install NewtonSoft.Json:
using Newtonsoft.Json;
A circular reference was detected while serializing an object of type occurred because JSON serializer doesn't support circular references inside your object hierarchy (i.e. passing PersonnelDetailsVm that contains references to data models). To resolve this issue, use JSON.NET's JsonConvert.SerializeObject() with default settings set like this:
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.All,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
Afterwards, you can return JsonResult from viewmodel:
string jsonStr = JsonConvert.SerializeObject(person);
return Json(jsonStr);
If you're using IE and encountering save dialog because of friendly JSON errors configuration, you may need to add text/html or text/plain when returning JSON data:
return Json(jsonStr, "text/html");
Or hide the Json() method inside controller class like this:
protected new JsonResult Json(object data)
{
if (!Request.AcceptTypes.Contains("application/json"))
return base.Json(data, "text/plain");
else
return base.Json(data);
}
Additionally, instead of return View(person); you may consider return PartialView("Details", person); because AJAX call intended to stay on same page.
Setting ReferenceLoopHandling = ReferenceLoopHandling.Ignore will handle your issue related to circular reference exception.
Now to about issue(s) you presented in your updates, I think you might have some misunderstanding about how Ajax request work. I think this because when you make an ajax request to server it 'll respond with JSON data which will be representation of your view model and will be agnostic to your view (cshtml) code, so when you call $("body").html(response); you are replacing content of your page with stringified representation of your JSON view model. Take away here is that when you make an ajax request only back-end code will get executed and your view code(cshtml) will not be executed, period.
To solve your you will have to replace content of the table itself and not the body of the page, so you Ajax success callback should be something like:
var tempData = {
"IBM": "IBM",
"UserName": "UserName",
"Active": false,
"CurrentWeekDates": [],
"WeighAssociations": [],
"ArrestAssociations": [],
"InspectionAssociations": [],
"WeighLocations": [],
"ArrestTypes": [],
"InspectionLevels": []
};
function onSuccess(response){
var table = $("#tblData");
table.html("");
// code to create your head same code as your cshtml
table.append("<thead><th>New Column</th></thead>");
table.append("<tr><td>Column 1</td></tr>");
$("#btnLoad").text("Loaded");
}
$("#btnLoad").click(function(){
$("#btnLoad").attr("disabled", "");
$("#btnLoad").text("Loading...");
// call onSuccess function after 5 second to replicate server call
setTimeout(function(){ onSuccess(tempData) }, 5000);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="btnLoad">Load Data</button>
<table id="tblData" class="table table-bordered">
<thead>
<tr>
<th></th>
<th class="text-center">Mon<br /> 01-01-1990</th>
<th class="text-center table-success">Total For Week</th>
</tr>
</thead>
<tbody>
<tr class="text-center">
<td class="table-dark">Column 1</td>
<td>Columns 2</td>
<td>0</td>
<td class="table-success font-weight-bold">0</td>
</tr>
</tbody>
</table>
I hope this helps you!
You have 2 different return methods and you're setting the body content to the response of the request, which if the else statement is run will be JSON and not html.
I would suggest to either always return JSON or always return a View/Partial.
else
{
var paramDate = DateTime.ParseExact(detailsDate, "MM/dd/yyyy", CultureInfo.CurrentCulture);
var startOfWeek = paramDate.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -
(int)paramDate.DayOfWeek);
person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList();
var teuFormIds = db.tbl_TEUForm
.Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();
person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
person.InspectionAssociations =
db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();
JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
{
PreserveReferencesHandling = PreserveReferencesHandling.All,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var jsonStr = JsonConvert.SerializeObject(person);
//return Json(jsonStr, "text/plain");
return Partial(person);
}
I would suggest the below steps
One partial view for filters which include DateTime and Submit button(FilterPartial)
One Partial view for tables to be rendered(ResultsPartial)
When the body is loaded then load the first partial view(let's call FilterPartial) which will set the values in the ResultsPartial view.
function GetData(params) {
$.ajax({
type: "POST",
url: '/controller/Action',
data: {
//Parameters
},
dataType: 'html',
success: function (data) {
//You can either load the html directly or render the control here
}
});
}
public PartialViewResult Action()
{
return PartialView("");
}
The ViewModel logic can be kept as it is, you have to segregate the views.
Let me know if this helps...
You need to create two methods, first one to return the view with ActionResult return type:
public ActionResult Details(string id, string detailsDate)
{
....
return View(person);
}
And a second method which will be called via Ajax to return the Json data with JsonResult type
public JsonResult GetData(string id, string detailsDate)
{
....
return Json(person, JsonRequestBehavior.AllowGet);
}
To prevent repeating same getting data logic twice, you can move all data logic to the Json method and keep View method return with no data, then just trigger Ajax call when page load is completed to get initial data from Json method.
We are working with a 3rd party grid (telerik kendo) that has paging/sorting/filtering built in. It will send the requests in a certain way when making the GET call and I'm trying to determine if there is a way to translate these requests to AutoQuery friendly requests.
Query string params
Sort Pattern:
sort[{0}][field] and sort[{0}][dir]
Filtering:
filter[filters][{0}][field]
filter[filters][{0}][operator]
filter[filters][{0}][value]
So this which is populated in the querystring:
filter[filters][0][field]
filter[filters][0][operator]
filter[filters][0][value]
would need to be translated to.
FieldName=1 // filter[filters][0][field]+filter[filters][0][operator]+filter[filters][0][value] in a nutshell (not exactly true)
Should I manipulate the querystring object in a plugin by removing the filters (or just adding the ones I need) ? Is there a better option here?
I'm not sure there is a clean way to do this on the kendo side either.
I will explain the two routes I'm going down, I hope to see a better answer.
First, I tried to modify the querystring in a request filter, but could not. I ended up having to run the autoqueries manually by getting the params and modifying them before calling AutoQuery.Execute. Something like this:
var requestparams = Request.ToAutoQueryParams();
var q = AutoQueryDb.CreateQuery(requestobject, requestparams);
AutoQueryDb.Execute(requestobject, q);
I wish there was a more global way to do this. The extension method just loops over all the querystring params and adds the ones that I need.
After doing the above work, I wasn't very happy with the result so I investigated doing it differently and ended up with the following:
Register the Kendo grid filter operations to their equivalent Service Stack auto query ones:
var aq = new AutoQueryFeature { MaxLimit = 100, EnableAutoQueryViewer=true };
aq.ImplicitConventions.Add("%neq", aq.ImplicitConventions["%NotEqualTo"]);
aq.ImplicitConventions.Add("%eq", "{Field} = {Value}");
Next, on the grid's read operation, we need to reformat the the querystring:
read: {
url: "/api/stuff?format=json&isGrid=true",
data: function (options) {
if (options.sort && options.sort.length > 0) {
options.OrderBy = (options.sort[0].dir == "desc" ? "-" : "") + options.sort[0].field;
}
if (options.filter && options.filter.filters.length > 0) {
for (var i = 0; i < options.filter.filters.length; i++) {
var f = options.filter.filters[i];
console.log(f);
options[f.field + f.operator] = f.value;
}
}
}
Now, the grid will send the operations in a Autoquery friendly manner.
I created an AutoQueryDataSource ts class that you may or may not find useful.
It's usage is along the lines of:
this.gridDataSource = AutoQueryKendoDataSource.getDefaultInstance<dtos.QueryDbSubclass, dtos.ListDefinition>('/api/autoQueryRoute', { orderByDesc: 'createdOn' });
export default class AutoQueryKendoDataSource<queryT extends dtos.QueryDb_1<T>, T> extends kendo.data.DataSource {
private constructor(options: kendo.data.DataSourceOptions = {}, public route?: string, public request?: queryT) {
super(options)
}
defer: ng.IDeferred<any>;
static exportToExcel(columns: kendo.ui.GridColumn[], dataSource: kendo.data.DataSource, filename: string) {
let rows = [{ cells: columns.map(d => { return { value: d.field }; }) }];
dataSource.fetch(function () {
var data = this.data();
for (var i = 0; i < data.length; i++) {
//push single row for every record
rows.push({
cells: _.map(columns, d => { return { value: data[i][d.field] } })
})
}
var workbook = new kendo.ooxml.Workbook({
sheets: [
{
columns: _.map(columns, d => { return { autoWidth: true } }),
// Title of the sheet
title: filename,
// Rows of the sheet
rows: rows
}
]
});
//save the file as Excel file with extension xlsx
kendo.saveAs({ dataURI: workbook.toDataURL(), fileName: filename });
})
}
static getDefaultInstance<queryT extends dtos.QueryDb_1<T>, T>(route: string, request: queryT, $q?: ng.IQService, model?: any) {
let sortInfo: {
orderBy?: string,
orderByDesc?: string,
skip?: number
} = {
};
let opts = {
transport: {
read: {
url: route,
dataType: 'json',
data: request
},
parameterMap: (data, type) => {
if (type == 'read') {
if (data.sort) {
data.sort.forEach((s: any) => {
if (s.field.indexOf('.') > -1) {
var arr = _.split(s.field, '.')
s.field = arr[arr.length - 1];
}
})
}//for autoquery to work, need only field names not entity names.
sortInfo = {
orderByDesc: _.join(_.map(_.filter(data.sort, (s: any) => s.dir == 'desc'), 'field'), ','),
orderBy: _.join(_.map(_.filter(data.sort, (s: any) => s.dir == 'asc'), 'field'), ','),
skip: 0
}
if (data.page)
sortInfo.skip = (data.page - 1) * data.pageSize,
_.extend(data, request);
//override sorting if done via grid
if (sortInfo.orderByDesc) {
(<any>data).orderByDesc = sortInfo.orderByDesc;
(<any>data).orderBy = null;
}
if (sortInfo.orderBy) {
(<any>data).orderBy = sortInfo.orderBy;
(<any>data).orderByDesc = null;
}
(<any>data).skip = sortInfo.skip;
return data;
}
return data;
},
},
requestStart: (e: kendo.data.DataSourceRequestStartEvent) => {
let ds = <AutoQueryKendoDataSource<queryT, T>>e.sender;
if ($q)
ds.defer = $q.defer();
},
requestEnd: (e: kendo.data.DataSourceRequestEndEvent) => {
new DatesToStringsService().convert(e.response);
let ds = <AutoQueryKendoDataSource<queryT, T>>e.sender;
if (ds.defer)
ds.defer.resolve();
},
schema: {
data: (response: dtos.QueryResponse<T>) => {
return response.results;
},
type: 'json',
total: 'total',
model: model
},
pageSize: request.take || 40,
page: 1,
serverPaging: true,
serverSorting: true
}
let ds = new AutoQueryKendoDataSource<queryT, T>(opts, route, request);
return ds;
}
}
I am trying to follow this example
and I have the following in JSP page
(getData.jsp)
Department t = new Department ();
String query = request.getParameter("q");
List<String> tenders = t.getDepartments(query);
Iterator<String> iterator = tenders.iterator();
while(iterator.hasNext()) {
String deptName= (String)iterator.next();
String depto = (String)iterator.next();
out.println(deptName);
}
How can I use the above to use in Jquery autcomplete? When I tried there was no output coming.
My Jquery autoComplete code
<script>
$(function() {
$( "#dept" ).autocomplete({
source: function( request, response ) {
$.ajax({
url: "getData.jsp",
dataType: "jsonp",
data: {
featureClass: "P",
style: "full",
maxRows: 12,
name_startsWith: request.term
},
success: function( data ) {
response( $.map( data.<??>, function( item ) {
return {
label: item.name + (item.<??> ? ", " + item.<??> : "") + ", " + item.<??>,
value: item.name
}
}));
}
});
},
minLength: 2,
select: function( event, ui ) {
alert(ui.item.label);
}
});
});
</script>
Is your response in JSON format?
Here's what I do when I use Jquery UI Autocomplete:
Create a class whose parameters are the ones you will use when you say item.name
public string Pam1{ get; set; }
public string Pam2{ get; set; }
public string Pam3{ get; set; }
public SomeResponse(string SomePam)
{
// Pam1 = ???
// Pam2 = ???
// Pam3 = ???
}
In your handler:
context.Response.ContentType = "application/json";
string query = (string)context.Request.QueryString["query"];
var json = new JavaScriptSerializer();
context.Response.Write(
json.Serialize(new SomeResponse(query))
);
context.Response.Flush();
context.Response.End();
EDIT
The javascript (Here is an example where the user can choose more than one option, separated by coma. If you don't want that, remove it.) txt_autocomplete is the class of the TextBox.
$(function () {
function split(val) {
return val.split(/,\s*/);
}
function extractLast(term) {
return split(term).pop();
}
$(".txt_autocomplete")
// don't navigate away from the field on tab when selecting an item
.bind("keydown", function (event) {
if (event.keyCode === $.ui.keyCode.TAB &&
$(this).data("ui-autocomplete").menu.active) {
event.preventDefault();
}
})
.autocomplete({
source: function (request, response) {
$.getJSON("handlers/autocomplete.ashx?query=" + extractLast(request.term), {
term: extractLast(request.term)
}, response);
},
search: function () {
var term = extractLast(this.value);
if (term.length < 2) {
return false;
}
},
focus: function () {
return false;
},
select: function (event, ui) {
var terms = split(this.value);
terms.pop();
terms.push(ui.item.Pam1);
terms.push("");
this.value = terms.join(", ");
console.log("Pam1 is :" + ui.item.Pam1 + " Pam2 is: " + ui.item.Pam2 + " Pam 3 is : " + ui.item.Pam3);
return false;
}
});
});