How to make the method getItem from the offlineStorage of the Kendo Datasource work with LocalForage - kendo-ui

I need to setup my datasource to use localForage to manage my offline data storage.
The problem I'm having is that localForage is asynchronous by nature, only allowing us to use a callback or a promise to retrieve data.
This is how my code is set-up:
(function () {
'use strict';
var serviceId = 'employeeDataSource';
angular.module('app').factory(serviceId, function () {
var crudServiceBaseUrl = 'Data/Employees';
var dataSource = new kendo.data.DataSource({
type: "odata",
offlineStorage: {
getItem: function () {
localforage.getItem('employees-key').then(function (value) {
console.log(value);
return JSON.parse(value);
});
},
setItem: function (item) {
if (item.length > 0) {
localforage.setItem("employees-key", JSON.stringify(item));
}
}
},
transport: {
read: {
url: crudServiceBaseUrl + "/",
dataType: "json"
},
update: {
url: function (data) {
return crudServiceBaseUrl + "(guid'" + data.EmployeeId + "')";
}
},
create: {
url: crudServiceBaseUrl
},
destroy: {
url: function (data) {
return crudServiceBaseUrl + "(guid'" + data.EmployeeId + "')";
}
}
},
batch: false,
pageSize: 5,
serverPaging: true,
schema: {
data: function (data) {
return data.value;
},
total: function (data) {
return data['odata.count'];
},
model: {
id: "EmployeeId",
fields: {
EmployeeId: { editable: false, nullable: true },
Name: { validation: { required: true } },
Email: { validation: { required: true } },
IsManager: { type: "boolean" },
MaxHolidaysPerYear: { editable: false, type: "number", validation: { min: 0, required: true } },
HolidaysLeftThisYear: { type: "number", validation: { min: 0, required: true } }
}
}
},
error: function (e) {
if (e.xhr.responseText !== undefined) {
console.log(e.xhr.responseText);
}
}
});
dataSource.online(navigator.onLine);
$(window).on("offline", function () {
dataSource.online(false);
});
$(window).on("online", function () {
dataSource.online(true);
});
return dataSource;
});
})();
When off-line, the getItem gets called, then the setItem gets call as well with an empty array, hence the:
if (item.length > 0) {
localforage.setItem("employees-key", JSON.stringify(item));
}
When the promise finally returns the off-line data (with the correct values I expected), the Grid displays no results.
This behaviour is presumably because of the promise ?
I tried the same thing with sessionStorage and its worked perfectly... i.e.:
getItem: function () {
return JSON.parse(sessionStorage.getItem("employees-key"));
},
setItem: function (item) {
sessionStorage.setItem("employees-key", JSON.stringify(item));
}
What can I do to get around this?

Just got a heads-up from Telerik
There is an issue logged in their GitHub repo about the same problem.
You can keep track on the progress here:
https://github.com/telerik/kendo-ui-core/issues/326

Related

Kendo Multiselect with pre determined values

I have looked everywhere and cant seem to get this figured out. My issue..
I have 1 kendo dropdown list and 3 kendo mutliselects. each selection filters the next object. Dropdownl => MS1 => MS2 => MS3...
At times, a user may come into this page with a predetermined set of values that need to be selected in the above objects. 1 for the DD and 1+ for the MS.
I can get the dropdown to populate and have the corrected selected item. I can get the first MS to populate with the correct values but not select the values that are needed (0 values are selected) and the next 2 dont work cause i cant get past the first MS properly. I feel like I am running into some kind of sync/async issues that I cant wrap my head around.
See code below. I have included all my data sources, object setups and functions that i felt were relevant (maybe too much). The important function is prePopulateSelectedValues. this is the one that gets called to use the values to select the DD list values. I only included one of the updateLineShopGroupMS (for example) as the other update functions are basically the same. Right now I am stuck on 2 different versions of code which both provide the same results - a promise and non-promise version which you can see in prePopulateSelectedValues.
Thank you in advance
var lineShopGroupDataSource = new kendo.data.DataSource({
serverFiltering: false,
transport: {
read: {
url: "/Base/GetLineShopGroupsFilteredByLocationGroup",
type: "GET",
dataType: "json",
data: function (e) {
return {
LocationGroupId: getDropDownLists("LocationGroupId").value()
};
}
}
}
});
var locationGroupDataSource = new kendo.data.DataSource({
transport: {
read: {
type: "GET",
dataType: "json",
url: '/Base/GetLocationGroupNames',
data: function (e) {
return {
LocationGroupId: getDropDownLists("LocationGroupId").value()
};
}
}
}
});
var substationDataSource = new kendo.data.DataSource({
transport: {
read: {
type: "GET",
dataType: "json",
url: '/Base/GetSubstationFilteredByLineShopGroup',
data: function (e) {
let ids = getMultiSelect("LineShopGroup").value();
return {
LineShopIds: ids.toString()
};
}
}
}
});
var circuitDataSource = new kendo.data.DataSource({
transport: {
read: {
type: "GET",
dataType: "json",
url: '/Base/GetCircuitFilteredBySubstation',
data: function (e) {
let ids = getMultiSelect("Substation").value();
return {
SubstationIds: ids.toString()
};
}
}
}
});
function setUpCircuit() {
$("#Circuit").kendoMultiSelect({
autoBind: false,
enable: false,
placeholder: "Select Circuit(s)...",
dataTextField: "Text",
dataValueField: "Value",
dataSource: circuitDataSource,
filter: "contains",
change: function () {
getHiddenCircuitList();
}
});
}
function setUpSubstation() {
$("#Substation").kendoMultiSelect({
autoBind: false,
enable: false,
dataTextField: "Text",
dataValueField: "Value",
placeholder: "Select Substation(s)...",
dataSource: substationDataSource,
filter: "contains",
change: function () {
if (document.getElementById('Circuit')) {
updateCircuitMS();
}
getHiddenSubstationList();
}
});
}
function setUpLineShopGroup() {
$("#LineShopGroup").kendoMultiSelect({
autoBind: false,
dataTextField: "Text",
dataValueField: "Value",
headerTemplate: "<label style='padding-left: 10px;'><input type='checkbox' id='selectAllLineShopGroups'> Select All</label>",
dataSource: lineShopGroupDataSource,
placeholder: "Select Line Shop/Group...",
change: function () {
if (document.getElementById('Substation')) {
updateSubstationMS();
}
checkAllFlag("LineShopGroup");
getHiddenLineShopList();
}
});
}
function setUpLocationGroupId() {
$("#LocationGroupId").kendoDropDownList({
autoBind: true,
dataTextField: "Text",
dataValueField: "Value",
optionLabel: "Select Location Group...",
dataSource: locationGroupDataSource,
change: function () {
if (document.getElementById('LineShopGroup')) {
updateLineShopGroupMS();
}
}
});
}
function prePopulateSelectedValues(locGroupList, lineShopGroupsList, substationList, circuitList) {
if (locGroupList !== "0") {
var locGrpDD = $('#LocationGroupId').data('kendoDropDownList');
var lineShopMS;
$("#LocGroupString").val(locGroupList);
locGrpDD.value(locGroupList);
if (document.getElementById('LineShopGroupString')) {
debugger
var promise = new Promise(function (resolve, reject) {
debugger
resolve(function () {
updateLineShopGroupMS();
});
});
promise.then(function() {
//we have our result here
debugger
var lineShopMS = $('#LineShopGroup').data('kendoMultiSelect');
return lineShopMS; //return a promise here again
}).then(function(result) {
//handle the final result
debugger
$("#LineShopGroupString").val(lineShopGroupsList);
result.value([lineShopGroupsList]);
}).catch(function (reason) {
debugger
});
}
if (document.getElementById("SubstationString")) {
updateSubstationMS();
var substationMS = $('#Substation').data('kendoMultiSelect');
$("#SubstationString").val(substationList);
substationMS.value(substationList);
}
if (document.getElementById("CircuitsString")) {
updateCircuitMS();
var circuitMS = $('#Circuit').data('kendoMultiSelect');
$("#CircuitsString").val(circuitList);
substationMS.value(circuitList);
}
}
}
function updateLineShopGroupMS() {
var msObj = getMultiSelect('LineShopGroup');
var ddValue = getDropDownLists('LocationGroupId').value();
if (ddValue !== '') {
msObj.enable();
lineShopGroupDataSource.read();
}
else {
msObj.enable(false);
msObj.value([]);
}
}

How to set ajax global event to false when using Bloodhound

i am using typeahead for autocomplete.The following code is working fine.
var employees = new Bloodhound({
datumTokenizer: function (d) { return Bloodhound.tokenizers.whitespace(d.name); },
queryTokenizer: Bloodhound.tokenizers.whitespace,
addOnBlur: true,
remote: {
url: $url + '?name=%QUERY',
global: false,
wildcard: '%QUERY',
filter: function (response) {
return $.map(response.results, function (employee) {
return {
mark: employee.MARK,
actitle: employee.ACCOUNTTITLE,
code: employee.CODE
}
});
}
}
});
employees.initialize();
$($classname).typeahead({ highlight: true, minLength: 1, limit: 5 }, {
name: 'employees', displayKey: 'mark', source: employees.ttAdapter(), global: false
})
.on("typeahead:selected", function (obj, company1) {
//debugger;
$($retField).val(company1.actitle);
$($cField).val(company1.code);
})
.on('focusout', function (obj, company) {
//debugger;
$($classname).trigger("typeahead:first-child");
});
}
i want to set ajax global event to false.
Please help me to solve the issue
Late to the party but I figured it out. Use the transport property:
var employees = new Bloodhound({
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.name);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
addOnBlur: true,
remote: {
url: $url + '?name=%QUERY',
global: false,
wildcard: '%QUERY',
transport: function(opts, onSuccess, onError) {
var url = opts.url.split("#")[0];
var query = opts.url.split("#")[1];
$.ajax({
url: url,
data: "search=" + query,
type: "POST",
success: onSuccess,
error: onError,
global: false
})
}
filter: function(response) {
return $.map(response.results, function(employee) {
return {
mark: employee.MARK,
actitle: employee.ACCOUNTTITLE,
code: employee.CODE
}
});
}
}
});
employees.initialize();
Based on this other stackoverflow answer:

Kndo TreeView LoadOnDemand not hitting the server

for my kendo treeview, i set load on demand to true but it is not hitting the server to load childs, because the 'children' property is set for the datasource model, even its empty.
homogeneous = new kendo.data.HierarchicalDataSource({
transport: {
read: {
type: 'POST',
contentType: "application/json; charset=utf-8",
url: obj.DataSourcesURL,
dataType: "json"
},
parameterMap: function (options) {
if (!options.Id) {
options.Id = null;
}
if (options.filter) {
options.Search = options.filter.filters[0].name;
}
else {
options.Search = '';
}
return JSON.stringify(options);
}
},
schema: {
data: "d",
errors: function (response) {
console.log(response);
console.log("errors");
},
model: {
hasChildren: "hasChild",
children:'items',
id: "Id"
}
}
});
And treeView code is
$treeView.kendoTreeView({
dataTextField: 'Description',
checkboxes: {
checkChildren: true
},
dataSource: homogeneous,
loadOnDemand: loadOnDemand,
check: function () {
var checkedNodes = [];
var treeView = $treeView.data("kendoTreeView");
getCheckedNodes(treeView.dataSource.view(), checkedNodes);
setMessage(checkedNodes.length);
},
dataBound: function (e) {
if (e !== undefined)
resetCheckedNodes(e.sender.dataItems());
},
messages: {
loading: "Laden..."
},
expand: function (e) {
var dataItem = this.dataItem(e.node);
}
});
Try to set hasChildren like this in your schema:
schema: {
data: "d",
errors: function (response) {
console.log(response);
console.log("errors");
},
model: {
hasChildren: function(e) {
return e.items && e.items.length;
},
children:'items',
id: "Id"
}
}
And also from your question I am not sure do you even get any data or it is only Children data that is not returned. Maybe you could also show how your data structure look like.

webapi 2 key delta patch update

Using le framework here
http://blog.longle.net/2014/03/04/harness-the-power-of-asp-net-mvc-web-api-odata-kendo-ui-requirejs-to-build-an-easy-maintainable-spa-for-the-net-developer-published/
and here
Web API + OData - PATCH request 400 error
how to send key delta in patch update of WebAPI 2 odata where kendo datasource "batch: true"
AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Company> patch)
The key is always empty!!!
Does WebAPI odata supported by kendo?
Since it is a very specific task, I have only one tweak that is working for Repository Pattern provided by Le framework shown in the above mentioned link.
define(['kendo', 'testModel'],
function (kendo, testModel) {
var svcUrl = '/odata/modelURL';
var ds_test = new kendo.data.DataSource({
type: 'odata',
transport: {
read: {
//async: false,
url: svcUrl,
dataType: 'json'
},
update: {
url: function (data) {
return svcUrl + '(' + data.models[0].ID + ')';
},
dataType: 'json',
type: 'PATCH'
},
create: {
url: function (data) {
return svcUrl + '(' + data.models[0].ID + ')';
},
dataType: 'json',
type: 'PATCH'
},
destroy: {
url: function (data) {
return svcUrl + '(' + data.models[0].ID + ')';
},
dataType: 'json',
type: 'PATCH'
},
parameterMap: function(data, operation) {
if (operation != 'read') {
var model = kendo.stringify(data.models[0]);
return model;
};
return data.models;
}
},
batch: true,
serverPaging: true,
serverSorting: true,
serverFiltering: true,
pageSize: 10,
schema: {
data: function (data) { return data['value']; }, //{ return data.value; },
total: function (data) { return data['odata.count']; },
model: testModel
//parse: function(response) {
// var f = ds_appl_home.options.schema.model.fields;
// $.each(response, function (key, value) {
// if (!(key.toString() in f)) {
// delete response[key];
// }
// });
// return response;
//}
},
error: function (e) {
...
}
});
return ds_test;
});

Kendo UI Grid posting back already Inserted Rows again

I am running into problem, where when an Insert is completed successfully and if i continue to insert another row, in the next insert it is also sending the row that was inserted successfully earlier, so it goes like this.
On the First insert that row is posted back to webAPI and inserted successfully.
On Next Insert Two rows are sent one of them was from first step.
On third Insert it send previous two rows as well as third row and so on.
What could be the cause of this ?
This is the Code in problem.
$(document).ready(function () {
try {
var degreeYearsArray = new Array();
function GetDegreeName(dgID, degreeName) {
for (var i = 0; i < degreeYearsArray.length; i++) {
if (degreeYearsArray[i].dgID_PK == dgID) {
return degreeYearsArray[i].Name;
}
}
return degreeName;
}
var degreeYearModel = {
id: "DGYR_PK",
fields: {
DGID_FK: {
type: "number",
nullable: false,
editable: false
},
Code: {
type: "string",
validation: {
required: true,
minlength: 2,
maxlength: 160
}
},
Year: {
type: "number",
validation: {
required: true
}
},
EffectiveDate: {
type: "date",
validation: true
},
TerminationDate: {
type: "date",
validation: true
}
}
};
var baseURL = "http://localhost:3103/api/Degree";
var degreeYearTransport = {
create: {
url: baseURL + "/PostDegreeYears", // "/PutOrgSchool",
type: "POST",
// contentType: "application/json"
dataType: "json"
},
read: {
url: function () {
var newURL = "";
if (window.SelectedDegree == null)
newURL = baseURL + "/GetDegreeYears"
else
newURL = baseURL + "/GetDegreeYears?degreeID=" + window.SelectedDegree.DGID_PK;
return newURL;
},
dataType: "json" // <-- The default was "jsonp"
},
update: {
url: baseURL + "/PutDegreeYears", //"/PostOrgSchool",
type: "PUT",
// contentType: "application/json",
dataType: "json"
},
destroy: {
url: function (employee) {
return baseURL + "/deleteOrg" + employee.Id
},
type: "DELETE",
dataType: "json",
contentType: "application/json"
},
parameterMap: function (options, operation) {
try {
if (operation != "read") {
options.EffectiveDate = moment(options.EffectiveDate).format("MM-DD-YYYY");
options.TerminationDate = moment(options.TerminationDate).format("MM-DD-YYYY")
}
var paramMap = kendo.data.transports.odata.parameterMap(options);
delete paramMap.$format; // <-- remove format parameter.
return paramMap;
} catch (e) {
console.error("Error occure in parameter Map or Degree.js" + e.message);
}
}
}; //transport
var dsDegreeYears = new kendo.data.DataSource({
serverFiltering: true, // <-- Do filtering server-side
serverPaging: true, // <-- Do paging server-side
pageSize: 2,
transport: degreeYearTransport,
requestEnd: function (e) {
try {
if (e.type == "update"){
$.pnotify({
title: 'Update Sucessful',
text: 'Record was Updated Successfully',
type: 'success'
});
}
if (e.type == "create") {
$.pnotify({
title: 'Insert Sucessful',
text: 'Record was Inserted Successfully',
type: 'success'
});
}
} catch (e) {
console.error("error occured in requestEnd of dsDegreeYears datasource in DegreeYears.js" + e.message);
}
},
schema: {
data: function (data) {
return data.Items; // <-- The result is just the data, it doesn't need to be unpacked.
},
total: function (data) {
return data.Count; // <-- The total items count is the data length, there is no .Count to unpack.
},
model: degreeYearModel
}, //schema
error: function (e) {
var dialog = $('<div></div>').css({ height: "350px", overflow: "auto" }).html(e.xhr.responseText).kendoWindow({
height: "300px",
modal: true,
title: "Error",
visible: false,
width: "600px"
});
dialog.data("kendoWindow").center().open();
},
});
$("#" + gridName).kendoGrid({
dataSource: dsDegreeYears,
autoBind: false,
groupable: true,
sortable: true,
selectable: true,
filterable: true,
reorderable: true,
resizable: true,
columnMenu: true,
height: 430,
editable: "inline",
toolbar: ["create"],
pageable: {
refresh: true,
pageSizes: true,
buttonCount: 5
},
columns: [ {
field: "DGID_FK",
title: "Degree Name",
width: 140,
template: function (dataItem) {
if (window.SelectedDegree != null) {
dataItem.DGID_FK = window.SelectedDegree.DGID_PK;
return window.SelectedDegree.Name;
}
else
return "";
}
},
{
field: "Code",
title: "Code",
width: 140
},
{
field: "Year",
title: "Year",
width: 140
},
{
field: "Description",
width: 110
},
{
field: "EffectiveDate",
width: 110,
format: "{0:MM/dd/yyyy}"
},
{
field: "TerminationDate",
width: 110,
format: "{0:MM/dd/yyyy}"
},
{
command: ["edit"] , title: " ", width: "172px"
}
]
}); //grid
//Hide history pull-down menu in the top corner
$.pnotify.defaults.history = false;
$.pnotify.defaults.styling = "bootstrap";
// styling: 'bootstrap'
//styling: 'jqueryui'
} catch (e) {
console.error("Error occured in DegreeYears" + e.message);
}
}); // document
This is the Response that is sent from WebAPI
{"$id":"1","DGYR_PK":27,"DGID_FK":64,"Year":4,"Code":"4THYR","EffectiveDate":"2014-01-11T00:00:00","TerminationDate":"2014-01-11T00:00:00","CreatedByUserID_FK":1,"DateCreated":"0001-01-01T00:00:00","UpdatedByUserID_FK":1,"DateUpdated":"0001-01-01T00:00:00","RowStatusID_FK":1,"Degree":null,"DegreeYearExamSchedules":[],"User":null,"RowStatu":null,"User1":null,"DegreeYearSubjects":[]}
So i do see i am returning ID as suggested by the users in response to the question.
still wondering
After you have inserted a record, you need to return the id of that row, otherwise grid consider the previous row as a new row too.
In your create function you call the web API
baseURL + "/PostDegreeYears", // "/PutOrgSchool",
In the server side consider the below code.
public void Create(ABSModel model)
{
try
{
using (context = new Pinc_DBEntities())
{
tblAB tb = new tblAB();
tb.ABS = model.ABS;
tb.AreaFacility = model.AreaFacility;
context.tblABS.Add(tb);
Save();
model.ABSID = tb.ABSID;//this is the important line of code, you are returning the just inserted record's id (primary key) back to the kendo grid after successfully saving the record to the database.
}
}
catch (Exception ex)
{
throw ex;
}
}
please adjust the above example according to your requirement.
This will happen if you don't return the "DGYR_PK" value of the newly inserted model. The data source needs a model instance to have its "id" field set in order not to consider it "new". When you return the "ID" as a response of the "create" operation the data source will work properly.
You can check this example for fully working Web API CRUD: https://github.com/telerik/kendo-examples-asp-net/tree/master/grid-webapi-crud
Here is the relevant code:
public HttpResponseMessage Post(Product product)
{
if (ModelState.IsValid)
{
db.Products.AddObject(product);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, product);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = product.ProductID }));
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
Your primary key cannot be 0 or null. If you are using auto-incrementing values then you should invoke the "re-read" of your dataSource after post. Check the values and make sure your data values are >0. Note: I have set the default value of the PK in the model to -1 in the column model to help with this.
You can attached and respond to the requestEnd event on the DataSource.
requestEnd: function(e) {
if (e.type === "create") {
this.read();
}
}
What that is saying is: "After you created a record, reread the entries (including the newly created one) into the grid". Thereby giving you the newly created entry with key and all. Of course you see that the extra read may have some performance issue.

Resources