How do I reset a collection in Titanium Alloy? - jasmine

A Backbonejs collection has a function to reset a collection for bulk updates. I would like to use this feature in Titanium Alloy when I sync with JSON data from a server but it appears as if this is not being committed/saved to SQLite - I am using an sql adapter.
config: {
columns: {
// stuff
name: "TEXT"
},
adapter: {
type: "sql",
collection_name: "pony",
db_name: Alloy.CFG.db_name
}
}
I have some jasmine tests which keep failing. FYI I have migration script for development that adds 7 items to the collection so that I have something to work with.
describe("pony model", function () {
var Alloy = require("alloy")
data = {name: "My little pony"},
collection,
item;
beforeEach(function(){
collection = Alloy.createCollection('pony');
item = Alloy.createModel('pony');
});
// PASSES
it('can reset all data', function () {
collection.fetch();
expect(collection.length).toEqual(7);
collection.reset(data)
expect(collection.length).toEqual(1);
})
// FAILS
it('saves reset data', function () {
collection.fetch();
expect(collection.length).toEqual(7);
collection.reset(data)
collection.fetch()
expect(collection.length).toEqual(1);
})
afterEach(function () {
item.destroy();
});
})
The way this bug shows in the UI is that when I save that when I sync data with the server the TableView shows the new records then when I go to another view and come back to the same TableView the synced data is gone and replaced with the default data.

The best approach I found (and I shamefully can't remember where I copied the code from) was to do the reset manually. I posted the code to do this: https://gist.github.com/sukima/8321859
Basically I do my own SQL DELETE then a Backbone reset(), then looped INSERT INTO, finally finishing with a backbone trigger("fetch") event. Doing this via backbone's sync was way to slow. And the normal reset() doesn't run sync anyways.
exports.definition = {
config: {
columns: {
// ...
},
adapter: {
type: "sql",
collection_name: "MyModels"
}
},
extendCollection: function(Collection) {
Collection.prototype.destroyAll = function(opt) {
var db = Ti.Database.open(this.config.adapter.db_name);
db.execute("DELETE FROM " + this.config.adapter.collection_name);
db.close();
this.models = [];
if (!opt || !opt.silent) { this.trigger("reset"); }
return this;
};
Collection.prototype.saveAll = function(opt) {
var util = require("alloy/sync/util");
var dbName = this.config.adapter.db_name;
var table = this.config.adapter.collection_name;
var columns = this.config.columns;
var db = Ti.Database.open(dbName);
db.execute("BEGIN;");
this.forEach(function (model) {
if (!model.id) {
model.id = util.guid();
model.attributes[model.idAttribute ] = model.id;
}
var names = [], values = [], q = [];
for (var k in columns) {
names.push(k);
values.push(model.get(k));
q.push("?");
}
var sqlInsert = "INSERT INTO " + table + " (" + names.join(",") + ") VALUES (" + q.join(",") + ");";
db.execute(sqlInsert, values);
});
db.execute("COMMIT;");
db.close();
if (!opt || !opt.silent) { this.trigger("reset"); }
return this;
};
Collection.prototype.refreshFromData = function refreshFromData(data) {
this.destroyAll({silent:true});
this.reset(data, {silent:true});
this.saveAll({silent: true});
this.trigger("fetch");
};
}
};

Related

failing to reset language selection after sync

I am facing a problem which I am not aware how to resolve. Let me describe elaborately below:
I have a commonViewModel kendo class where event like save, cancel are written. I am facing problem with the save event of this class.
save: function () {
var that = this;
var routeLanguage = "";
that._showBackConfirmation(false);
that.set("isFormSubmitted", true);
console.log("form is valid, sending the save request!");
if (vm.get("languageTabsVm.selectedLanguage")) {
routeLanguage = "/" + vm.get("languageTabsVm.selectedLanguage");
}
else if (that.get("model.Languages") && that.get("model.Languages").length > 1) {
that.get("model.Languages").forEach(function (lang) {
if (lang.get("IsActive") === true) {
//sätt cv-visning till det språk jag senast redigerade på detta item
routeLanguage = "/" + lang.LanguageId;
}
});
}
//if i call the function _loadDefaultLanguageSelection here, it
// works. because, the datasource is not synced yet.
//Make sure the datasource are syncing changes to the server (includes all crud)
return that.dataSource.sync().fail(function (e) {
//i need to do something here to be in the same language tab. But
//as i am changing directly in to model, it is not possible. But
//saving directly to model is essential because that model is
//shared to other viewmodel for language tab synching purpose.
that.set("isFormSubmitted", false);
console.log("form rejected");
}).done(function () {
if (that.get("isPersonaldetail")) {
var name = that.get("model.Name");
if (name.length > 12)
name = name.substring(0, 11) + "...";
$("#profileName").text(name);
}
that.set("isFormSubmitted", false);
that.set("isSelected", false);
// it is called from here right now. but it is failing because
// model is updated but not synced in that function
that._loadDefaultLanguageSelection();
router.navigate(that.nextRoute + routeLanguage);
});
},
_loadDefaultLanguageSelection: function () {
var that = this;
if (that.get("model.Languages") && that.get("model.Languages").length > 1) {
that.get("model.Languages").forEach(function (lang) {
if (!that.get("isPersonaldetail")) {
lang.set("IsActive", lang.get("LanguageId") === vm.get("languageTabsVm.selectedLanguage"));
}
});
}
},
So, my question is, how can i resolve this problem. one solution is i will have to sync twice. that is not nice. So, I am looking for efficient solution.

Adding contraints to a column on Parse Data

I'm saving some objects into tables on my Parse Data. But I need to add a constraint or make sure that the data i'm trying to insert is unique. I'm using something like the following code. But i want to guarantee that the eventId (that I'm getting from facebook API) is unique in my tables, so i don't have any redundant information. What is the best way to make it work?
var Event = Parse.Object.extend("Event");
var event = new Event();
event.set("eventId", id);
event.set("eventName", name);
event.save(null, {
success: function(event) {
console.log('New object created with objectId: ' + event.eventId);
},
error: function(event, error) {
console.log('Failed to create new object, with error code: ' + error.message);
}
});
Update:
I'm calling it inside a httpRequest. The following is pretty much what I have and I cant figure out just how to call a beforeSave inside it.
Parse.Cloud.define("hello", function(request, response) {
var query = new Parse.Query("Location");
query.find({
success: function(results) {
console.log(results);
var totalResults = results.length;
var completedResults = 0;
var completion = function() {
response.success("Finished");
};
for (var i = 0; i < totalResults; ++i){
locationId = results[i].get("locationFbId");
Parse.Cloud.httpRequest({
url: 'https://graph.facebook.com/v2.2/'+locationId+'/events?access_token='+accessToken,
success: function(httpResponse) {
console.log(httpResponse.data);
console.log("dsa"+locationId);
for (var key in httpResponse.data) {
var obj = httpResponse.data[key];
for (var prop in obj) {
var eventObj = obj[prop];
if (typeof(eventObj) === 'object' && eventObj.hasOwnProperty("id")) {
var FbEvent = Parse.Object.extend("FbEvent");
var fbEvent = new FbEvent();
fbEvent.set("startDate",eventObj["start_time"]);
fbEvent.set("locationFbId", locationId);
fbEvent.set("fbEventId", eventObj["id"]);
fbEvent.set("fbEventName", eventObj["name"]);
Parse.Cloud.beforeSave("FbEvent", function(request, response) {
var query = new Parse.Query("FbEvent");
query.equalTo("fbEventId", request.params.fbEventId);
query.count({
success: function(number) {
if(number>0){
response.error("Event not unique");
} else {
response.success();
}
},
error: function(error) {
response.error(error);
}
});
});
}
}
}
completedResults++;
if (completedResults == totalResults) {
completion();
}
},
error:function(httpResponse){
completedResults++;
if (completedResults == totalResults)
response.error("Failed to login");
}
});
}
},
error: function() {
response.error("Failed on getting locationId");
}
});
});
So this is occurring in Cloud Code correct? (Im assuming since this is Javascript)
What you could do is create a function that occurs before each "Event" object is saved and run a query to make sure that the event is unique (query based off of "eventId" key, not objectId since the id comes from Facebook). If the event is unique, return response.success(), otherwise return response.error("Event not unique")
EX:
Parse.Cloud.beforeSave("Event", function(request, response) {
if(request.object.dirty("eventId")){
var query = var new Parse.Query("Event");
query.equalTo("eventId", request.object.eventId);
query.count({
success: function(number) {
if(number>0){
response.error("Event not unique");
} else {
response.success();
}
},
error: function(error) {
response.error(error);
}
});
} else {
response.success();
}
});
Parse.Cloud.define("hello", function(request, response) {
var query = new Parse.Query("Location");
query.find({
success: function(results) {
console.log(results);
var totalResults = results.length;
var completedResults = 0;
var completion = function() {
response.success("Finished");
};
for (var i = 0; i < totalResults; ++i){
locationId = results[i].get("locationFbId");
Parse.Cloud.httpRequest({
url: 'https://graph.facebook.com/v2.2/'+locationId+'/events?access_token='+accessToken,
success: function(httpResponse) {
console.log(httpResponse.data);
console.log("dsa"+locationId);
for (var key in httpResponse.data) {
var obj = httpResponse.data[key];
for (var prop in obj) {
var eventObj = obj[prop];
if (typeof(eventObj) === 'object' && eventObj.hasOwnProperty("id")) {
var FbEvent = Parse.Object.extend("FbEvent");
var fbEvent = new FbEvent();
fbEvent.set("startDate",eventObj["start_time"]);
fbEvent.set("locationFbId", locationId);
fbEvent.set("fbEventId", eventObj["id"]);
fbEvent.set("fbEventName", eventObj["name"]);
// Our beforeSave function is automatically called here when we save it (this will happen every time we save, so we could even upgrade our method as shown in its definition above)
fbEvent.save(null, {
success: function(event) {
console.log('New object created with objectId: ' + event.eventId);
},
error: function(event, error) {
console.log('Failed to create new object, with error code: ' + error.message);
}
});
}
}
}
completedResults++;
if (completedResults == totalResults) {
completion();
}
},
error:function(httpResponse){
completedResults++;
if (completedResults == totalResults)
response.error("Failed to login");
}
});
}
},
error: function() {
response.error("Failed on getting locationId");
}
});
});
This can also be accomplished before ever calling the save by querying and only saving if the query returns with a number == 0.
Summary: For those joining later, what we are doing here is checking to see if an object is unique (this time based on key eventId, but we could use any key) by overriding Parse's beforeSave function. This does mean that when we save our objects (for the first time) we need to be extra sure we have logic to handle the error that the object is not unique. Otherwise this could break the user experience (you should have error handling that doesn't break the user experience anyway though).

Got promise not working

I'm trying to use promise to get in promise2
But if I have an object Widgets with several elements in it...
Why can't I have been able to get my console.log's output
Parse.Cloud.define("extract", function(request, response) {
var user = request.params.user;
var promise = Parse.Promise.as();
[...]
}).then(function() {
return query.find().then(function(results) {
_.each(results, function(result) {
[...]
Widget.objectId = result.id;
Widgets[timestamp] = Widget;
});
return promise;
}).then(function(results) {
for (var key in Widgets) {
var Widget = Widgets[key];
var widget_data = Widgets[key].widget_data;
var promise2 = Parse.Promise.as();
promise2 = promise2.then(function() {
return Parse.Cloud.run('extractWidgetData', {
'widget_data': widget_data,
}).then(function(newresult) {
Widgets[key].data = newresult.data;
console.log('--------WHY NOT HERE ALL TIME ?--------');
});
});
return promise2;
}
}).then(function() {
response.success(Widgets);
},
function(error) {
response.error("Error: " + error.code + " " + error.message);
});
});
});
I'm becoming crazy to run this damn Code
EDIT : I finally followed Roamer's advices to implement something but I'm not sure if it's the good way to work with Promise in series...
Parse.Cloud.define("extract", function(request, response) {
var user = request.params.user;
var Widgets = {};
...
... .then(function() {
return query.find().then(function(results) {
return Parse.Promise.when(results.map(function(result) {
var Widget = ...;//some transform of `result`
Widget.id = ...;//some transform of `result`
var timestamp = createdAtDate.getTime();
...
return Parse.Cloud.run('extractData', {
'widget_data': Widget.widget_data,
}).then(function(newresult) {
Widget.stat = newresult.stats;
return Widget;//<<<<<<< important! This ensures that results.map() returns an array of promises, each of which delivers a Widget objects.
});
}));
}).then(function() {
var promisedWidget = Array.prototype.slice.apply(arguments);
return Parse.Promise.when(promisedWidget.map(function(Widget) {
return Parse.Cloud.run('getWineStats', {
'id': Widget.data.id
}).then(function(stat) {
Widget.stat = stat;
return Widget;
});
}));
}).then(function() {
var promisedWidget = Array.prototype.slice.apply(arguments);
_.each(promisedWidget, function(Widget) {
var createdAtObject = Widget.createdAt;
var strDate = createdAtObject.toString();
var createdAtDate = new Date(strDate);
timestamp = createdAtDate.getTime();
Widgets[timestamp] = Widget;
});
return Widgets;
}).then(function(Widgets) {
response.success(Widgets);
},
function(error) {
response.error("Error: " + error.code + " " + error.message);
});
});
});
First, I echo Bergi's comment on indentation/matching parenthesis.
But ignoring that for a moment, at the heart of the code you have return query.find().then(...).then(...).then(...) but the flow from the first .then() to the second is incorrect. Besides which, only two .then()s are necessary as the code in the first then is synchronous, so can be merged with the second.
Delete the two lines above for (var key in Widgets) { then at least Widgets will be available to be processed further.
Going slightly further, you should be able to do all the required processing of results in a single loop. There seems to be little pont in building Widgets with _.each(...) then looping through the resulting object with for (var key in Widgets) {...}.
In the single loop, you probably want a Parse.Promise.when(results.map(...)) pattern, each turn of the map returning a promise of a Widget. This way, you are passing the required data down the promise chain rather than building a Widgets object in an outer scope.
Do all this and you will end up with something like this :
Parse.Cloud.define("extract", function(request, response) {
var user = request.params.user;
...
... .then(function() {
return query.find().then(function(results) {
return Parse.Promise.when(results.map(function(result) {
var Widget = ...;//some transform of `result`
...
return Parse.Cloud.run('extractWidgetData', {
'widget_data': Widget.widget_data,
}).then(function(newresult) {
Widget.data = newresult.data;
return Widget;//<<<<<<< important! This ensures that results.map() returns an array of promises, each of which delivers a Widget objects.
});
}));
}).then(function() {
//Here, compose the required Widgets array from this function's arguments
var Widgets = Array.prototype.slice.apply(arguments);//Yay, we got Widgets
response.success(Widgets);
}, function(error) {
response.error("Error: " + error.code + " " + error.message);
});
});
});

Kendo UI grid databinding using mvvm pattern

I am new to Kendo UI, i want to bind kendo ui grid using MVVM pattern, and my datasource is sharepoint list. so i am calling sharepoint list data through CSOM javascript code. I tried different solution but nothing seems to working. I have collection of data from sharepoint list.
var divisionListData = [];
//var divisionsViewModel;
var viewModel = kendo.observable({
isVisible: true,
onSave: function (e) {
alert('hi');
kendoConsole.log("event :: save(" + kendo.stringify(e.values, null, 4) + ")");
},
divisions: new kendo.data.DataSource({
// schema: {
data: divisionListData,
schema: {
data: "rows",
model: {
fields:
{
ID: { type: "string" },
DivisionName: { type: "string" },
DivisionCode: { type: "string" },
OpenDate: { type: "datetime" },
CloseDate: { type: "datetime" },
Description: { type: "string" },
}
}
},
batch: true,
transport: {
read: function (e) {
return divisionListData;
}
})
})
function ReadList() {
//this.set("isDisabled", false);
var clientContext = new SP.ClientContext.get_current();
// denote that we will be performing operations on the current web
var web = clientContext.get_web();
// denote that we will be querying the "Business Divisions" custom SharePoint list
var divisionsList = web.get_lists().getByTitle("Divisions");
// create a CAML query (blank means just return all items)
var camlQuery = new SP.CamlQuery();
// denote that the operation we want to perform is getItems() on the list
var divisionsListItems = divisionsList.getItems(camlQuery);
var fields = 'Include(ID,DivisionCode, DivisionName, OpenDate, CloseDate, Description)';
clientContext.load(divisionsListItems, fields);
clientContext.executeQueryAsync(function () {
// get the list item enumerator
var listItemEnumerator = divisionsListItems.getEnumerator();
// loop through the items in our custom
// "Divisions" SharePoint list
var listItem;
while (listItemEnumerator.moveNext()) {
var division = new Division();
// get the list item we are on
listItem = listItemEnumerator.get_current();
// get the divisions
division.ID = listItem.get_item("ID");
// var lookup_DivisionCode = listItem.get_item("DivisionCode").get_lookupValue();
//lookup_DivisionCode.get_l
var divisionLookupField = new SP.FieldLookupValue();
divisionLookupField = listItem.get_item("DivisionCode");
//var test = divisionLookupField.$2d_1;
if (divisionLookupField != null)
division.DivisionCode = divisionLookupField.$2d_1;
division.DivisionName = listItem.get_item("DivisionName");
division.Description = listItem.get_item("Description");
division.OpenDate = listItem.get_item("OpenDate");
division.CloseDate = listItem.get_item("CloseDate");
divisionListData.push(division);
kendo.bind($("body"), viewModel);
}
})
}
You are pretty close, instead of returning the array inside the read: function(e), you need to call
e.success(yourArrayOfData);

MVVM binding to a Kendo Grid is VERY slow?

I am trying to bind a ViewModel to a Kendo DataSource which in turn is given to a Kendo Grid. Nothing too fancy at this point.
It sort of works but is VERY slow! I have an alert informing me that I have received my json data (700 rows) within 2 seconds but it then takes around 15 seconds to update the viewmodel.
What am I doing wrong?
Thanks
$(document).ready(function () {
// create the viewmodel we use as the source for the list
var viewModel = kendo.observable({
items: [],
total: function () {
return this.get("items").length;
}
});
var dataSource2 = new kendo.data.DataSource({
data: viewModel,
pageSize: 50
});
// create the grid
$("#grid").kendoGrid({
dataSource: dataSource2,
height: 500,
scrollable: {
virtual: true
},
columns: [
{ field: "ID_ORDER", title: "ID", width: 80 },
{ field: "CREATION_DATE", title: "Creation Date" },
{ field: "STATUS", title: "STATUS", width: 80 },
** more columns (around 10) **
]
});
// pass this on to initialise
APPS.View.Orders.Initialise(viewModel);
});
Then in my typescript I am handling the Initialise call where the viewModel is passed in:
module APP.View.Orders {
export var _Scope: string = "Orders";
var _viewModelOrders: any;
export var Initialise = function (viewModelOrders: any) {
_viewModelOrders = viewModelOrders;
var orderdetails = {
userid: APP.Core.userID,
context: "DEAL"
};
// retrieve all orders
$.getJSON("/api/omsapi/GetOrders", orderdetails, function (mydata) {
try {
alert("item count (1): " + mydata.length);
jQuery.each(mydata, function () {
var newItem = this;
_viewModelOrders.items.push(newItem);
});
alert("item count (2): " + _viewModelOrders.items.length);
}
catch (e) {
alert(e.message);
}
});
}
}
Try building the item array and then assign it into the model.
Something like:
// retrieve all orders
$.getJSON("/api/omsapi/GetOrders", orderdetails, function (mydata) {
try {
alert("item count (1): " + mydata.length);
var items = [];
jQuery.each(mydata, function () {
items.push(this);
});
_viewModelOrders.items = items;
alert("item count (2): " + _viewModelOrders.items.length);
}
catch (e) {
alert(e.message);
}
});
You can suspend the observable temporarily by doing the following:
$.getJSON("/api/omsapi/GetOrders", orderdetails, function (mydata) {
try {
var simpleArray = viewModel.items(); // get a reference to the underlying array instance of the observable
jQuery.each(mydata, function () {
items.push(this);
});
viewModel.items.valueHasMutated(); // let the observable know it's underlying data has been updated
}
catch (e) {
alert(e.message);
}
}
Doing the above technique dramatically improves loading times. I have testing this loading a few thousand rows in a reasonable time.
To explain further, this is due to the line:
_viewModelOrders.items.push(newItem);
Each time you push an item into the array, it triggers a change event, which the Grid sees and updates itself. So if you push 700 items in, you are really causing the grid to update the DOM 700 times.
It would be much better to aggregate all the items into an array, then assign the array to the DataSource, with something like:
$.getJSON("/api/omsapi/GetOrders", orderdetails, function (mydata) {
datasource2.data(mydata);

Resources