Good to append a view inside a view? - javascript

I over complicated things at first and could not figure out how to create a list within a list using backbone.js. I finally got it, by simply creating a list item view for all of the players in my app. Then created a view for all of the teams inside my app.
I "glued" or "appended" them together by creating an app view that put them together, there is an each statement for both, before the two views were appended to the app view root, I appended the player list item view into the team view. Let me show you.
Here is my render method inside the app view: I am just not sure if this is a bad idea or not, I am thinking there are much better ways, but this is the only method I have had success with. It really makes sense to me, I can run events on each view without a problem
render: function() {
var self = this;
this.teams.each(function(team) {
var teamView = new TeamView({ model: team });
var teamHtml = teamView.render().el;
var teamPlayers = this.players.where({team_id: team.get('id')})
_.each(teamPlayers, function(player) {
var playerView = new PlayerView({ model: player });
var playerHtml = playerView.render().el;
$(teamHtml).append(playerHtml);
}, this);
this.$el.append(teamHtml);
}, this);
return this;
},
I asked about this and was told it would be better to create sub-views, well I am pretty sure this is a sub-view structure? Are there any holes to this method, if so I would like an explanation why this method is bad and how I can improve it. Last but not least I do care about clean maintainable code but what matters most is that I have teams wrap its respected players with an HTML result like below.
<div>
<ul class="lakers">
<li>Kobe</li>
<li>Pau</li>
</ul>
<ul class="spurs">
<li>Tony</li>
<li>Tim</li>
</ul>
</div>
Again id like some constructive criticism, mainly PROS & CONS with connecting the two views like that. Just needed to ask before I move on I want to make sure I am not getting into bad habits or creating problems in my code when I start expanding it, I am sure you understand that.

I asked about this and was told it would be better to create sub-views, well I am pretty sure this is a sub-view structure?
Yes you knew it, and TeamView is sub-view. However it's a "zombie view". Does it do anything itself? A view should be responsible of rendering itself, including appending its direct sub-views, but without knowing how to render its sub-views, i.e., you should pass the players collection to the TeamView and move the following logic into TeamView:
var teamPlayers = this.players.where({team_id: team.get('id')})
_.each(teamPlayers, function(player) {
var playerView = new PlayerView({ model: player });
var playerHtml = playerView.render().el;
$(teamHtml).append(playerHtml);
}, this);

Related

Dynamically add to a list in angular

Goal: a dynamically generated list from external source.
I've set up a simple angular app that gets a list of events from an external JSON source. I want the list to update when events are added from the external source. It's currently working, but I have one problem and three questions:
1) I'm currently rewriting the list every 15 seconds. How do I just add to the end of the list without rewriting the list? (problem and question)
2) Is there another, better way to keep up to date with the external list? I'm trying to follow "RESTful" techniques, does that mean I should rely on the client side code to poll every so many seconds the way I'm doing? (best practice question)
3) Is setting the timeout in the controller best practice? Because it's controlling the action on the page?(best practice/comprehension question)
var eventModule = angular.module('eventModule', []);
eventModule.controller('eventControlller',
function($scope, $timeout, eventList) {
$scope.events = eventList.getAllEvents().success(
function(events) {$scope.events = events});
var poll = function() {
$timeout(function() {
$scope.events = eventList.getAllEvents().success(
function(events) {$scope.events = events});
poll();
}, 15000);
};
poll();
});
eventModule.factory('eventList', function($http) {
var url = "http://localhost/d8/events/request";
return {
getAllEvents: function() {
return $http.get(url);
}
};
});
If the list is an array, and you want to add new members to it, there are a few different ways. One way is to use the prototype.concat() function, like so:
function(events) {
$scope.events = $scope.events.concat(events)
});
If you cannot use that then you can go for loops solution:
function concatenateEvents(events) {
events.forEach(function(element) {
events.push(element);
}
}
Regarding the best ways to update the list, it depends on your requirements. If 15 seconds is not too long for you, then you can keep this logic, but if you need to speed up the response time, or even make it real time, then you need to emulate server-push architecture, which is different than the default web architecture, which is request-response architecture. Basically you may want to explore web sockets, and/or long polling, or reverse ajax, or comet... has many names. Web sockets is the recommended solution, others are only in case you have to use some non-compatible browsers.
Regarding the third question, I honestly don't know. Truly it doesn't feel good to control the UI from within your controller, but as I don't really know what your app is supposed to be doing, I don't know whether this is actually a bad way to do it.
Hope this helps!
EDIT - forgot to add another important point: You don't need to assign the eventList.getAllEvents() to $scope.events, as you are doing that in the callback handler function.
Perhaps you can modify your controller to something like this:
eventModule.controller('eventControlller', function($scope, $timeout, eventList) {
eventList.getAllEvents().success(
function(events) {
$scope.events = events
});
var poll = function() {
$timeout(function() {
eventList.getAllEvents().success(
function(events) {$scope.events = events});
poll();
}, 15000);
};
poll();
});

Can not update observable of one viewModel from another

I want to update one observable of one viewModel from another. The value gets changed but it does not reflect on UI (in HTML)
I am not sure what's wrong with the code..
Here is the code :
http://jsfiddle.net/rahulrulez/RSL4u/2/
<p data-bind="text: name"></p>
does not gets updated.
You are binding to two independent view-model instances
ko.applyBindings(new viewModel1(), document.getElementById("firstViewModel"));
ko.applyBindings(new viewModel2(), document.getElementById("secondViewModel"));
So there is no connection between your viewModel1 and viewModel2 and when you write in your viewModel1:
var vm2Object = new viewModel2();
then you are creating a completly new viewModel2 instance which has nothing to do with the one used in the applyBindings.
To fix this you need to make a connection between your view models, somehow like this (there are multiple other ways to do like using a container view model, nest your view models to each other, etc.):
var viewModel1 = function(vm2){
var self = this;
var vm2Object = vm2; //use vm from parameter
//...
}
And when calling the applyBindings:
var vm2 = new viewModel2();
ko.applyBindings(new viewModel1(vm2), document.getElementById("firstViewModel"));
ko.applyBindings(vm2, document.getElementById("secondViewModel"));
Demo JSFiddle.
You have two different instances of the VM2 ViewModel. Instead do
http://jsfiddle.net/RSL4u/4/
I have made VM2 a sub model of VM1
this.vm2Object = new viewModel2();
I tried all your approaches, for some reason they were not really working with the solution I have.
After a lot of googling and digging deep in KnockoutJS documentations and extensions, I found Knockout Postbox which lets us sync or communicate between multiple irrelevant View Models.
https://github.com/rniemeyer/knockout-postbox
Here is snippet of my demo example...
var viewModel1 = function(){
var _self = this;
_self.name = ko.observable("Rahul").syncWith("syncedName");
}
var viewModel2 = function(){
var _self = this;
_self.firstName = ko.observable("Rahul").syncWith("syncedName");
}
ko.applyBindings(new viewModel1(), document.getElementById("div1"));
ko.applyBindings(new viewModel1(), document.getElementById("div2"));
Both the observable are in sync now.. I found this much better than nesting objects.. At least it satisfied the need of my application..
Thank you so much for help anyway..
My Demo : JSFiddle : http://jsfiddle.net/rahulrulez/2kuSh/1/
I hope it helped..
Thank you so much,
Rahul Patil.

Knockout observable/viewmodel that can be accessed by other viewmodels?

I am building an SPA and everything is going well. It has multiple Viewmodels which are built dynamically and there can be multiple of the same kind, i.e you can open two calculators each having its own model which is bound to a specific div on the page.
Recently I realized that several of the viewmodels were requesting the same data from a web service and on a constant loop every 30 secs - 1 minute. So the same service call was being made multiple times every 30 seconds yet returning the same information.
So what I am trying to figure out is how I can create a "global" observableArray which multiple viewModels can be notified of a change and update rather than doing it themselves, this also helps to make sure the data on the page is consistent.
I was hoping I could do something like:
var GlobalData = (function() {
var commonData = ko.observableArray();
setInterval(function() {...go get data...commonData(data);}, 30000);
return {CommonData:commonData}
})();
ko.applyBindings(GlobalData, $('#RandomLonelyDiv')[0]);
Then later
function Calculator(element){
function init() { ko.applyBindings(calculator, $(element)[0]); }
var calculator = {
CommonData = GlobalData.CommonData
}
return calculator;
}
If it helps the only reason why I dont have a MainViewModel which contains all my other viewmodels is because i frankly dont know how to set that up for my environment.
I have a AppViewModel which contains a ko.observableArray called Windows, which is contains objects which define the options/information to build certain window types.
<!-- ko template:{name:'WindowTemplate', foreach:SelectedTab().Windows} --><!-- /ko -->
and then I have a custom Window binding that creates a modified kendoWindow, which creates a new viewmodel of a specific type such as Calculator, and like I said you could have multiple calculators at one time. But when I started this I wasnt really sure how to put that viewmodel into my AppViewModel. Perhaps its just another array?
It sounds like what you really need is a "Pub/Sub" model. That would allow you to publish and subscribe to messages that are ignorant of their generation or destination. Check out https://github.com/postaljs/postal.js/wiki.
I believe this may be what you are looking for: http://jsfiddle.net/xSKyR/474/
You can subscribe to another viewmodel's observable like so..
var ViewModel1 = function () {
var self = this;
self.something1 = ko.observable("1");
self.clickMe = function (data, event) {
self.something1("2");
};
};
var ViewModel2 = function () {
var self = this;
self.something2 = ko.observable();
vm1.something1.subscribe(function (newValue) {
self.something2(newValue);
});
};
var vm1 = new ViewModel1();
var vm2 = new ViewModel2();
ko.applyBindings(vm1, document.getElementById("vm1"));
ko.applyBindings(vm2, document.getElementById("vm2"));

Backbone variables versus page containers . . .?

So I have inherited a bit of backbone.js code and need to make a change to it today. The guy who wrote the original code is on vacation. I am just barely studying up on backbone.js and am pretty much a backbone newbie.
The code below works and does what it was designed for. There is only one issue: The contents of the template file (see below) get rendered into a specific HTML page.
My problem is that I don't fully understand the flow of the code to make an educated guess as far as how and where to insert a reference to an actual container on that HTML page, and get the content to display inside that container.
The class name of the container where I need the output from this function to go is .mngmnt-main-sctn. Is this possible to do?
.
window.ManagementInstancesBackupView = ManagementView.extend({
events: _.extend({
}, ManagementView.prototype.events
),
initialize: function() {
this.model = this.options.model
this.collection = this.options.collection
this.template = _.template($('#instances-management-backup-template').html())
},
render: function() {
var instances = this.collection
// Append container and title
var $el = this.$el.html(this.template({}))
instances.each(function(instance) {
// THIS IS THE CONTAINER THAT SHOULD GET STUFF APPENDED TO:
// $(".mngmnt-main-sctn")
$el.append(this.renderParent(instance));
instance.get('nic').each(function(nic) {
$el.append(this.renderChild(nic));
}, this)
}, this)
return this
},
renderParent: function(instance) {
return new ManagementInstancesBackupParentView({model: instance}).render().$el
},
renderChild: function(nic) {
return new ManagementInstancesBackupChildView({model: nic}).render().$el
}
});
I believe what you are asking is possible like this.
window.ManagementInstancesBackupView = ManagementView.extend({
el: ".mngmnt-main-sctn"
[...code excluded...]
});
We are overriding the el property meaning that when this line is called
var $el = this.$el.html(this.template({}))
this.$el will refer to the element you have specified.
Jacob, thanks again for looking into this.
I found a solution and now I'm definitely going to hit additional backbonejs tutorials. Within the code, I was able to add the selector like so:
// Append container and title
var $el = this.$el.html(this.template({})).find('.mngmnt-main-sctn')
.
I'm always perplexed by stuff like this. You can't find any answers to solve the problem, then you try a 1,000 different things . . . and then the solution seems so simple and I always feel a bit foolish after such an experience.

Backbone.js relations

I'm having an issue wrapping my head around relational models in Backbone. I've just started using it and I'm tasked with a fairly large application.
The main issue I'm having is that I have a model that should contain a collection.
This is what I have to work with:
modelA
id: _id
url: api/model/:modelA_id
nested:
url: api/:modelA_id/nest
I think I'm making it a bigger deal than I need to, but I just can't seem to wrap my head around how to set this up.
Any help will be most appreciated.
The biggest thing to wrap your head around with Backbone is how to properly use events to deal with basically everything in the app. The other big thing to understand is that there are probably 5 different ways to attack a problem, where none of them are better/worse than the other.
Given that loose structure you've provided, I would do something like:
var YourApp = {
Models : {}
Collections : {}
Views : {}
};
YourApp.Models.First = Backbone.Model.extend({
initialize : function(){
var nestedCollection;
this.url = 'api/model/' + this.id;
nestedCollection = new Backbone.Collection({
url : this.url + '/nest'
});
this.set('nested', nestedCollection);
}
});
new YourApp.Models.First({
id : 23
});

Categories