Underscore indexOf within a Collection - backbone.js

I've been looking already around for the answer with no luck.
Basically I am reviewing again the Addy Osmani guide to Backbone, and it seems I can't get through this code here.
var people = new Backbone.Collection;
people.comparator = function(a, b) {
return a.get('name') < b.get('name') ? -1 : 1;
};
var tom = new Backbone.Model({name: 'Tom'});
var rob = new Backbone.Model({name: 'Rob'});
var tim = new Backbone.Model({name: 'Tim'});
people.add(tom);
people.add(rob);
people.add(tim);
console.log(people.indexOf(rob) === 0); // true
console.log(people.indexOf(tim) === 1); // true
console.log(people.indexOf(tom) === 2); // true
I don't see how people.comparator can reorder the collection even though is not called anywhere, plus how comes that returning 1 or -1 can reorder it.
Or is it implicitly called once the Collection is created or indexOf is called on the Collection itself?

From the backbone documentation:
By default there is no comparator for a collection. If you define a
comparator, it will be used to maintain the collection in sorted
order. This means that as models are added, they are inserted at the
correct index in collection.models.
So every time you call people.add(...) the collection uses the comparator that you have set with people.comparator = function(a, b) { ... } to insert the model in an ordered position.

Related

'504 - Gateway Timeout' when Indexing the items in Episerver Find

When Indexing the items, it fails sometimes and it gives,
The remote server returned an error: (504) Gateway Timeout. [The remote server returned an error: (504) Gateway Timeout.]
The Indexing logic is here as below,
var client = EPiServer.Find.Framework.SearchClient.Instance;
List<ItemModel> items = getItems(); // Get more than 1000 items
List<ItemModel> tempItems = new List<ItemModel>();
//Index 50 items at a time
foreach(var item in items)
{
tempItems.Add(item);
if (tempItems.Count == 50)
{
client.Index(tempItems);
tempItems.Clear();
}
}
What causes this to happen ?
Note: The above mentioned ItemModel is a custom model which is not implemented interfaces (such as IContent). And the items is a list of ItemModel objects.
Additional info:
EPiServer.Find.Framework version 13.0.1
EPiServer.CMS.Core version 11.9.2
I always figured the SearchClient to be a bit sketchy when manipulating data in Find, as far as I figured (but I have to check this) the SearchClient obey under the request limitation of Episerver Find and when doing bigger operations in loops it tends to time out.
Instead, use the ContentIndexer, i.e.
// Use this or injected parameter
var loader = ServiceLocator.Current.GetInstance<IContentLoader>();
// Remove all children or not
var cascade = true;
ContentReference entryPoint = ...where you want to start
// Get all indexable languages from Find
Languages languages = SearchClient.Instance.Settings.Languages;
// Remove all current instances of all languages below the selected content node
//languages.ForEach(x => ContentIndexer.Instance.RemoveFromIndex(entryPoint, cascade.Checked, x.FieldSuffix));
foreach (var lang in languages)
{
if (cascade)
{
var descendents = loader.GetDescendents(entryPoint);
foreach (ContentReference descendent in descendents)
{
ContentIndexer.Instance.RemoveFromIndex(descendent, false, lang.FieldSuffix);
}
}
// Try delete the entrypoint
var entryTest = loader.Get<IContent>(entryPoint, new CultureInfo(lang.FieldSuffix));
if (entryTest != null)
{
var delRes = ContentIndexer.Instance.Delete(entryTest);
}
}
This is the most bulletproof way to delete stuff from the index as far as I figured.

WPF list<t> sort

my first post here.
i have a list"<"frameworkelement> that i'm populating with a select process. each frameworkelement has a uid that holds its ZOrder.
i need to sort these by the ZOrder from lowest to highest. i can get this using a listbox and adding the Uid's like this:
//Add Object Uid's
ListBox lstTempOrder = new ListBox();
foreach(FrameworkElement feObject in MainWindow.Data.SelectedObjects)
{
lstTempOrder.Items.Add(feObject.Uid);
}
//Reorder from 0 to above of the ZIndexes
lstTempOrder.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("", System.ComponentModel.ListSortDirection.Ascending));
but i need to do this with a List"<"FrameWorkElement> and Sort.
Here is the code where i populate the List"<"T> (SelectedObjects and CopyObjectsCollections are List"<"FrameWorkElement>" lists.
foreach(FrameworkElement feObject in MainWindow.Data.SelectedObjects)
{
MainWindow.Data.CopyObjectsCollection.Add(feObject);
}
i've looked at CollectionViewSource and IComparer but i can't really make any sense of it.
I might have miss-read your question, but if you just want to sort your List<T>, then why don't you just use the LinQ OrderBy method?
MainWindow.Data.CopyObjectsCollection =
MainWindow.Data.CopyObjectsCollection.OrderBy(f => f.Uid).ToList();
If that sorts it the wrong way round for your requirements, then you can use this:
MainWindow.Data.CopyObjectsCollection =
MainWindow.Data.CopyObjectsCollection.OrderByDescending(f => f.Uid).ToList();
UPDATE >>>
OrderBy is a LinQ extension method. Add using System.Linq; at the top of your class to use it. f relates to an instance of your FrameworkElement object. The above lambda expression basically means 'sort using the Uid property values'.
UPDATE 2 >>>
The OrderBy method does not alter the original collection... that is why my example sets the collection to the result of the OrderBy method. See this basic example:
List<FrameworkElement> elements = new List<FrameworkElement>();
elements.Add(new FrameworkElement() { Uid = "Object1003-1" });
elements.Add(new FrameworkElement() { Uid = "Object1002-2" });
elements.Add(new FrameworkElement() { Uid = "Object1002-1" });
elements.Add(new FrameworkElement() { Uid = "Object1001-1" });
elements.Add(new FrameworkElement() { Uid = "Object1001-3" });
elements.Add(new FrameworkElement() { Uid = "Object1001-2" });
string result = string.Join(", ", elements.Select(f => f.Uid));
elements = elements.OrderBy(f => f.Uid).ToList();
string orderedResult = string.Join(", ", elements.Select(f => f.Uid));
By comparing the values of result and orderedResult you can see that this orders them perfectly.
UPDATE 3 (and hopefully the LAST one) >>>
Dude, you need to learn about Lambda expressions... take a look at the Lambda Expressions (C# Programming Guide) page at MSDN for more information.
elements = elements.OrderBy(f => f.Uid).ToList();
The f in this Lambda expression is declared in this expression before the '=>'. It is fairly standard to name these parameters with one letter like Exceptions, but we could name it anything:
elements = elements.OrderBy(frameworkElement => frameworkElement.Uid).ToList();

Calling shuffle on a filtered backbone collection

I'm trying to filter a Collection, and then shuffle the filtered values.
I was thinking of using the where method Backbone provides. Something like:
myRandomModel = #.where({ someAttribute: true }).shuffle()[0]
However, where returns an array of all the models which match the attributes; and apparently shuffle needs a list to work with:
shuffle_ .shuffle(list)
Returns a shuffled copy of the list
http://documentcloud.github.com/underscore/#shuffle
Is there a way to turn my array of models into a 'list'? Or should I write some logic myself to get this done?
When the Underscore docs say list, they mean array. So you can use _.shuffle like this:
shuffled = _([1, 2, 3, 4]).shuffle()
Or in your case:
_(#where(someAttribute: true)).shuffle()
However, since you're just grabbing a single model, you could simply generate a random index instead of shuffling:
matches = #where(someAttribute: true)
a_model = matches[Math.floor(Math.random() * matches.length)]
The shuffle() and where() method are just a proxy in Backbone collections to the underscore method. Underscore methods still work on their own, with arrays as argument. Here is what I would do:
myRandomModel = _.shuffle(#.where({ someAttribute: true }))[0]
Reference: http://documentcloud.github.com/underscore/#shuffle
PS: #"mu is too short" is right however, to get a single model I would go the Math.random() way myself.
I put the following in my application.js file (using Rails 3):
Array.prototype.shuffleArray = function() {
var i = this.length, j, tempi, tempj;
if ( i === 0 ) return false;
while ( --i ) {
j = Math.floor( Math.random() * ( i + 1 ) );
tempi = this[i];
tempj = this[j];
this[i] = tempj;
this[j] = tempi;
}
return this;
};
and now I can call shuffleArray() on an array of arrays. Leaving this unanswered for now though, since I would like to know if there is a better way to do it with Underscore/Backbone.
First in your collection you should have a filtered function
Like
var MyCollection = Backbone.Collection.extend ({
filtered : function ( ) {
Normally you will use _.filter to get only the models you wanted but you may also use the suffle as a replacement use this.models to get the collection models
Here shuffle will mix the models
var results = _ .shuffle( this.models ) ;
Then use underscore map your results and transform it to JSON
like so
results = _.map( results, function( model ) { return model.toJSON() } );
finally returning a new backbone collection with only results you may return only the json if that's what you are looking for
return new Backbone.Collection( results ) ;
note if you don't want to keep all data in collection for later uses you may use the following and ignore the view below ;
this.reset( results ) ;
}
});

Backbone.marionette compositeview ignores the index for addItemView

I am using Backbone.Marionette's CompositeView and I want to insert a new model at index 0 of the collection and have it appear as the first view in the composite.
I have this code to insert the element:
PlansApp.Plans.collection.unshift new PlansApp.Plan data
The problem is that the CompositeView ignores the index of the newly inserted item in the collection.
in Backbone.Marionette, the appendHtml method looks like this:
appendHtml: function(collectionView, itemView, index){
collectionView.$el.append(itemView.el);
},
The index argument is never used.
The newly inserted view will always be placed at the end of the composite.
How can I get it to appear at position 0?
This is by design, due to the number of edge cases that have been identified in supporting sorted collections directly.
The index parameter was added so that developers could individually add support in their applications. There's a wiki page that shows an example of how to do this: https://github.com/derickbailey/backbone.marionette/wiki/Adding-support-for-sorted-collections
MySortedView = Backbone.Marionette.CompositeView.extend({
// ...,
appendHtml: function(collectionView, itemView, index){
var childrenContainer = $(collectionView.childrenContainer || collectionView.el);
var children = childrenContainer.children();
if (children.size() === index) {
childrenContainer.append(itemView.el);
} else {
childrenContainer.children().eq(index).before(itemView.el);
}
}
});
The original Derick's answer is a bit dated for the current version of Marionette, and the wiki contains only an example for a collectionview.
Here's what I use for compositeviews currently:
appendHtml: (collectionView, itemView, index) ->
$container = #getItemViewContainer(collectionView)
if index is 0
$container.prepend itemView.el
else
childAtIndex = $container.children().eq(index)
if childAtIndex.length
childAtIndex.before itemView.el
else
$container.append itemView.el

google like search of backbone collection

I'd like to be able to search model attributes contained within a backbonejs collection. This is how I do it now...
wherePartial: function(attrs) {
// this method is really only tolerant of string values. you can't do partial
// matches on objects, but you can compare a list of strings. If you send it a list
// of values; attrs={keyA:[1,2,3],keyB:1}, etc the code will loop through the entire
// attrs obj and look for a match. strings are partially matched and if a list is found
// it's expected that it contains a list of string values. The string values should be considered
// to be like an OR operation in a query. Non-list items are like an AND.
if (_.isEmpty(attrs)) return [];
var matchFound = false;
return this.filter(function(model) {
// this is in the outer for loop so that a function isn't created on each iteration
function listComparator(value, index, list){
return model.get(key).toLowerCase().indexOf(value.toLowerCase()) >= 0;
}
for (var key in attrs) {
if (_.isArray(attrs[key])){
matchFound = _.any(attrs[key],listComparator);
if (matchFound !== true) return false;
} else {
matchFound = model.get(key).toLowerCase().indexOf(attrs[key].toLowerCase()) >= 0;
if (matchFound === false) return false;
}
}
return true;
});
}
Assume "C" is an instantiated collection, this is how I use it:
name:joe (nickname:joe the man nickname:joe cool nickname:joey)
is typed into a textbox and converted into this:
C.wherePartial({name:"joe",nicknames:["joe the man","joe cool","joey"]})
The above method returns all models that have the name joe and within that scope, any of the models that have the name joe and any of the nicknames. It works well for what I use it for. However, I'd really like to make a search that doesn't require the key:value pattern. I'd like to do this in a search box like when using a search engine on the web. I thought about just looking at every attribute on each model, but that takes awhile when you have a large collection (160k+ models).
Has anyone come across a need like this in the past? If so, how did you solve it? I'd like to keep the search contained on the client and not use any ajax calls to the backend. The reason for this is that the entire collection is already loaded on the client.
I thought of a way to do it. Serialize the attributes to a string during model instantiation. Listen for updates and update the serialization.
serializeAttr: function(){
this.serializedAttr = "";
var self = this;
_.each(this.toJSON(),function(value, key, list){
self.serializedAttr += value;
});
}
Then I can do simple searches on that cached value:
cc.serializedAttr.toLowerCase().indexOf("joe") >= 0

Resources