I'm trying to use jqTree to render a collapsible tree to display data from a MySQL database via an ASP.NET project using AJAX.
The Problem:
I can successfully get the string containing the jqTree formatted data (which is not JSON even though they say it supports it) back from my AJAX call. However, once I get it there it get rendered as a vertical string of characters. If I do a typeof call on the data, it says it's a string, even though it 'looks' like an object when visually inspected via console.log.
I have tried a number of different ways to get the string into an object, with varying results.
I'm using this in the code behind to return the manufactured string:
return sb.ToString();
The resultant string looks like this (notice no wrapping quotation marks):
[{label: 'PCBID: 350',children:[{label: 'TimeStamp: 04-Sep-14 10:30:23'},{label: 'User: DAVEG'},{label: 'PCBID: 350'},{label: 'Assembly Drawing: 41411'},{label: 'PCB Drawing: 10348'},{label: 'Vendor: SBE'},{label: 'PO Number: 98019'}]},{label: 'Serial Number: Not Assigned'},{label: 'Assembly Drawing: 41411'},{label: 'Last Test Result: None Found'}]
Which gets rendered like this in my div:
[
{
l
a
b
e
l
:
'
P
C
B
I
D
...and so on...
I know these are being rendered by jqTree because I can drag & drop them, they highlight when clicked on, etc., but instead of a tree view, I get a "dangling vine" view, not exactly useful.
If I simply take that same exact same string and declare it as a var inside the JS (not using the return value of message.d):
var data = [{label: 'PCBID: 350',children:[{label: 'TimeStamp: 04-Sep-14 10:30:23'},{label: 'User: DAVEG'},{label: 'PCBID: 350'},{label: 'Assembly Drawing: 41411'},{label: 'PCB Drawing: 10348'},{label: 'Vendor: SBE'},{label: 'PO Number: 98019'}]},{label: 'Serial Number: Not Assigned'},{label: 'Assembly Drawing: 41411'},{label: 'Last Test Result: None Found'}]
inside my JS code & use that, it displays perfectly and typeof thinks it's an object.
Working Example so you can see what I'm looking for:
JSFiddle
The Code on the JS side:
Here's the Success portion of my AJAX call with a bunch of commented out versions that don't work either:
success: function (message)
{
console.log("SUCCESS: Inside processEvent AJAX success call");
console.log(message.d);
console.log(typeof message);
console.log(typeof message.d);
var data = message.d;
//this method works, but not very useful as it's hard coded:
//var data = [{ label: 'PCBID: 350', children: [{ label: 'TimeStamp: 04-Sep-14 10:30:23' }, { label: 'User: DAVEG' }, { label: 'PCBID: 350' }, { label: 'Assembly Drawing: 41411' }, { label: 'PCB Drawing: 10348' }, { label: 'Vendor: SBE' }, { label: 'PO Number: 98019' }] }, { label: 'Serial Number: Not Assigned' }, { label: 'Assembly Drawing: 41411' }, { label: 'Last Test Result: None Found' }];
var data = $.getJSON(message.d);
//var data = { JSON.parse(message.d) };
//var data = ({}).valueOf.call($.parseJSON(message.d));
//var data = object.create(message.d);
console.log(typeof data);
console.log(data);
$(function ()
{
$('#tree1').tree({
data: data,
autoOpen: false,
saveState: true,
dragAndDrop: true
});
});
The Question:
So after all that, my question is, how do I take the string from the AJAX message.d and turn it into an object so that jqTree can use it to render the tree I'm looking for?
Working Code:
I've added back in some of the success user informing stuff (jGrowl) so don't let that throw you. The bit of code that fixed it is here: data = eval($.parseJSON(message.d));
success: function (message)
{
console.log("SUCCESS: Inside processEvent AJAX success call");
console.log(message.d);
//if it's a non query event, do this
if (DTO.eventData.eventType != "PCBID_query")
{
$.jGrowl("\nSuccessfully inserted a " + DTO.eventData.eventType + " event into the MySQL database.",
{ header: 'SUCCESS', theme: "pcb-success", life: 10000 });
}
//if processData was used for a PCBID query, process this code
if (DTO.eventData.eventType === "PCBID_query")
{
var data = {};
data = eval($.parseJSON(message.d));
$(function ()
{
//force reload of tree data
$('#tree1').tree('loadData', data);
$('#tree1').tree({
data: data,
autoOpen: false,
saveState: true,
dragAndDrop: true
});
});
}
I know the eval is evil & presents a security hole, however, this is all internal code that'll only be used on local servers & production floor computers so I think the risk is acceptable (as does my manager).
eval(data) would work in this case, but using eval() is usually a security issue, especially when getting data from public areas, such as user submitted SQL data.
The best solution would be to look for a real way to export JSON from your ASP.NET. Without knowing your code, a little googling shows there are solutions out there.
Related
I'm having trouble binding my JavaScript kendo ui grid to model data from an action method. All the examples i see are mostly MVC wrappers and the JavaScript examples are all different and none seem to work for me.
Here is where i'm at below.
I did a generic test with static data that works.
var dataSource_Test = new kendo.data.DataSource({
data: [{ LeagueDetailGroupId: "15", GroupName: "Best Team 5"}]
});
Here is the datasource object im trying to create with the controller action:
var dataSource = new kendo.data.DataSource({
transport: {
read: {
url: "#Url.Action("LeagueDetailGroup_Read", "Configuration")?_leagueTypeId=" + leagueTypeId,
// i have tried all kinds of variants here, and not sure what to put
// my action method is returning json using kendo's DataSourceResult method
//contentType: "application/json",
type: "POST"
//dataType: "odata"
},
schema: {
data: "Data", // seen this in examples, dunno what it does
total: "Total", // seen this in examples, dunno what it does
model: {
id: "LeagueDetailGroupId",
fields: {
LeagueDetailGroupId: { editable: false, nullable: true },
GroupName: { validation: { required: true } }
}
}
},
// i seen this is an example from telerik but dont understand the use case for it
parameterMap: function (data, operation) {
// this prints no data before i even start so its a moot point configuring it from products to my stuff at this moment
// but not sure what todo here of if i need this anyways
console.log(data);
if (operation != "read") {
// post the products so the ASP.NET DefaultModelBinder will understand them
var result = {};
for (var i = 0; i < data.models.length; i++) {
var product = data.models[i];
for (var member in product) {
result["products[" + i + "]." + member] = product[member];
}
}
return result;
} else {
return JSON.stringify(data)
}
}
}
});
Here is the grid which works ok with the generic static datasouce object.
var grid = $("#leagueEdit_ldg_grid").kendoGrid({
dataSource: dataSource,
sortable: true,
pageable: true,
autobind: false,
//detailInit: leagueEdit_ldg_detailInit,
dataBound: function () {
this.expandRow(this.tbody.find("tr.k-master-row").first());
},
columns: [
{
field: "LeagueDetailGroupId",
title: "Group Id",
width: "110px"
},
{
field: "GroupName",
title: "Group Name",
width: "110px"
}
]
});
Delayed read, autobind set to false.
dataSource.read();
Here is my simplified Controller action. It runs and gets data, and works fine for my MVC wrapper grids.
[Route("LeagueDetailGroup_Read/{_leagueTypeId:int}")]
public ActionResult LeagueDetailGroup_Read([DataSourceRequest]DataSourceRequest request, int _leagueTypeId = -1)
{
DataSourceResult result =
_unitOfWork.FSMDataRepositories.LeagueDetailGroupRepository.Get(
ld => ld.LeagueTypeId == _leagueTypeId
)
.ToDataSourceResult(request,
ld => new LeagueDetailGroupViewModel
{
LeagueDetailGroupId = ld.LeagueDetailGroupId,
LeagueTypeId = ld.LeagueTypeId,
GroupName = ld.GroupName,
DateCreated = ld.DateCreated,
DateLastChanged = ld.DateLastChanged
}
);
// data looks fine here
return Json(result, JsonRequestBehavior.AllowGet);
}
Currently i'm getting this error:
Uncaught TypeError: e.slice is not a function
at init.success (kendo.all.js:6704)
at success (kendo.all.js:6637)
at Object.n.success (kendo.all.js:5616)
at i (jquery-3.1.1.min.js:2)
at Object.fireWith [as resolveWith] (jquery-3.1.1.min.js:2)
at A (jquery-3.1.1.min.js:4)
at XMLHttpRequest.<anonymous> (jquery-3.1.1.min.js:4)
It's hard to know without testing but let me know how this works.
Change your controller so that you are just returning a json string.
Also, try removing your schema and the parameter map, and set your dataType to json:
var dataSource = new kendo.data.DataSource({
transport: {
read: {
url: "#Url.Action("LeagueDetailGroup_Read", "Configuration")?_leagueTypeId=" + leagueTypeId,
dataType: "json"
}
}
});
For the grid I find simple json data does not usually need a schema/model defined. Kendo is super annoying and hard to debug. Let me know how it goes.
In my experience, an e.slice error happens when you have a record that has a null value in it somewhere. The kendo grid is not really smart enough to deal with this so you either have to make sure your datasource returns empty strings instead of nulls for string fields, or put a client template on the columns that translates a null into an empty string. It's possible that the kendo todatasourceresult made the problem come to light. Note that that is usually the last step before returning your dataset since it can modify the entity queries to give paging, so that you never query more than a single page of data (for ajax grids).
I'm working with this bootstrap library and actually everything works fine. The question is, Can bootstrap-table generate header automatically in depend of JSON file? I've tried to find any information about that, but unlucky. Now my header is generated from script like from this example:
function initTable() {
$table.bootstrapTable({
height: getHeight(),
columns: [{
field: 'field1',
title: 'title1',
sortable: true
}, {
field: 'field2',
title: 'title2',
sortable: true
}, {
field: 'field3',
title: 'title3',
sortable: true
}, {
field: 'Actions',
title: 'Item Operate',
align: 'center',
events: operateEvents,
formatter: operateFormatter
}
],
formatNoMatches: function () {
return "This table is empty...";
}
});
Does anyone how to generate header automatically?
Populating from a flat json file is definetly possible but harder than from a seperate (slimmer and preped) data request, because title and other attributes 'might' have to be guessed at.
Ill show basic approach, then tell you how to make it work if stuck with a flat file that you CAN or CANT affect the format of (important point, see notes at end).
Make a seperate ajax requests that populates var colArray = [], or passes direct inside done callback.
For example, in callback (.done(),.success(), ect) also calls to the function that contains the js init code for the table.
You might make it look something like this:
function initTable(cols) {
cols.push({
field: 'Actions',
title: 'Item Operate',
align: 'center',
events: operateEvents,
formatter: operateFormatter
});
$("#table").bootstrapTable({
height: getHeight(),
columns: cols,
formatNoMatches: function () {
return "This table is empty...";
}
});
}
$(document).ready(function(){
$.ajax({
method: "POST",
url: "data/getColumns",
// data: { context: "getColumns" }
datatype: "json"
})
.done(function( data ) {
console.log( "getCols data: ", data );
// Prep column data, depending on what detail you sent back
$.each(data,function(ind,val){
data.sortable = true;
});
initTable(data);
});
});
Now, if you are in fact stuck with a flat file, point the ajax towards that then realise the question is whether you can edit the contents.
If yes, then add a columns array into it with whatever base data (title, fieldname, ect) that you need to help build your columns array. Then use responseHandler if needed to strip that columns array if it causes issues when loading into table.
http://bootstrap-table.wenzhixin.net.cn/documentation/#table-options
http://issues.wenzhixin.net.cn/bootstrap-table/ (click 'see source').
If no, you cant edit contents, and only have the fieldname, then look at using that in the .done() handler with whatever string operation (str_replace(), ect) that you need to make it look the way you want.
Ok I'm pretty sure I know exactly what I need to do here but I'm not sure how to do it. Basically I have a grid that I want to make a key column bind to an array of key/values, which I've done before with kendo (not using Angular) and I know that when I'm creating my key/value array asynchronously then that needs to complete before I can get them show-up with kendo, which I have done using promises before.
So here I have the same issue only angular is also involved. I need to fetch and format an array of data into the format in which a kendo grid column can digest it, so no problem here is my controller code:
var realm = kendo.data.Model.define({
id: 'realmID',
fields: {
realmID: { editable: false, nullable: true }
realmType: { type: 'string', validation: { required: true } }
}
})
var ds1 = kendoHelpers.dataSourceFactory('realms', realm, 'realmID')
var realmType = kendo.data.Model.define({
id: 'realmTypeID',
fields: {
realmTypeID: { editable: false, nullable: true },
name: { type: 'string', validation: { required: true } }
}
})
var ds2 = kendoHelpers.dataSourceFactory('realms/types', realmType, 'realmTypeID')
$scope.mainGridOptions = {
dataSource: ds1,
editable: true,
navigatable: true,
autoBind:false,
toolbar: [
{ name: "create" },
{ name: 'save' },
{ name: 'cancel' }
],
columns: [
{ field: 'realmID', title: 'ID' }
{ field: 'realmTypeID', title: 'Realm Type', editor: realmTypesDDL, values: $scope.realmTypeValues },
{ command: "destroy" }
]
}
$scope.secondGridOptions = {
dataSource: ds2,
editable: true,
navigatable: true,
toolbar: [
{ name: "create" },
{ name: 'save' },
{ name: 'cancel' }
],
columns: [
{ field: 'realmTypeID', title: 'ID' },
{ field: 'name', title: 'Name' }
{ command: "destroy" }
]
}
ds2.fetch(function () {
$scope.realmTypeValues = [{ text: 'Test', value: "24bc2e62-f761-4e70-804c-bc36fdeced3d" }];
//this.data().map(function (v, i) {
// $scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID})
//});
//$scope.mainGridOptions.ds1.read()
});
function realmTypesDDL(container, options) {
$('<input />')
.appendTo(container)
.kendoDropDownList({
dataSource: ds2,
dataTextField: 'name',
dataValueField: 'realmTypeID'
});
}
I made this dataSourceFatory helper method above to return me a basic CRUD kendo dataSource that uses transport and also injects an authorization header which is working fine so don't get hung up on that, ultimately I'm going to be using this data in another grid as well as for reference values for the main grid, but I've hard coded some values that I can use to test with in the ds2.fetch callback.
My HTML is pretty plain:
<div>
<h2>Realms</h2>
<kendo-grid options="mainGridOptions"></kendo-grid>
<h2>Realm Types</h2>
<kendo-grid options="secondGridOptions"></kendo-grid>
</div>
This all works fine and well except I am only seeing the GUID of the realmTypeID in the grid, I click it and the editor is populated correctly so that's good but I want the text value to be displayed instead of the GUID. I'm sure the issue is that the array of values is empty whenever angular is binding to the grid options. My questions are:
How do I either delay this bind operation or manually rebind it after the fetch call?
Is there a better way to handle a situation like this? I try not to expend finite resources for no reason (IE making server calls when unnecessary)
Note: When I move the creation of the text/value array to happen before the grid options, I get the desired behavior I am after
EDIT A work around is to not use the directive to create the grid and instead defer the grid creation until the callback of whatever data your column is dependent on, I was hoping for a more elegant solution but this is better than nothing. So your HTML becomes something like
<h2>Realms</h2>
<div id="realms"></div>
<h2>Realm Types</h2>
<kendo-grid options="secondGridOptions"></kendo-grid>
Then you can create the grid in the fetch callback for example:
ds2.fetch(function () {this.data().map(function (v, i) {
$scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID})
});
$('#realms').kendoGrid($scope.mainGridOptions);
$scope.mainGridOptions.dataSource.fetch()
});
But this doesn't feel very angularish so I'm really hoping for a better solution!
Ok...well I think I hacked this enough and without another suggestion I'm going to go forward with this approach. I'm just going to move the binding logic to the requestEnd event of the second grid so that the values array can be populated right before the binding even. I'm also reworking the values array in this method. It is a bit weird though, I think there is some kendo black magic going on with this array because I can't just set it to a new empty array without it breaking completely...which is why I'm poping everything out prior to repopulating the array. That way when something is deleted or edited in the second grid, the DDL in the first grid is updated in the callback.
function requestEnd(e) {
for (var i = $scope.realmTypeValues.length; i >= 0; i--) $scope.realmTypeValues.pop();
var data;
if (e.type == "read")
data = e.response;
else
data = e.sender.data();
data.map(function (v, i) { $scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID }); });
if ($('#realms').data('kendoGrid') == undefined) {
$('#realms').kendoGrid($scope.mainGridOptions);
}
else
$('#realms').data('kendoGrid').columns[4].values = $scope.realmTypeValues;
}
ds2.bind('requestEnd', requestEnd);
So I'm going to accept my own answer unless anyone has a better approach!
I am working on an ASP.NET MVC 4 app. In my app, I'm trying to replace my drop down lists with the Select 2 plugin. Currently, I'm having problems loading data from my ASP.NET MVC controller. My controller looks like this:
public class MyController : System.Web.Http.ApiController
{
[ResponseType(typeof(IEnumerable<MyItem>))]
public IHttpActionResult Get(string startsWith)
{
IEnumerable<MyItem> results = MyItem.LoadAll();
List<MyItem> temp = results.ToList<MyItem>();
var filtered = temp.Where(r => r.Name.ToLower().StartsWith(startsWith.ToLower());
return Ok(filtered);
}
}
When I set a breakpoint in this code, I notice that startsWith does not have a value The fact that the breakpoint is being hit means (I think) my url property below is set correct. However, I'm not sure why startsWith is not set. I'm calling it from Select 2 using the following JavaScript:
function formatItem(item) {
console.log(item);
}
function formatSelectedItem(item) {
console.log(item);
}
$('#mySelect').select2({
placeholder: 'Search for an item',
minimumInputLength: 3,
ajax: {
url: '/api/my',
dataType: 'jsonp',
quietMillis: 150,
data: function (term, page) {
return {
startsWith: term
};
},
results: function (data, page) {
return { results: data };
}
},
formatResult: formatItem,
formatSelection: formatSelectedItem
});
When this code runs, the only thing I see in the select 2 drop down list is Loading failed. However, I know my api is getting called. I can see in fiddler that a 200 is coming back. I can even see the JSON results, which look like this:
[
{"Id":1,"TypeId":2,"Name":"Test", "CreatedOn":"2013-07-20T15:10:31.67","CreatedBy":1},{"Id":2,"TypeId":2,"Name":"Another Item","CreatedOn":"2013-07-21T16:10:31.67","CreatedBy":1}
]
I do not understand why this isn't working.
From the documentation:
Select2 provides some shortcuts that make it easy to access local data
stored in an array...
... such an array must have "id" and "text" keys.
Your json object does not contain "id" or "text" keys :) This should work though i have not tested it:
results: function (data, page) {
return { results: data, id: 'Id', text: 'Name' }
}
There's further documentation following the link on alternative key binding... I believe thats where your problem lies.
Hopefully this helps.
Until now I've been using Select2's normal AJAX method of searching and filtering data serverside, but now I have a usecase where I want to retrieve all the results via AJAX when the select is opened and then use those results (now stored locally) to search and filter.
I've trawled the web looking for examples of how to do this and all i've found is lots of people saying the Query method should be used rather than the AJAX helper. Unfortunately I haven't found any examples.
So far all I've managed is the basic:
$('#parent').select2({
placeholder: "Select Parent",
minimumInputLength: 0,
allowClear: true,
query: function (query) {
//console.log(query);
query.callback(data);
}
});
data = {
more: false,
results: [
{ id: "CA", text: "California" },
{ id: "AL", text: "Alabama" }
]
}
Data is being passed to the select but query filtering is not implemented.
I'm struggling to understand the select2 documentation and would appreciate any assistance or links to examples.
Try the following - pre-load json data into local variable and when ready bind this to select2 object
<script>
function format(item) { return item.text; }
var jresults;
$(document).ready(function() {
$.getJSON("http://yoururl.com/api/select_something.json").done(
function( data ) {
$.jresults = data;
$("#parent").select2(
{formatResult: format,
formatSelection: format,
data: $.jresults }
);
}
)
});
</script>