How to use Q.js with breeze - breeze

This is my first attempt at using q.js. It appears to work, I have data being retrieved and then my function is called. The problem is the data is not being passed to the function. Is it a syntax problem or am I misusing Q?
getCategories = function (observable) {
var query = breeze.EntityQuery
.from("Categories")
.orderBy('Order');
Q(executeLocalQuery(query))
.then(processResult);
function processResult(data) { //data = undefined
if (data.results.length)
return observable(data.results)
else
return observable(create('Item', { CategoryId: id, Name: 'Add12', ImageName: 'icon.png', Order: '999' })); //create add thumbnail if zero records
};
},
executeLocalQuery = function (query) {
manager.executeQuery(query.using(breeze.FetchStrategy.FromLocalCache))
.then(localFetchSucceeded)
.fail(queryFailed);
function localFetchSucceeded(data) {
return data;
}
},

Solved it! No need to use Q when at executeLocalQuery. Changes are in the called function. Deferred waits for data.results and the resolves the defer. Then a promise is returned back to the caller. The caller can then process the .then and retrieve the data.
getCategories = function (observable) {
var query = breeze.EntityQuery
.from("Categories")
.orderBy('Order');
executeLocalQuery(query)
.then(processResult);
function processResult(data) {
if (data.results.length)
return observable(data.results)
else {
var addThumbnail = create('Category', { CategoryId: generateGUID(), Name: 'Add', ImageName: 'icon.png', Order: '999' });
observable(addThumbnail);
}
};
},
executeLocalQuery = function (query) {
var deferred = Q.defer();
manager.executeQuery(query.using(breeze.FetchStrategy.FromLocalCache))
.then(localFetchSucceeded)
.fail(queryFailed);
function localFetchSucceeded(data) {
deferred.resolve(data);
};
return deferred.promise;
},

Related

indexeddb on IOS devices

I have a problem with an indexeddb query with index when running on IOS devices.
$.indexedDB(dbName).objectStore(tablename).index("INDICE").each(function(itemLocal) {
itemLocal.delete();
}, [VALORINDICE]).then(function() {
callback();
}, function() {
console.log("error");
});
The problem is if there is more than one record that matches the index, it does not eliminate them, it eliminates the first one and leaves. But if for example I put console.log (itemLocal) instead of itemLocal.delete() if it shows all those that match the index. Any suggestions of something that may be leaking?
I have tried with this code and I get the same error(code without api jquery)
var request = indexedDB.open(DATABASE_NAME);
request.onsuccess = function(event) {
var db = request.result;
var transaction = db.transaction(["TABLE"], "readwrite");
var table = transaction.objectStore("TABLE");
var index = table.index("INDEX");
var req = index.openCursor();
req.onsuccess = function() {
var cursor = req.result;
if (cursor) {
console.info(cursor.value);
cursor["delete"]();
cursor["continue"]();
}
};
req.onerror = function(e) {
console.error(e, req);
};
};
request.onerror = function(e) {
console.error(e, request);
};

How to import entities after save changes with breeze across two entity managers

I've implemented repository pattern with two entity managers,
mainManager is for read only and delete, and updateManager is used for edit and add new entities. I use createEmptyCopy() to create updateManager.
Before i update an entity i export the entity from mainManager and import into the updateManager, after the change i call to updateManager.saveChanges() method.
I've noticed that i get back the updated entities in the promise response. i wonder what is the best practice to import those entities back into the mainManager?
here is my code:
function ($q, $http, entityManagerFactory) {
var self = this;
self.mainManager = entityManagerFactory.newManager();
self.updateManager = entityManagerFactory.newManager();
self.saveChanges = function () {
return self.updateManager.saveChanges();
};
self.rejectChanges = function() {
self.updateManager.rejectChanges();
};
self.getDomains = function () {
self.mainManager.clear();
var query = new breeze.EntityQuery()
.from('Domains')
.orderBy('name');
return self.mainManager.executeQuery(query);
};
self.createEmptyDomain = function () {
var domain = self.updateManager.createEntity('Domain');
return domain;
};
self.editDomain = function(domain) {
var exported = self.mainManager.exportEntities([domain]);
return self.updateManager.importEntities(exported).entities[0];
}
self.addDomain = function (domain) {
self.updateManager.addEntity(domain);
return self.updateManager.saveChanges();
};
self.deleteDomain = function (domain) {
domain.entityAspect.setDeleted();
var deferred = $q.defer();
self.mainManager.saveChanges().then(
function(data) {
deferred.resolve(data);
},
function (reason) {
console.log(reason);
self.mainManager.rejectChanges();
deferred.reject(reason);
});
return deferred.promise;
};
}
Right now i'm calling mainManager.clear() and get the data again from the server as you can see above in getDomains function.
But i think this is too expansive, why call the server if i already have the updated entities from the saveChanges promise?
i've also tried to import those entities back to mainManager using:
mainManager.importEntities(data.entities, { mergeStrategy: breeze.MergeStrategy.OverwriteChanges });
but i get an internal null breeze exception:
TypeError: Cannot read property 'forEach' of undefined
at EntityManager.proto.importEntities (breeze.debug.js:13081)
at self.importEntities (domain-list.service.js:22)
at domain-list.controller.js:70
at processQueue (angular.js:13170)
at angular.js:13186
at Scope.promises.$get.Scope.$eval (angular.js:14383)
at Scope.promises.$get.Scope.$digest (angular.js:14199)
at Scope.promises.$get.Scope.$apply (angular.js:14488)
at done (angular.js:9646)
at completeRequest (angular.js:9836)
the error is from this line breeze.debug.js:13081
13080: var tempKeyMap = {};
13081: json.tempKeys.forEach(function (k) {
13082: var oldKey = EntityKey.fromJSON(k, that.metadataStore);
13083: // try to use oldKey if not already used in this keyGenerator. 13084: tempKeyMap[oldKey.toString()] = new EntityKey(oldKey.entityType,
13085: that.keyGenerator.generateTempKeyValue(oldKey.entityType, oldKey.values[0]));
13086: });
var exportData = updateManager.exportEntities(data.entities, false);
mainManager.importEntities(exportData,
{ mergeStrategy: breeze.MergeStrategy.OverwriteChanges });

Failing Parse background job when using beforesave with thousands of objects

I am using a background job to query a json with thousands of objects to initially populate my database. I have also implemented the beforesave function to prevent any duplicate entries. However, once I implemented this, it seems my background job called response.error and does not save all objects. It looks like I might be exceeding the requests/sec? I would really appreciate if someone could take a look at my code and tell me why it is not saving all entries successfully.
Here is my background job:
Parse.Cloud.job("testing", function(request, response) {
var json;
Parse.Cloud.httpRequest({
url: stringURL + pageNumber.toString(),
success: function(httpResponse) {
json = httpResponse.data;
console.log("total is: " + json["meta"].total);
console.log("object 1 is: " + json["events"][1].title);
return json;
}
//after getting the json, save all 1000
}).then(function() {
//helper function called
saveObjects(json).then(function() {
response.success("success");
},
function(error) {
response.error("nooooo");
});
});
});
function saveObjects(json) {
var promises = [];
for(var i = 0; i < 1000; i++) {
var newEvent = new Event();
promises.push(newEvent.save(new Event(json["events"][i])));
}
return Parse.Promise.when(promises);
}
Here is my beforesave code:
Parse.Cloud.beforeSave("Event", function(request, response) {
var newEvent = request.object;
var Event = Parse.Object.extend("Event");
var query = new Parse.Query("Event");
query.equalTo("title", newEvent.get("title"));
query.equalTo("datetime_utc", newEvent.get("datetime_utc"));
query.equalTo("url", newEvent.get("url"));
query.first({
success: function(temp) {
response.error({errorCode:123,errorMsg:"Event already exist!"});
},
error: function(error) {
response.success();
}
});
});
Thanks I really appreciate any help... I've been stuck for a while.
If it's a request rate issue, then you could probably use something like node-function-rate-limit but it's fairly simple to write your own rate limiting batcher. See doInBatches() below.
Also, when using promise-returning methods that also offer a "success:..." callback, it's better not to mix the two styles. It may behave as expected but you are denied the opportunity to pass results from the "success:..." callback to the rest of the promise chain. As you can see below, the "success:..." code has simply been shuffled into the .then() callback.
Parse.Cloud.job("testing", function(request, response) {
Parse.Cloud.httpRequest({
url: stringURL + pageNumber.toString()
}).then(function(httpResponse) {
var json = httpResponse.data;
// console.log("total is: " + json.meta.total);
// console.log("object 1 is: " + json.events[1].title);
/* helper function called */
doInBatches(json.events, 30, 1000, function(evt, i) {
var newEvent = new Event();
return newEvent.save(new Event(evt));
}).then(function() {
response.success('success');
}, function(error) {
response.error('nooooo');
});
});
});
// Async batcher.
function doInBatches(arr, batchSize, delay, fn) {
function delayAsync() {
var p = new Parse.Promise();
setTimeout(p.resolve, delay);
return p;
}
function saveBatch(start) {
if(start < arr.length) {
return Parse.Promise.when(arr.slice(start, start+batchSize).map(fn))
.then(delayAsync) // delay between batches
.then(function() {
return saveBatch(start + batchSize);
});
} else {
return Parse.Promise.as();
}
}
return saveBatch(0);
}
I can't see how or why the beforesave code might affect things.

breeze observableArray binding - are properties observable?

I have a viewmodel which consists of a list(foreach loop) of DoctorPrices and when clicking on an item in the list it open up a CRUD form on the side. However when i update the values on the CRUD the observableArray that is bound to the foreach is not refreshing? (although the values are updates in the DB correctly)
From my data access module i call the following query.
function getDoctorServices(doctorId) {
var query = breeze.EntityQuery
.from('DoctorPrices')
.where('DoctorID', 'eq', doctorId).orderBy('ListOrder');
return manager.executeQueryLocally(query);
}
In my viewmodel i have the following code:
this.services = ko.computed(function() {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
});
services is bound using a foreach loop (not posting here as the code is simple and works)
When i click on a one of the DoctorPrices it gets the data as follows and places it in an observable:
this.selectedPrice = function (data, event) {
self.currentService(data);
self.showEdit(true);
};
I then bind selectPrice to a simple form that has the properties on it to be modified by the user. I then call manager.SaveChanges().
This results in the following problem: the value is being updated correctly but the GUI / Original List that is bound in the foreach is not being updated? Are the properties in breeze not observables? What is the best way to work with something like this.
I thought of a workaround and changing the code with something like this:
doctorList.viewModel.instance.currentDoctorID.subscribe(function() {
self.services([]);
self.services(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
});
But i feel that clearing the array in that way is sloppy and not the right way of doing things specially with long lists.
Can someone please point me in the right direction on how to bind observableArray properties properly so they are updated?
Additional code my VM Component:
function services() {
var self = this;
this.showForm = ko.observable(false);
this.currentService = ko.observable();
this.services = ko.observableArray(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
this.title = ko.observable();
doctorList.viewModel.instance.currentDoctorID.subscribe(function() {
self.services([]);
self.services(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
self.showDetails(false);
});
this.show = function (value) {
self.showForm(value);
};
this.showDetails = ko.observable(false);
this.addNewService = function() {
self.currentService(doctorServices.createService(doctorList.viewModel.instance.currentDoctorID()));
console.log(self.currentService().entityAspect.entityState);
self.showDetails(true);
};
this.showDelete = ko.computed(function() {
if (self.currentService() == null)
return false;
else if (self.currentService().entityAspect.entityState.isDetached()) {
self.title('Add new service');
return false;
} else {
self.title('Edit service');
return true;
}
});
this.deleteService = function() {
self.currentService().entityAspect.setDeleted();
doctorServices.saveChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
};
this.closeDetails = function () {
doctorServices.manager.rejectChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
self.showDetails(false);
};
this.selectService = function (data, event) {
self.currentService(data);
self.showDetails(true);
};
this.saveChanges = function () {
console.log(self.currentService().entityAspect.entityState);
if (self.currentService().entityAspect.entityState.isDetached()) {
doctorServices.attachEntity(self.currentService());
}
console.log(self.currentService().entityAspect.entityState);
doctorServices.saveChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
self.currentService.notifySubscribers();
self.showDetails(true);
};
}
return {
viewModel: {
instance: new services()
},
template: servicesTemplate,
};
Below is my Breeze Data Class:
define('data/doctorServices', ['jquery', 'data/dataManager', 'knockout','mod/medappBase', 'breeze', 'breeze.savequeuing'], function ($, manager, ko,base, breeze, savequeuing) {
var services = ko.observableArray([]);
return {
attachEntity:attachEntity,
getServices: getServices,
services: services,
manager:manager,
getDoctorServices: getDoctorServices,
getServiceById: getServiceById,
createService:createService,
hasChanges: hasChanges,
saveChanges: saveChanges
};
function getServices() {
var query = breeze.EntityQuery.from("DoctorPrices");
return manager.executeQuery(query).then(function (data) {
services(data.results);
}).fail(function (data) {
console.log('fetch failed...');
console.log(data);
});;
}
function getDoctorServices(doctorId) {
var query = breeze.EntityQuery
.from('DoctorPrices')
.where('DoctorID', 'eq', doctorId).orderBy('ListOrder');
var set = manager.executeQueryLocally(query);
return set;
}
function getServiceById(serviceId) {
return manager.createEntity('DoctorPrice', serviceId);
//return manager.getEntityByKey('DoctorPrice', serviceId);
}
function handleSaveValidationError(error) {
var message = "Not saved due to validation error";
try { // fish out the first error
var firstErr = error.innerError.entityErrors[0];
message += ": " + firstErr.errorMessage;
base.addNotify('error', 'Could not save.', message);
} catch (e) { /* eat it for now */ }
return message;
}
function hasChanges() {
return manager.hasChanges();
}
function attachEntity(entity) {
manager.addEntity(entity);
}
function createService(doctorId) {
return manager.createEntity('DoctorPrice', { DoctorPricingID: breeze.core.getUuid(), DoctorID:doctorId }, breeze.EntityState.Detached);
};
function saveChanges() {
return manager.saveChanges()
.then(saveSucceeded)
.fail(saveFailed);
function saveSucceeded(saveResult) {
base.addNotify('success', 'Saved.', 'Your updates have been saved.');
}
function saveFailed(error) {
var reason = error.message;
var detail = error.detail;
if (error.innerError.entityErrors) {
reason = handleSaveValidationError(error);
} else if (detail && detail.ExceptionType &&
detail.ExceptionType.indexOf('OptimisticConcurrencyException') !== -1) {
// Concurrency error
reason =
"Another user, perhaps the server, " +
"may have deleted one or all of the settings." +
" You may have to restart the app.";
} else {
reason = "Failed to save changes: " + reason +
" You may have to restart the app.";
}
console.log(error);
console.log(reason);
}
}
});
Please note this is my frist attempt at both a data class and VM. At the moment i am relying heavily on clearing the array ([]) and using notifySubscribers to make the array refresh :(
I bet you're missing an observable somewhere. I can't tell because you keep hopping from property to property whose definition is not shown.
For example, I don't know how you defined this.currentService.
I'm confused by this:
this.services = ko.computed(function() {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
});
Why is it a ko.computed? Why not just make it an observable array.
self.service = ko.observableArray();
// ... later replace the inner array in one step ...
self.service(doctorServices.getDoctorServices(
doctorList.viewModel.instance.currentDoctorID()));
I urge you to follow the observability trail, confident that your Breeze entity properties are indeed observable.
vm.selectedPrice = ko.dependentObservable(function () {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
}, vm);
vm is ur model on which u applied bindings , try this it will work.

knockout validation - advanced search user interface

I am building an advanced search UI similar to the TFS query builder web interface. Using knockout for the client side implementation and have everything more or less working except the final validation to make certain required items are basically selected. It sort-of works as far as giving me a validation error if I select an item and then de-select the item. Which is fine, but I would like to have the form validate when hitting the search button.
I am pretty sure I need to make use of the ko.validatedobservable method, I'm just not sure exactly how. Anyway, I have a fiddle to look at: http://jsfiddle.net/sstolp/uXBSA/ if anyone has the time or inclination to help me out. I would deeply appreciate it.
Thank you for your time.
scvm.SearchLine = function () {
var self = this;
self.selectedField = ko.observable().extend({ required: true });
self.selectedOperator = ko.observable().extend({ required: true });
self.firstdate = ko.observable(new Date());
self.lastdate = ko.observable(new Date());
self.thedate = ko.observable(new Date());
return self;};
scvm.Criteria = function () {
var self = this,
lines = ko.observableArray([]),
// Put one line in by default
loadInitialData = function () {
lines.push(new scvm.SearchLine());
},
rowcount = ko.computed(function () {
return lines().length;
}),
// Operations
addLine = function () {
lines.push(new scvm.SearchLine());
},
removeLine = function (line) {
lines.remove(line);
},
search = function () {
var data = $.map(lines(), function (line) {
return line.selectedField() ? {
selectedField: line.selectedField().searchfield,
selectedOperator: line.selectedOperator().name,
} : undefined
});
alert("Send to server: " + JSON.stringify(data));
},
clear = function () {
lines.removeAll();
};
return {
lines: lines,
loadInitialData: loadInitialData,
rowcount: rowcount,
addLine: addLine,
removeLine: removeLine,
search: search,
clear: clear
};
}();
Yes, all your SearchLine objects must be wrapped into ko.validatedObservable. Also you should implement computed property which will check isValid() for each criteria line and return global validity flag.
scvm.SearchLine = function () {
var self = this;
self.selectedField = ko.observable().extend({ required: true });
self.selectedOperator = ko.observable().extend({ required: true });
self.firstdate = ko.observable(new Date());
self.lastdate = ko.observable(new Date());
self.thedate = ko.observable(new Date());
return ko.validatedObservable(self);
};
scvm.Criteria = function () {
// ...
return {
lines: lines,
loadInitialData: loadInitialData,
rowcount: rowcount,
addLine: addLine,
removeLine: removeLine,
search: search,
clear: clear,
// new property that indicates validity of all lines
linesValid: ko.computed(function(){
var items = lines();
for (var i = 0, l = items.length; i < l; i++)
if (!items[i].isValid()) return false;
return true;
})
};
}();
This new property can be used in enable binding of you "Search" button:
<input type="button"
data-bind="enable: linesValid, click: search"
title="Clicking this button will run a search."
value="Search" />
I've modified your fiddle. Take a look: http://jsfiddle.net/ostgals/uXBSA/8/
Update:
Also we should slightly modify Criteria.search method, since our line array contains observables rather than objects:
//...
search = function () {
var data = $.map(lines(), function (line) {
line = ko.utils.unwrapObservable(line);
return line.selectedField() ? {
selectedField: line.selectedField().searchfield,
selectedOperator: line.selectedOperator().name,
} : undefined
});
alert("Send to server: " + JSON.stringify(data));
},
//...

Resources