Backbone variables versus page containers . . .? - javascript

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.

Related

Backbone subview not rendered properly

I started developping a website using backbone.js and after trying during the whole morning, i'm quite stuck on the following problem.
I output here only the relevant code.
I've a View called Navigator, that contains a Collection of Records (initially empty) :
var NavigatorView = Backbone.View.extend({
template: JST['app/scripts/templates/Navigator.ejs'],
tagName: 'div',
id: '',
className: 'saiNavigator',
events: {},
initialize: function () {
this.currentRecords = new RecordsCollection();
this.currentRecords.on('reset', this.onRecordsCollectionReseted.bind(this));
},
onRecordsCollectionReseted: function(){
this.render();
},
render: function () {
var tplResult = this.template({
computeTemplate: this.computeTemplate,
records: this.currentRecords
});
this.$el.html(tplResult);
},
onDOMUpdated: function(){
var me = this;
var data = {
device : 'web',
gridId : this.model.get('gridId'),
filterId : this.model.get('filterId')
};
$.ajax({
url: App.getTokenedUrl() + '/task/getGridData.'+this.model.get('taskId')+'.action',
success: me.onRecordReceived.bind(me),
statusCode: {
500: App.handleInternalError
},
type: 'GET',
crossDomain: true,
data : data,
dataType: 'json'
});
},
onRecordReceived: function(result){
var newRecords = [];
for(var i = 0; i < result.items.length; i++){
var newRecord = new RecordModel(result.items[i]);
newRecords.push(newRecord);
}
this.currentRecords.reset(newRecords);
}
});
I've a View called dossier which html is
<div id="dossier1" class="dossier">
<div id="dossier1-navContainer" class="navigatorContainer"/>
<div class="pagesNavigatorContainer"/>
<div class="pagesContainer"/>
<div class="readOnlyFiche"/>
</div>
When i first render the dossier (and i render it only once) i create the navigator in the following render function
render: function () {
this.$el.html(this.template({
uniqBaseId: this.id,
className: this.className
}));
var nav = this.navigator = new NavigatorView({
model : this.model,
id: this.id+'navigator',
el: $('#'+this.id+'-navContainer')
});
this.navigator.render();
//We notify the navigator that it's ready. This will allow the nav to load records
nav.onDOMUpdated();
}
}
As we can see, i give the '#dossier1-navContainer' id to the navigator so that he renders there
So, here is how it works. When i render the dossier, it creates a navigator and inserts it in the DOM. When done, i notify the navigator that it can load its data from the server trough ajax request. When i receive the answer i reset the collection of data with the incoming record.
Juste before the this.$el.html(tplResult) in the navigator render function i output the resulting string.
First time it's
<div class="items"></div>
Second time when i get records, it's
<div class="items">
<div>item1</div>
<div>item2</div>
<div>item3</div>
</div>
So the template generation is correct. However, when the second rendering occurs, the this.$el.html(tplResult) does NOTHING. If i look at the DOM in the browser NOTHING CHANGED
However if i replace this line by
$('#dossier1-navigator').html(tplResult)
it works. Which means that the first time, $('#dossier1-navigator') and this.$el are the same object, the second time not.
I've NO idea why it doesn't work the second time with the standard this.$el.
Help!!
Thanks in advance
Edit : after discussing a lot with Seebiscuit, i'm adding the few lines that helped answering the question
newTask.render();
var taskHtml = newTask.$el.html();
$('#mainTaskContainer').append(taskHtml);
My hunch is that your having a binding problem. I would suggest that you replace
this.currentRecords.on('reset', this.onRecordsCollectionReseted.bind(this)); },
in your initialize, with:
this.listenTo(this.currentRecords, "reset", this.render);
No need to specially bind. Backbone's listenTo bids the callback to the Backbone object that sets the listener (the this in this.listenTo). Also has the added benefit that when you close the view (by calling this.remove()) it'll remove the listener, and help you avoid zombie views.
Try it out.
I think the problem is that you are not using what your are passing to your navigatorView;
In your navigatorView try this:
initialize:function(el) {
this.$el=el
...
}
Let me know if it helps
After countless minutes of discussion with seebiscuit, we came up with the solution. The problem is all on the definition of the $el element. The formal definition defines it as
A cached jQuery object for the view's element. A handy reference instead of re-wrapping the DOM element all the time
This is actually not very exact from a standard cache point of view. From my point of view at least the principle of a cache is to look for the value if it doesn't have it, and use it otherwise. However in this case this is NOT the case. As Seebiscuit told me,
Because when you first bound this.$el = $(someelement) this.$el will always refer to the return of $(someelement) and not to $(someelement). When does the difference matter?
When the element is not in the DOM when you do the assignment
So actually, $el holds the result of the first lookup of the selector. Thus, if the first lookup misses then it won't succeed ever! Even if the element is added later.
My mistake here is to add the main dossierView into the DOM after rendering its NavigatorView subview. I could have found the solution if the $el was a real cache as the 2nd rendering in the ajax callback would have found the element. With the current way $el works i had just nothing.
Conclusion : make sure every part of your view is properly rendered in the DOM at the moment your try to render a subview.

How to trigger a JavaScript function using the URL

I really didn't know how to explain my question in the title, so I tried.
Anyways, this is my problem. I have a webpage which is basically a puzzle. The basic premise of it is that when you visit a certain link, it will trigger a function and show the next piece.
Here's one of the functions that will show the piece -
function showone() {
var elem = document.getElementById("one");
if (elem.className = "hide") {
elem.className = "show"
}
}
The reason that it's built like this, is because the pieces are constructed and placed using an HTML table, using classes to hide and show them.
What I need to do, is somehow create a URL that will trigger a new piece. For example, "www.website.com/index.html?showone" is what I'd like. This would trigger the "showone" function.
I don't know how to do this though, and after a fair bit of searching, I'm more confused than I was to begin with.
The reason I'm using JavaScript to begin with, is that the page can't refresh. I understand that this might not be possible, in which case, I'm open to any suggestions on how I could get this to work.
Thanks in advance, any suggestions would be greatly appreciated.
-Mitchyl
Javascript web application frameworks can to this for you, they allow to build web application without refresh page.
For example you can use backbonejs it has Router class inside and it very easy to use.
code is easy as :
var Workspace = Backbone.Router.extend({
routes: {
"help": "help", // #help
"search/:query": "search", // #search/kiwis
"search/:query/p:page": "search" // #search/kiwis/p7
},
help: function() {
...
},
search: function(query, page) {
...
}
});
is also you can use angularjs it is big one that supports by Google.
Maybe this solution can help you?
$("a.icon-loading-link").click(function(e){
var link = $(e.target).prop("href"); //save link of current <a> into variable
/* Creating new icon-tag, for example $("<img/>", {src: "link/to/file"}).appendTo("next/dt"); */
e.preventDefault(); //Cancel opening link
return false; //For WebKit browsers
});

Good to append a view inside a view?

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);

Sorting Dynamic Data with Isotope

I am trying to use Isotope.js to sort data by type. There seem to be a few ways to do this however they all require that you know the sort variables before hand.
One of the best examples of what I'm talking about is found in this question.
In the example they are trying to sort by class for example group all elements with class .milk like so:
milk: function( $elem ) {
var isMilk = $elem.hasClass('milk');
return (!isMilk?' ':'');
},
A jsfiddle is provided here: http://jsfiddle.net/Yvk9q/9/
My problem:
I am pulling the categories (classes or data-type) from a user generated database. For this reason I cannot simply add all the sorting variables to the code before hand.
I played with the fiddle and got a semi working sort here: http://jsfiddle.net/BandonRandon/erfXH/1/ by using data-category instead of class. However,this just sorts all data alphabetically not by actual category.
Some possible solutions:
Use JSON to return an array of all categories and then use this to loop through classes
Use inline javascript and run a PHP loop inside a <script> tag
Write an external PHP file with a javascript header
What I'm looking for
The simplest best approach here, being if it's one of the solutions above or something different. This doesn't seem like it should need to be this complicated. So I may be over complicating this.
EDIT:
I now have a json array of my data but I can't figure out how to pass the data into the isotope settings when i try something like this
var $container = $('.sort-container');
var opts = {
itemSelector: '.member-item',
layoutMode: 'straightDown',
getSortData : {
$.getJSON( 'member-cat-json.php', function(data) {
$.each(data, function(i, item) {
var slug = data[i].slug;
slug : function( $elem ) {
var is+slug = $elem.hasClass(slug);
return (!is+slug?' ':'');
}
}
});
});
}
}
var $container = $('.sort-container');
$container.isotope(opts);
It fails because I can't use a loop inside of the plugin settings. Not sure what can be done about this though.
EDIT 2:
I found this question which seems about what I'm trying to do but unfortunately the most recent jsfiddle fails with isotope
Here is a sample of my JSON output:
{term_id:9, name:Milk, slug:milk, term_group:0, term_taxonomy_id:17...}
{term_id:9, name:Eggs, slug:eggs, term_group:0, term_taxonomy_id:17...}
I am using the slug as the class name and in my loop.
I'm not sure I entirely understand your question, but I'll state my assumptions and work from there:
You have data in a format as described above:
{term_id:9, name:Milk, slug:milk, term_group:0, term_taxonomy_id:17...}
You want to sort on the slug names, even though we do not know what the slugs will be named ahead of time.
Assuming these two things, the fiddle you've linked to is close, but has a problem due to closures which I have fixed.
As expected, your situation is similar to the one listed, except that you need to obtain the JSON data first, as you have.
var $container = $('.sort-container'),
createSortFunction = function(slug) {
return function($elem) {
return $elem.hasClass(slug) ? ' ' : '';
};
},
getSortData = function(data) {
var sortMethods = {};
for (var index in data) {
var slug = data[index].slug;
// immediately create the function to avoid
// closure problems
sortMethods[slug] = createSortFunction(slug);
}
return sortMethods;
}
$.getJSON('member-cat-json.php', function (data) {
// I'm wrapping the isotop creation inside the `getJSON`
// call, just to ensure that we have `data`
$container.isotope({
itemSelector: '.member-item',
layoutMode: 'straightDown',
getSortData: getSortData(data);
});
});

Advice needed... Javascript OOP/namespacing

right now i am at a point where i feel that i need to improve my javascript skills because i already see that what i want to realize will get quite complex. I've iterrated over the same fragment of code now 4 times and i am still not sure if it's the best way.
The task:
A user of a webpage can add different forms to a webpage which i call modules. Each form provides different user inputs and needs to be handled differently. Forms/Modules of the same type can be added to the list of forms as the user likes.
My current solution:
To make the code more readable and seperate functions i use namespaced objects. The first object holds general tasks and refers to the individual forms via a map which holds several arrays where each contains the id of a form and the reference to the object which holds all the functions which need to be performed especially for that kind of form.
The structure looks more or less similar to this:
var module_handler = {
_map : [], /* Map {reference_to_obj, id} */
init: function(){
var module = example_module; /* Predefined for this example */
this.create(module);
},
create: function(module) {
//Store reference to obj id in map
this._map.push([module,id = this.createID()]);
module.create(id);
},
createID: function(id) {
//Recursive function to find an available id
},
remove: function(id) {
//Remove from map
var idx = this._map.indexOf(id);
if(idx!=-1) this._map.splice(idx, 1);
//Remove from DOM
$('#'+id+'').remove();
}
}
var example_module = {
create: function(id) {
//Insert html
$('#'+id+' > .module_edit_inner').replaceWith("<some html>");
}
}
Now comes my question ;-)
Is the idea with the map needed?
I mean: Isn't there something more elegant like:
var moduleXYZ = new example_module(id)
which copies the object and refers only to that form.... Something more logical and making speed improvements?? The main issue is that right now i need to traverse the DOM each time if i call for example "example_module.create() or later on any other function. With this structure i cant refer to the form like with something like "this"???
Do you see any improvements at this point??? This would help me very much!!! Really i am just scared to go the wrong way now looking at all the stuff i will put on top of this ;-)
Thank You!
I think you're looking for prototype:
=========
function exampleModule(id)
{
this.id = id;
}
exampleModule.prototype.create = function()
{
}
=========
var module1 = new exampleModule(123);
module1.create();
var module2 = new exampleModule(456);
module2.create();

Categories