Multi-Property lookup with Backbone and Underscore - javascript

I have a method on my collection called activeCall that takes an id as a parameter. This method does a _find with underscore and returns the first match. What I want to do is pass multiple parameters to the method, then have the .get() function use those params.
Here's what I currently have:
var Activities = Backbone.Collection.extend({
model: Activity,
url: "/activity",
activeCall: function (Activity_id) {
return _.find(this.models, function (item) {
return item.get("Activity_id") === Activity_id;
});
}
});
Here is what I'd like to do:
var Activities = Backbone.Collection.extend({
model: Activity,
url: "/activity",
activeCall: function (Activity_id, CallType_id) {
return _.find(this.models, function (item) {
//How do I use both Activity_id & CallType_id with the call to item.get()?
return item.get("Activity_id") === Activity_id && item.get("CallType_id") === CallType_id;
});
}
});

Well, turns out return item.get("Activity_id") === Activity_id && item.get("CallType_id") === CallType_id; actually works. I tried it just for kicks earlier and it did not work. I must have been doing something wrong because it's working now.

Related

Need to Implement Custom Search in Select2 by using Asynchronous Function

I am developing wrapper over Select2 plugin to make a reusable Blazor Component. I have integrated it's basic usage in Blazor using the JSInterop.
Now, I am stuck in a problem while integrating custom search by using a custom function for the matcher.
Actually, everything works fine but I am not getting the results as I am bound to use async function for the matcher. I have to use
var result = await dotNetReference.invokeMethodAsync(searchMethodName, params.term, filterItems);
to get the searched results from a JSInvokable search method.
The method is hitting correctly and returning the results properly. But, as the matcher function in the Select2 is synchronous and my custom function is asynchronous, it doesn't show the results in the SelectBox because the interpreter doesn't wait for the C# method to return the result.
I am sharing my Select2 init code below, so that anyone can help me to get a solution:
$(id).select2({
placeholder: placeholder,
allowClear: isClearAllowed,
minimumResultsForSearch: minSearchResults,
minimumInputLength: minimumInputLength,
maximumInputLength: maximumInputLength,
matcher: async function (params, data) {
if ($.trim(params.term) === '') {
return data;
}
if (typeof data.children !== 'undefined') {
//Grouped Item
var filterItems = [];
data.children.forEach(function (e) {
filterItems.push({ id: e.id, text: e.text, isDisabled: e.disabled, selected: e.selected });
});
var result = await dotNetReference.invokeMethodAsync(searchCallback, params.term, filterItems);
if (result.length) {
var modifiedData = $.extend({}, data, true);
modifiedData.children = data.children.filter(function (x) {
return result.some((r) => r.id === x.id);
});
return modifiedData;
}
}
else if (typeof data.id !== 'undefined' && data.id != "") {
//UnGrouped Item
}
//No Item
return null;
}
});
I am C# developer who knows little about the JavaScript that's why I might missing something here. Blazor also provides non-async function to invoke the C# method but that is only available in WebAssembly. I am making this plugin to be available for both Blazor Server and WebAssembly.
I will be grateful if someone help me over making an async call for a sync function.
So, after trying for several hours, I have found and implemented a solution which works well.
First, I created a custom SelectAdapter and made the required methods async. I had to implement some different logic to get the sequential results. I did a change in Select.prototype.matches function and added await keyword with the matcher function call. In Select.prototype.query function I also changed the way of calling the self.matches(params, option) function for each option. I took the help from this blog post and used the GitHub repository to find out the original functions.
$.fn.select2.amd.define("CustomSelectAdapter", ["select2/utils", "select2/data/select", "select2/data/minimumInputLength", "select2/data/maximumInputLength"], function (Utils, Select, MinimumInputLength, MaximumInputLength) {
//changed the matches function to async
Select.prototype.matches = async function (params, data) {
var matcher = this.options.get('matcher');
return await matcher(params, data); //added await which will call our defined matcher function asynchronously
};
//changed query function to async
Select.prototype.query = async function (params, callback) {
var data = [];
var self = this;
var $options = this.$element.children();
for (var i in $options) {
if (!$options[i].tagName || ($options[i].tagName.toLowerCase() !== 'option' && $options[i].tagName.toLowerCase() !== 'optgroup')) {
break;
}
var $option = $($options[i]);
var option = self.item($option);
var matches = await self.matches(params, option); //added await to call the asynced matcher function
if (matches !== null) {
data.push(matches);
}
}
callback({
results: data
});
};
var customSelectAdapter = Utils.Decorate(Select, MinimumInputLength);
customSelectAdapter = Utils.Decorate(customSelectAdapter, MaximumInputLength);
return customSelectAdapter;
});
After creating the custom adapter by using the above code, I assigned the adapter to dataAdapter property (according to the official documentation).
$(id).select2({
placeholder: placeholder,
allowClear: isClearAllowed,
minimumResultsForSearch: minSearchResults,
minimumInputLength: minimumInputLength,
maximumInputLength: maximumInputLength,
dataAdapter: $.fn.select2.amd.require("CustomSelectAdapter"), //assinged the custom created adapter
matcher: async function (params, data) {
.....
}
});

Perform a set of recursive HTTP GET calls, and wait for them all

I have a REST service, offering a list of 'Json' objects, and each object may potentially have a link for another resource of its own class. Starting with a particular one, I need to fetch them all, performing a recursive http call.
So I wrote:
var steps = [];
var recursiveLookup = function(processId) {
return $.ajax({
url: SERVER_URL + processId,
success: function (activity) {
// Activity has a set of json objects called steps
var rtn = activity.path.map(step => {
if (step.type != "Node") {
steps.push(step);
} else {
return recursiveLookup(step.subProcessIntanceId);
}
}).filter(a => a != undefined);
return rtn;
}
});
}
That would correctly load all objects into the global steps var.
I need to be sure the method has finished, so I wrote:
var promises = recursiveLookup(processId);
Promise.all(promises).then(function () {
console.log(steps);
});
But it's not working, as the 'recursiveLookup' is returning the promise of $.ajax, instead of the set of promises pretended to be returned with the success method.
Furthermore, is it possible to get the steps as a returned value from the 'recursiveLookup' method instead, of using it as a global variable?
Nested recursion is not within my confort zone but maybe this will work:
var recursiveLookup = function(processId,steps=[]) {
return $.ajax({
url: SERVER_URL + processId,
})
.then(
function (activity) {
// Activity has a set of json objects called steps
steps = steps.concat(
activity.path.filter(
step => step.type !== "Node"
)
);
return Promise.all(
activity.path.filter(
step => step.type === "Node"
)
.map(
step=>
recursiveLookup(step.subProcessIntanceId,steps)
)
).then(
result=>steps.concat(result)
)
}
);
}
For tail call optimization to work the last thing the function does should be to call the recursive function but I think in promise chains it doesn't matter too much.
You should not use the success parameter if you want to work with promises. Instead, you want to return a promise, and you want to use then to transform the results of a promise into something different, possibly even another promise.
function request(page) {
…
// return the AJAX promise
return $.ajax({
url: '/echo/json/',
method: 'POST',
dataType: 'json',
data: {
delay: 1,
json: JSON.stringify(ret)
}
});
}
function requestOddsFrom(page, items) {
return request(page).then(function(data){
if (data.currentPage > data.totalPage) {
return items;
} else {
var filtered = data.items.filter(function(el){ return el%2 == 1; });
return requestOddsFrom(data.currentPage + 1, items.concat(filtered));
}
});
}
function requestAll(){
return requestOddsFrom(1, []);
}
requestAll().then(function(items) {
console.dir(items);
});
for more info jQuery Recursive AJAX Call Promise
How do I return the response from an asynchronous call?

Maintaining a resource collection with ngResource, socket.io and $q

I am trying to create an AngularJS factory that maintains a collection of resources automatically by retrieving the initial items from the API and then listening for socket updates to keep the collection current.
angular.module("myApp").factory("myRESTFactory", function (Resource, Socket, ErrorHandler, Confirm, $mdToast, $q, $rootScope) {
var Factory = {};
// Resource is the ngResource that fetches from the API
// Factory.collection is where we'll store the items
Factory.collection = Resource.query();
// manually add something to the collection
Factory.push = function(item) {
Factory.collection.push(item);
};
// search the collection for matching objects
Factory.find = function(opts) {
return $q(function(resolve, reject) {
Factory.collection.$promise.then(function(collection){
resolve(_.where(Factory.collection, opts || {}));
});
});
};
// search the collection for a matching object
Factory.findOne = function(opts) {
return $q(function(resolve, reject) {
Factory.collection.$promise.then(function(collection){
var item = _.findWhere(collection, opts || {});
idx = _.findIndex(Factory.collection, function(u) {
return u._id === item._id;
});
resolve(Factory.collection[idx]);
});
});
};
// create a new item; save to API & collection
Factory.create = function(opts) {
return $q(function(resolve, reject) {
Factory.collection.$promise.then(function(collection){
Resource.save(opts).$promise.then(function(item){
Factory.collection.push(item);
resolve(item);
});
});
});
};
Factory.update = function(item) {
return $q(function(resolve, reject) {
Factory.collection.$promise.then(function(collection){
Resource.update({_id: item._id}, item).$promise.then(function(item) {
var idx = _.findIndex(collection, function(u) {
return u._id === item._id;
});
Factory.collection[idx] = item;
resolve(item);
});
});
});
};
Factory.delete = function(item) {
return $q(function(resolve, reject) {
Factory.collection.$promise.then(function(collection){
Resource.delete({_id: item._id}, item).$promise.then(function(item) {
var idx = _.findIndex(collection, function(u) {
return u._id === item._id;
});
Factory.collection.splice(idx, 1);
resolve(item);
});
});
});
};
// new items received from the wire
Socket.on('new', function(item){
idx = _.findIndex(Factory.collection, function(u) {
return u._id === item._id;
});
if(idx===-1) Factory.collection.push(item);
// this doesn't help
$rootScope.$apply();
});
Socket.on('update', function(item) {
idx = _.findIndex(Factory.collection, function(u) {
return u._id === item._id;
});
Factory.collection[idx] = item;
// this doesn't help
$rootScope.$apply();
});
Socket.on('delete', function(item) {
idx = _.findIndex(Factory.collection, function(u) {
return u._id === item._id;
});
if(idx!==-1) Factory.collection.splice(idx, 1);
});
return Factory;
});
My backend is solid and the socket messages come through correctly. However, the controllers don't respond to updates to the collection if any of the Factory methods are used.
i.e.
This works (responds to socket updates to the collection):
$scope.users = User.collection;
This does not work (it loads the user initially but is not aware of updates to the collection):
User.findOne({ _id: $routeParams.user_id }).then(function(user){
$scope.user = user;
});
How can I get my controllers to respond to update to changes to the collection?
Update:
I was able to implement a workaround in the controller by changing this:
if($routeParams.user_id) {
User.findOne({ _id: $routeParams.user_id }).then(function(user){
$scope.user = user;
});
}
To this:
$scope.$watchCollection('users', function() {
if($routeParams.user_id) {
User.findOne({ _id: $routeParams.user_id }).then(function(user){
$scope.user = user;
});
}
});
However, nobody likes workarounds, especially when it involves redundant code in your controllers. I am adding a bounty to the question for the person who can solve this inside the Factory.
The solution is for the factory methods to return an empty object/array to be populated later (similar to the way ngResource works). Then attach the socket listeners to both those return objects/arrays and the main Factory.collection array.
angular.module("myApp").factory("myRESTFactory",
function (Resource, Socket, ErrorHandler, Confirm, $mdToast, $q) {
var Factory = {};
// Resource is the ngResource that fetches from the API
// Factory.collection is where we'll store the items
Factory.collection = Resource.query();
// This function attaches socket listeners to given array
// or object and automatically updates it based on updates
// from the websocket
var socketify = function(thing, opts){
// if attaching to array
// i.e. myRESTFactory.find({name: "John"})
// was used, returning an array
if(angular.isArray(thing)) {
Socket.on('new', function(item){
// push the object to the array only if it
// matches the query object
var matches = $filter('find')([item], opts);
if(matches.length){
var idx = _.findIndex(thing, function(u) {
return u._id === item._id;
});
if(idx===-1) thing.push(item);
}
});
Socket.on('update', function(item) {
var idx = _.findIndex(thing, function(u) {
return u._id === item._id;
});
var matches = $filter('find')([item], opts);
// if the object matches the query obj,
if(matches.length){
// and is already in the array
if(idx > -1){
// then update it
thing[idx] = item;
// otherwise
} else {
// add it to the array
thing.push(item);
}
// if the object doesn't match the query
// object anymore,
} else {
// and is currently in the array
if(idx > -1){
// then splice it out
thing.splice(idx, 1);
}
}
});
Socket.on('delete', function(item) {
...
});
// if attaching to object
// i.e. myRESTFactory.findOne({name: "John"})
// was used, returning an object
} else if (angular.isObject(thing)) {
Socket.on('update', function(item) {
...
});
Socket.on('delete', function(item) {
...
});
}
// attach the socket listeners to the factory
// collection so it is automatically maintained
// by updates from socket.io
socketify(Factory.collection);
// return an array of results that match
// the query object, opts
Factory.find = function(opts) {
// an empty array to hold matching results
var results = [];
// once the API responds,
Factory.collection.$promise.then(function(){
// see which items match
var matches = $filter('find')(Factory.collection, opts);
// and add them to the results array
for(var i = matches.length - 1; i >= 0; i--) {
results.push(matches[i]);
}
});
// attach socket listeners to the results
// array so that it is automatically maintained
socketify(results, opts);
// return results now. initially it is empty, but
// it will be populated with the matches once
// the api responds, as well as pushed, spliced,
// and updated since we socketified it
return results;
};
Factory.findOne = function(opts) {
var result = {};
Factory.collection.$promise.then(function(){
result = _.extend(result, $filter('findOne')(Factory.collection, opts));
});
socketify(result);
return result;
};
...
return Factory;
};
The reason this is so so great is that your controllers can be ridiculously simple yet powerful at the same time. For example,
$scope.users = User.find();
This returns an array of ALL users that you can use in your view; in an ng-repeat or something else. It will automatically be updated/spliced/pushed by updates from the socket and you don't need to do anything extra to get that. But wait, there's more.
$scope.users = User.find({status: "active"});
This will return an array of all active users. That array will also automatically be managed and filtered by our socketify function. So if a user is updated from "active" to "inactive", he is automatically spliced from the array. The inverse is also true; a user that gets updated from "inactive" to "active" is automatically added to the array.
The same is true for the other methods as well.
$scope.user = User.findOne({firstname: "Jon"});
If Jon's email changes, the object in the controller is updated. If his firstname changes to "Jonathan", $scope.user becomes an empty object. Better UX would be to do soft-delete or just mark the user deleted somehow, but that can be added later.
No $watch, $watchCollection, $digest, $broadcast, required--it just works.
Don't expose the collection property on Factory, keep it as a local variable.
Create a new exposed getter/setter on the Factory that proxies to and from the local variable.
Use the getter/setter Object internally in your find methods.
Something like this:
// internal variable
var collection = Resource.query();
// exposed 'proxy' object
Object.defineProperty(Factory, 'collection', {
get: function () {
return collection;
},
set: function (item) {
// If we got a finite Integer.
if (_.isFinite(item)) {
collection.splice(item, 1);
}
// Check if the given item is already in the collection.
var idx = _.findIndex(Factory.collection, function(u) {
return u._id === item._id;
});
if (idx) {
// Update the item in the collection.
collection[idx] = item;
} else {
// Push the new item to the collection.
collection.push(item);
}
// Trigger the $digest cycle as a last step after modifying the collection.
// Can safely be moved to Socket listeners so as to not trigger unnecessary $digests from an angular function.
$rootScope.$digest();
}
});
/**
* Change all calls from 'Factory.collection.push(item)' to
* 'Factory.collection = item;'
*
* Change all calls from 'Factory.collection[idx] = item' to
* 'Factory.collection = item;'
*
* Change all calls from 'Factory.collection.splice(idx, 1) to
* 'Factory.collection = idx;'
*
*/
Now, seeing as how the non angular parties modify your collection (namely Sockets in this case), you will need to trigger a $digest cycle to reflect the new state of the collection.
If you are only ever interested in keeping the collection in sync in a single $scope (or multiple ones, but not cross-scope) I would attach said $scope's to the factory, and run the $digest there instead of $rootScope. That will save you a little bit of performance down the line.
here's a jsbin showcasing how the usage of an Object.getter will keep your collection in sync and allow you to find items recently added to the collection.
I've opted for setTimeout in the jsbin so as to not trigger automatic $digests through the usage of $interval.
Obviously the jsbin is very barebones; There's no promises being shuffled around, no socket connections. I only wanted to showcase how you can keep things in sync.
I will admit that Factory.collection = value looks whack, but you could hide that away with the help of wrapping functions to make it more pretty / read better.

Ember Data model find by query deferred?

If I have a query that defines the model for my route:
App.MyRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('MyModel', {myProperty: myValue}).then(function(results) {
return results.get('firstObject');
});
}
});
How do I handle the case where the store has not yet been populated with the results of the above query?
I want the query to update the model with the results of the above query whenever a value is inserted into the store that satisfies the query.
Is this possible?
NOTE: I'm using the FixtureAdapter as I'm not using a REST back end and I don't need the persistence layer. As such, I have implemented the findQuery method myself as follows:
App.ApplicationAdapter = DS.FixtureAdapter.extend({
queryFixtures: function(records, query, type) {
return records.filter(function(record) {
for(var key in query) {
if (!query.hasOwnProperty(key)) { continue; }
var value = query[key];
if (record[key] != value) { return false; }
}
return true;
});
}
});
I am absolutely open to changing this up if it will help me reach this end.
Use a filter, it is a live record array that updates as new records are injected into the store. As a side note, filter doesn't make a call to the server for records, so if you still want that to happen you'll want to still make the find call, and then just return the filter.
App.MyRoute = Ember.Route.extend({
model: function (params) {
// trigger a find
this.store.find('MyModel', {myProperty: myValue});
// return a live filter (updates when the store updates)
return this.store.filter('MyModel', function(record){
return record.get('myProperty') == myValue;
});
}
});

Returning a value from 'success' block in JS (Azure Mobile Service)

I have a rather simple getUser method that I'm having some trouble with. I am not deeply familiar with scopes and such in JS so this is giving me a head ache. Basically I want to fetch an object from the database and return it to the calling method:
function getUser(uid)
{
var result = null;
var userTable = tables.getTable('Users');
userTable.where({
userId: uid
}).read({
success: function (results) {
if (results.length > 0) {
result = results[0];
console.log('userid'+result.id);
}
}
});
console.log('userid-'+result.id); // undefined!!
return result;
}
Also, returning from inside the success doesn't return from getUser, but just the function defined inside. I tried "result = function(results)" as well but it stores the defined function and not the return value.
How am I supposed to do this?
I found a solution to this elsewhere. In practice (to the best of my understanding), it is not possible to do this within a JavaScript with asynchronous functions. What you need to do is create a recursion instead from inside the success handler.
Because the call to the database is asynchronous, your last two lines are executed (and hence result is undefined) before the call the database actually finishes. So you need to handle everything inside your success callback. Or, if your getUser() func is a helper, you could structure your code (without recursion) like this with a callback:
function insertOrWhateverCallingMethod()
{
var uid = 'blah';
getUser(uid,function(user) {
// Do something with the user object
});
}
function getUser(uid,callback)
{
var result = null;
var userTable = tables.getTable('Users');
userTable.where({
userId: uid
}).read({
success: function (results) {
if (results.length > 0) {
result = results[0];
console.log('userid'+result.id);
callback(result);
}
}
});
callback(null);
}
The code above assumes you're in a table script, where the tables object is available - if it's not you can pass it as a parameter to getUser().

Categories

Resources