How do I use fetched backbone collection data in another view? - javascript

Trying to learn Backbone and hitting a stumbling block when trying to fetch data, I fetch the data fine from with my view SearchBarView but once the data has been fetched I don't know how I can get this data in my SearchResultsView in order to template out each result?
Sorry if this sounds a little vague, struggling to get my head around this at the moment so could do with the guidance!
SearchBarView
performSearch: function(searchTerm) {
// So trim any whitespace to make sure the word being used in the search is totally correct
var search = $.trim(searchTerm);
// Quick check if the search is empty then do nothing
if(search.length <= 0) {
return false;
}
// Make the fetch using our search term
dataStore.videos.getVideos(searchTerm);
},
Goes off to VideoSearchCollection
getVideos: function(searchTerm) {
console.log('Videos:getVideos', searchTerm);
// Update the search term property which will then be updated when the url method is run
// Note make sure any url changes are made BEFORE calling fetch
this.searchTerm = searchTerm;
this.fetch();
},
SearchResultsView
initialize: function() {
// listens to a change in the collection by the sync event and calls the render method
this.listenTo(this.collection, 'sync', this.render);
console.log('This collection should look like this: ', this.collection);
},
render: function() {
var self = this,
gridFragment = this.createItems();
this.$el.html(gridFragment);
return this;
},
createItems: function() {
var self = this,
gridFragment = document.createDocumentFragment();
this.collection.each(function (video) {
var searchResultView = new SearchResultView({
'model': video
});
gridFragment.appendChild(searchResultView.el);
}, this);
return gridFragment;
}
Now I'm not sure how I can get this data within SearchResultView, I think I need to trigger an event from somewhere and listen for the event in the initialize function but I'm not sure where I make this trigger or if the trigger is made automatically.

Solution 1
If dataStore is a global variable then
SearchBarView
dataStore - appears like a global variable
videos - a collection attached to global variable
then in
SearchResultsView
this.listenTo(dataStore.videos, 'sync', this.render);
Solution 2
If dataStore is not a global variable
getVideos: function(searchTerm) {
console.log('Videos:getVideos', searchTerm);
// Update the search term property which will then be updated when the url method is run
// Note make sure any url changes are made BEFORE calling fetch
this.searchTerm = searchTerm;
var coll=this; //this should refer to the collection itself
this.fetch().done(function(){
var searchResultView = new SearchResultsView({collection:coll});
searchResultView.render();
});
},

It is not 100% clear how you are initializing your SearchResultView.
But, in order to have reference to the collection, can't you simply pass in the reference to the constructor of the view. Something like this:
// In your SearchbarView
var myCollection = new Backbone.Collection(); // and you are populating the collection somewhere somehow
var searchResultView = new SearchResultView(myCollection) // you just pass this collection as argument.
myCollection.bind("change", function(){
searchResultView.parentCollection = myCollection;
}
And inside your searchResultView you just refer this collection by parentCollection for instance.
If you make it more explicit as in how these 2 views are connected or related, I may be able to help you more. But, with given info, this seems like the easiest way.

Related

How to get OData Context from parent Context in UI5

I'm using the OData V4 model in UI5. I've created a binding with some expands in it and now I try to obtain the context of a child entity.
Here is the code how I bind entities to some element. As a result I get an object with on 'SomeEntity' and an array with 'SomeOtherEntity' as a property.
oPage.bindElement({
path: /SomeEntity(id),
parameters: {
$expand: {
SomeOtherEntity: {
$select: ['ID', 'name', 'sequence'],
$orderby: 'sequence'
}
}
}
});
Now I can get the context of the binding with oPage.getBindingContext() and can execute methods like requestObject, setProperty, create and delete from this object.
What I want is to obtain the context of one of the 'SomeOtherEntity' properties to (for example) delete one of them.
I have no idea how to accomplish this. Anybody has an idea?
You can create an own ListBinding to SomeOtherEntity and filter the desired set.
(I'm not quite sure, but it might be necessary to trigger a refresh on the ListBinding to force an initial load)
After the data is loaded (dataReceived-Event), delete all the Contexts.
Each Delete returns a Promise and you can proceed with a Promise.all.
var oDataModel = this.getModel();
var aPromises= [];
var oListBinding = oDataModel.bindList("/SomeOtherEntity", undefined, undefined, new Filter("ID", FilterOperator.EQ, sIdToDelete), {
$$operationMode: OperationMode.Server
});
oListBinding.attachEventOnce("dataReceived", function (oEvent) {
var aContexts = oListBinding.getContexts();
aContexts.forEach(function (oContext) {
aPromises.push(oContext.delete("$auto"));
});
Promise.all(aPromises).then(function () {
/* Cleanup after Deletion
});
});

Preventing Marionette CompositeView render until fetch complete

I'm having a problem where render is being called autimatically in my Marionette CompositeView which is correct, the problem is that I'm fetching collection data in the initialize and want this to be present when the render happens. At the moment I'm running this.render() inside the done method of the fetch which re-renders but this causes problems as now I have 2 views per model. Can anyone recommend how I can properly prevent this initial render or prevent the duplicate views. 1 entry will output view1 and view2.
JS CompositeView
initialize: function() {
var self = this;
this.teamsCollection = new TeamsCollection();
this.teamsCollection.fetch().done(function() {
self.render();
});
},
First of all, I don't believe there is a way to stop rendering outright, but you have a bunch ways around that.
Option 1: fetch data first, then create your view and pass data into it when it's done.
//before view is rendered, this is outside of your view code.
var teamsCollection = new TeamsCollection();
teamsCollection.fetch().done(function(results) {
var options = {res: results};
var myView = new CompositeView(options);
myView.setElement( /* your element here */ ).render();
});
Option 2:
// don't use render method, use your own
initialize: function() {
var self = this;
this.teamsCollection = new TeamsCollection();
this.teamsCollection.fetch().done(function() {
self.doRender();
});
},
render: function(){}, // do nothing
doRender: function(){
// override render here rather than using default
}
Option 3: (if using template)
// if you have a template, then you can simply pass in a blank one on initialization
// then when the fetch is complete, replace the template and call render again
initialize: function() {
var self = this;
this.template = "<div></div"; // or anything else really
this.teamsCollection = new TeamsCollection();
this.teamsCollection.fetch().done(function() {
self.template = /* my template */;
self.render();
});
},
In reality I need more info. How is the view created? is it a region? is it added dynamically on the fly? Do you use templates? Can you provide any more code?

How to replace jquery with the mithril equivalent?

Something like :
peer.on('open', function(id){ // this is a non jquery event listener
$('#pid').text(id);
});
With something like...this is not correct:
peer.on('open', function(id){
m('#pid',[id])
});
Is this even the right approach? Should I be establishing a controller and model before I attempt to convert from jquery?
More details:
I am trying to rewrite the connect function in the PeerJS example: https://github.com/peers/peerjs/blob/master/examples/chat.html
If your event listener is something like websockets, then the event happens outside of Mithril, which means you need to manage redrawing yourself. This is what you'll need to do:
Store your data in an independent model
Use that model when rendering your Mithril view
On the open event, update your model, then call m.redraw()
Conceptual example:
var myModel = { id: 'blank' }
var MyComponent = {
view: function () {
return m('#pid', myModel.id)
}
}
m.mount(document.getElementById('app'), MyComponent)
// This happens outside mithril, so you need to redraw yourself
peer.on('open', function(id) {
myModel.id = id
m.redraw()
})
In Mithril, you should not try to touch the DOM directly. Your event handler should modify the View-Model's state, which should be accessed in your View method. If you post more code, I could give a more detailed explanation of how it pieces together.
Here is a bare-bones example that shows the data flowing through Mithril. Your situation will need to be more complicated but I'm not currently able to parse through all of that peer.js code.
http://codepen.io/anon/pen/eNBeQL?editors=001
var demo = {};
//define the view-model
demo.vm = {
init: function() {
//a running list of todos
demo.vm.description = m.prop('');
//adds a todo to the list, and clears the description field for user convenience
demo.vm.set = function(description) {
if (description) {
demo.vm.description(description);
}
};
}
};
//simple controller
demo.controller = function() {
demo.vm.init()
};
//here's the view
demo.view = function() {
return m("html", [
m("body", [
m("button", {onclick: demo.vm.set.bind(demo.vm, "This is set from the handler")}, "Set the description"),
m("div", demo.vm.description())
])
]);
};
//initialize the application
m.module(document, demo);
Notice that the button is calling a method on the View-Model (set), which is setting the value of a property (vm.description). This causes the View to re-render, and the div to show the new value (m("div", demo.vm.description())).

Different model types in one collection Backbone

Backbone 1.1.2
Underscore 1.7.0
jQuery 1.11.1
I have a single collection that holds messages.
My messages can be be of different types (and the endpoints in the api are different for each type, but I have an endpoint that allows me to do one request and get all the messages)
When Collection.fetch()
I need to be able to define which model to use when populating the collection based on existing properties.
I have tried as suggested here: A Backbone.js Collection of multiple Model subclasses
as well as the backbone documentation backbonejs.org
My code looks like this
model: function (attr, options) {
if(attr.hasOwnProperty('prop')){
return new PropModel(attr,options);
}
else if(attr.hasOwnProperty('another_prop')){
new AnotherPropModel(attr,options);
}
},
the attr value is just one big array of objects, so without traversing somehow this solution makes no sense to me and its obvious why it doesn't work.
Am I handling this correctly is there another way to do this?
---UPDATE----
I have also tried doing this in the Parse Function of the collection and my collection is just empty
parse: function (resp, options) {
_.each(resp, _.bind(function (r) {
console.log(this);
if(r.hasOwnProperty('prop')){
this.add(new PropModel(r));
}else{
this.add(new AnotherPropModel(r));
}
},this));
}
So the solution was a mix of using model function and return.
Here goes the explanation:
First off we have the parse function
which is just an entry point for us to alter a response that we receive from our server
parse: function (resp, options) {
return resp;
}
In my case, the server was returning an Object of Object as
{{1:data},{2:data}}
Firstly this is strange and obviously needs to be resolved.
The IMPORTANT POINT IS:
When backbone assess the response return from parse, it needs to decide where to break off for each model as in what defines a new model.
Backbone sees objects as a single model and as in my case, I had one big object, I was getting one big model... this is why the attrs argument in model function was one big mush of data.
So I simply altered my response in the parse function and Voila!! everything in model function worked as expected:
Here is the code:
model: function (attr, options) {
if(attr.hasOwnProperty('prop')){
return new PropModel(attr,options);
}
else if (attr.hasOwnProperty('anotherProp')){
return new AnotherPropModel(attr,options);
}
},
parse: function (resp, options) {
var response = [];
_.each(resp, _.bind(function (r) {
response.push(r);
},this));
return response;
}
Im sure there is a better way to resolve the object to array, but for now this works and I'm smiling again!!
This article lead me to the solution:
A collection of muppets
You could do something like the following - reacting to the different type (if possible) and then provide a different URL. Then once the JSON models are in the template, then you can render the HTML the way you like:
Example Json
"[{"id":1,"Type":"Person"},{"id":2,"Type":"Business"}]"
Example Model
var Person = Backbone.Model.extend({
keyTypes: {
Person: 'Person',
Business: 'Business'
},
url: function() {
// we are assuming only two types. Change logic if there are three or more types.
return this.get('Type') === this.keyTypes.Person ? '/api/people' : '/api/businesss';
}
});
Collection
var Collection = Backbone.Collection.extend({
model: Person
});
View
var View = Backbone.View.extend({
initialize: function() {
this.collection = new Collection()
.on('fetch', this.render, this);
},
bootstrap: function() {
this.collection.fetch();
}
render: function() {
this.$el.html(_.template({
models: this.collection.toJSON()
}));
}
})
** !! Update !! **
If you want to still use parse, it will could to look the following.
parse: function (data, options) {
var models = [];
_.each(data, function (entity) {
// this is IE8 Safe....
var model = Object.prototype.hasOwnProperty.call(entity,'prop') ? PropModel : AnotherPropModel;
models.push(new model(entity));
});
return models;
}

Can't see my model within backbone collection

I'm trying to add an item to a collection but first I want to remove the existing one. Only one item will ever exist. I can create a new one, just not remove one. Maybe I'm doing it backwards.
This is my collection, the changetheme is the function that gets called, which works away, but can't figure out how to remove the existing one. this.model.destroy() just throws an error. Maybe i'm out of context.
bb.model.Settings = Backbone.Collection.extend(_.extend({
model: bb.model.Setting,
localStorage: new Store("rrr"),
initialize: function() {
var self = this
this.model.bind('add', this.added, this);
},
changetheme: function(value) {
var self = this
this.destroy();
this.create({theme:value});
},
}));
If it matters this is my model
bb.model.Setting = Backbone.Model.extend(_.extend({
defaults: {
theme: 'e'
},
initialize: function() {
var self = this;
},
added: function(item) {
var self = this;
this.destroy();
},
}));
To remove first item from collection you can call collection.shift(), also you can just clear collection by calling collection.reset(). So in your case one could write:
changetheme: function(value) {
this.shift();
this.create({theme:value});
}
UPD
Ok, let me explain - in your example localStorage plays like any other server side. So when you call "create", then according to docs backbone instantiates a model with a hash of attributes, saves it to the server(localStorage), and adds to the set after being successfully created. That is why your collection items count increases on each page refresh. But when you call shift/remove docs then only you client side collection is affected, not the server(localStorage) one. Now the best option for you to remove model both from server and client is calling model's destroy method like that:
changetheme: function(value) {
var modelToDelete = this.at(0) //take first model
modelToDelete.destroy();
this.create({theme:value});
}

Categories

Resources