I have DocPad documents that look like this:
---
categories: [{slug: ""}, {slug: ""}, ...]
---
Document content.
How can I query all documents which have a predefined slug value in the meta.category array?
There's a few ways we can go about this.
Via a Template Helper and Query Engine's setFilter
https://gist.github.com/4556245
The most immediate way is via a getDocumentsWithCategory template helper that utilises Query Engine's setFilter call that allows us to specify a custom function (our filter) that will be executed against each model in the collection and based on it's boolean return value keep the model or remove it from the collection.
The downsides of this solution are:
We have to redefine our category information in each post
We have no immediate method of being able to get information about all the categories available to us
Via Template Helpers and the parseAfter event
https://gist.github.com/4555732
If we wanted to be able to get information on all the categories available to us, but still had the requirement of defining our categories every single time for each post, then we can use the parseAfter event to hook into the meta data for our documents, extract the categories out into a global categories object, and then update our document's categories with id references instead.
Downside is that we still have to have redundant category information.
Via Template Helpers and a global categories listing
https://gist.github.com/4555641
If we wanted to only define our category information once, and then just reference the categories ids within our posts, then this solution is most ideal.
I manage to create a "foo" collection for a given slug value "bar in docpad.coffee:
collections:
foo: (database) ->
database.findAllLive().setFilter("search", (model, value) ->
categories = model.get('categories')
return false unless Array.isArray categories
for category in categories
if (category.slug and category.slug == value)
return true
return false
).setSearchString("bar")
but when I try to create a helper function that returns a collection given the array meta ("categories"), the key ("slug") and value ("bar") of the object :
createCollection: (meta, key, value) ->
result = #getDatabase().createLiveChildCollection()
.setFilter("search2", (model, searchString) ->
objects = model.get(meta)
return false unless Array.isArray objects
for object in objects
if (object[key] and object[key] == searchString)
return true
return false
).setSearchString(value)
i get an empty collection when i try to call it.
I don't have a drop in solution, but have you seen the source code for the blog of docpad's author? There are some examples of querying for the existence of a metadata object attribute. Hope that helps!
Related
I am using meteor for development which using mongo db as store. Thats means that all operations are just display object value (frontend) or operate object (backend). For example, from [user] collection "copy" value (userId) to [message] collection but different key name. is there any way better to
describe the relationship between two object rather than using
message.userId = user._id
maybe using a object to describe
{"userId","_id"}
There are many ways to skin a cat. This is how I would do it.
I would first create a mapping object with key-value pairs which represent the field relations between the two objects. The keys are the keys from the first object and the values are the keys from the second object.
{
"userId":"_id",
"userName":"name"
//...
}
Then I would use a function like this to apply the mapping object to two objects:
function applyMapping(fromObj, toObj, mappingObj) {
for (fromKey in mappingObj) {
var toKey = mappingObj[fromKey];
toObj[toKey] = fromObj[fromKey];
}
}
I'd like to filter or run other functions on a Collection, but for the changes to remain with the collection, and not get a new array back.
For example:
In my Collection I have a few methods like:
approved: ->
filtered = #filter((model) ->
model.get("status") is "approved"
)
return filtered
getSubcategories: (obj) ->
...
And in my View, at one point I may want the approved list of models, and then later on I want to run the getSubcategories method. But right now using these methods I'll just get back a new array.
How can I modify my Collection in the View without getting back a new array that I can no longer run other collection methods on?
You could return a new instance of the collection, supplying it with the filtered array of models
approved: ->
filtered = #filter((model) ->
model.get("status") is "approved"
)
new Example.Collection(filtered)
When executing a Breeze entityQuery using projection with a .select, the result set still returns every property instead of only those selected, even though the httpresponse only contains the properties in the select.
query = breeze.EntityQuery
.from('MyClass')
.select('name,code,id')
.skip(0)
.take(20);
return manager.executeQuery(query)
.then(function (resp) {
return resp.results;
})
.catch(function (error) {
return error;
});
the httpResponse contains instances with only name, code, and id.....However, the results contains every property for the entity with only those values populated as well as the entityAspect. I was under the impression that the a projection query would contain POJO objects rather than full Breeze entities:
http://www.breezejs.com/documentation/knockout-circular-references
"Construct a query using a select clause that names just the property values you'll display in your grid (plus the entity key so you can get the full entity later when you need to).
A projection query returns JavaScript objects with raw property values."
The issue in the above link is the reason that I am attempting this. I need to bind to a kendo grid (though using AngularJS bindings rather than Knockout).
Thanks!!
I am trying to add a dynamic sort by to my collection using backbone.js.
At initialization the collection has the default sorting and the view is rendered. I made a button to test how to change the sorting. It calls the following function:
app.productList.comparator = function(product) {
return parseFloat(product.get("price"));
};
app.productList.sort();
If I understand correctly the Collection should now be sorted but the view still needs to be refreshed. I read in the documentation and in this topic to listen for the sort event
window.ProductCollection = Backbone.Collection.extend({
model:Product,
localStorage: new Backbone.LocalStorage("ProductCollection"),
events:{
"sort":"test"
},
test:function(){
alert('test');
}
});
For testing purposes I added a simple alert but this is not displayed. So it seems the sort event was not triggered.
Any ideas what I'm doing wrong here?
Backbone.Collection doesn't take into account a events hash like Backbone.View does, you have to bind events yourself. You could use the initialize method to do so, something like
var ProductCollection = Backbone.Collection.extend({
model:Product,
initialize: function() {
this.on('sort', this.test);
},
test: function(){
console.log('test');
}
});
And a demo http://jsfiddle.net/nikoshr/fTwpf/
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. A comparator can be defined as a sortBy (pass a function that takes a single argument), as a sort (pass a comparator function that expects two arguments), or as a string indicating the attribute to sort by.
"sortBy" comparator functions take a model and return a numeric or string value by which the model should be ordered relative to others. "sort" comparator functions take two models, and return -1 if the first model should come before the second, 0 if they are of the same rank and 1 if the first model should come after.
Note how even though all of the chapters in this example are added backwards, they come out in the proper order:
var Chapter = Backbone.Model;
var chapters = new Backbone.Collection;
chapters.comparator = function(chapter) {
return chapter.get("page");
};
chapters.add(new Chapter({page: 9, title: "The End"}));
chapters.add(new Chapter({page: 5, title: "The Middle"}));
chapters.add(new Chapter({page: 1, title: "The Beginning"}));
alert(chapters.pluck('title'));
Collections with a comparator will not automatically re-sort if you later change model attributes, so you may wish to call sort after changing model attributes that would affect the order.
Force a collection to re-sort itself. You don't need to call this under normal circumstances, as a collection with a comparator will sort itself whenever a model is added. To disable sorting when adding a model, pass {sort: false} to add. Calling sort triggers a "sort" event on the collection.
I'm working with several backbone collections and sometimes I need to access parts of them based on some criteria.
METHOD 1
As already stated in this question, using filter() on the collection itself returns an array of models and not another collection. This can work in simple cases, but it has the effect of losing collection's method concatenation as a plain array of models won't have all methods defined in the collection.
METHOD 2
The answer to that question suggested creating a new collection passing the array of models to the constructor. This works but has the side effect of calling the collection's constructor every time, so any event binding that might be defined there gets stacked on every time you filter the collection.
So what's the correct way to create a sub-collection based on some filter criteria?
Should I use method 1 and create more filtering methods instead on relying on method chaining?
Should I go with method 2 and avoid binding events in the collection's constructor?
Personally I would create more filtering methods on the collection, because it has the additional benefit of encapsulating logic inside the collection.
You could also try to reuse the existing collection. I was toying around with the idea, and arrived at something like this:
var Collection = Backbone.Collection.extend({
//Takes in n arrays. The first item of each array is the method you want
//to call and the rest are the arguments to that method.
//Sets the collection.models property to the value of each successive filter
//and returns the result of the last. Revers the collection.models to its original value.
chainFilters: function(/*args..*/) {
var models = this.models;
try {
filters = _.toArray(arguments);
_.each(filters, function(filter) {
this.models = filter[0].apply(this, _.rest(filter));
}, this);
} catch(err) {
this.models = models;
throw err;
}
var filtered = this.models;
this.models = models;
return filtered;
}
});
Usage:
var results = collection.chainFilters(
[ collection.filter, function(model) { return model.get('name') === 'foo'; } ],
[ collection.someMethod, 'someargument' ],
[ collection.someOtherMethod ]
);
Here's a working sample. It's a bit peculiar, I know.
It depends on the use case. If you want those models to update a view then you probably want a new collection as otherwise you don't get the nice reactive template updates. If you simply wanted the models to iterate through or manipulate data without worrying about the data updating then use the array + underscore.js.
Try it with the arrays and if you find yourself writing a lot of boiler plate code with features already in a collection but not in underscore.js, just start using a collection.