Backbone stops working if I remove view to separate file - javascript

Drives me crazy, take a look at this jsfiddle - it is very simplified version of my issue but it's absolutely demonstrative. Like this it works, but if you delete AppView definition
var AppView = Backbone.View.extend({
el: $("body"),
initialize: function() {
console.log('init');
this.render();
},
render: function() {
console.log('render');
this.$el.html("123");
}
});
from "main" script (check yourself - the same definition will be still included in "external resources" section) - it stops working. I've tried to check with console.log if the definition is still available in global namespace, and of course it is. Have no idea.
Update:
And even more - it is not just available - goddamn thing work as we can is in chrome dev console by console.log() but this.$el.html("123"); affect nothing!

If you inspect the bottom-right panel in your fiddle, you can see that the AppView.js file is included in the head of the generated document shown in the iframe.
The View is defined with the el element set to $('body')
el: $("body")
But the body Node does not exist, yet: it is an empty jQuery object.
Inside the jQuery document ready callback, the AppView definition finds the body element as you expect.
I understand your need to move the definition of the View in a separate javascript file.
You can achieve the result in many ways. The most obvious is to include the script element that imports it inside the body element. A nicer way could be to set the el in the initialize method of the AppView definition or, even better, you could pass the element to the constructor, as Kenan explains in his comment below.

Related

Can't find element using UI hash in Marionette Layout

I'm not sure why I can't get the button element using my UI hash. This is what my Layout looks like:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
ui: {
btnSave: "#btnSave"
},
events: {
"click #ui.btnSave": "onSave"
},
onInitialize: function () {
this.listenTo(App.vent, "DisableSaveButton", function(val) {
this.disableSaveButton(val);
},this);
},
disableSaveButton: function () {
this.ui.btnSave.prop("disabled",val).toggleClass("ui-state-disabled",val);
},
onSave: function () {
alert("saved!");
}
})
In VS2013, when my breakpoint hits the line inside disableSaveButton method, I entered $("#btnSave") into the Watch window and I was able to get the element back. I could tell because it had a length of 1. From this, I know the button is rendered. However, if I enter this.ui.btnSave into the Watch window, I would get an element with length of 0.
My BaseLayout object is basically a custom object extended from Marionette.Layout
Marionette version: 1.8.8
Any ideas why I can't find the button element using this.ui.btnSave?
Thanks in advance!
Got some help from a coworker and the issue might be because the element is out of scope. Basically, inside the Layout object, 'this' does not contain the element. We were able replace 'this.ui.btnSave' with '$("#btnSave",this.buttonset.el)' and that works fine. buttonset is the region that actually contains the html element.
This seems like an inconsistency because even though the ui hash didn't work, the click event utilizing the ui hash did work.
UPDATE 6/3/2015:
Another coworker of mine provided a better solution. Basically, in my Layout I use a display function to display my view. It looks something like this:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
display: function() {
$(this.buttonset.el).html(_.template($("#buttonset-view").html(), {"viewType": viewType}));
}
})
Basically, I'm saying to set the html of my region, which is this.buttonset.el, to my template's html. As of now, my layout doesn't know any of the elements inside the region. It just contains a region which displays the elements. So there is some sort of disconnect between my layout and the elements in my region.
The correct solution, as opposed to my earlier workaround, is to simply add the following line of code at the end:
this.bindUIElements();
From Marionette Annotated Source:
This method binds the elements specified in the “ui” hash inside the
view’s code with the associated jQuery selectors.
So this final code looks like this:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
display: function() {
$(this.buttonset.el).html(_.template($("#buttonset-view").html(), {"viewType": viewType}));
this.bindUIElements();
}
})
With this, I was able to finally able to retrieve my element using this.ui.btnSave.

Using highlight.js in ember

My ember app is set up with a list of posts on the left and a view for an individual post on the right. When one of the posts on the left is clicked it's content is rendered in the view on the right.
This is the code I'm using to add syntax highlighting to a post.
App.PostView = Ember.View.extend({
didInsertElement: function() {
$('pre code').each(function(i, e) {hljs.highlightBlock(e)});
}
});
When the first post view is rendered, it has the syntax highlighting, but when I click on a different post and it's content gets loaded into the post view the syntax highlighting does not get applied. How can I make it so that the highlighting applied every time a post is rendered?
I can only guess without a more comprehensive example. Is PostView what gets created in the right panel? If so, then you need to constrain your view rendering to the stuff inside the view.
In your example, $('pre code') will target all pre code elements inside the document. Try this.$('pre code'), or whatever element/selector needs to be highlighted within the view.
This may be not the cleanest way to do the job, but you could try adding observer to the controller's model, and make required changes. But this will only work, if the model itself changes.
Like this:
postHasChanged: function() {
if (this.get('state') === 'inDOM') {
$('pre code').each(function(i, e) {
hljs.highlightBlock(e)
});
}
}.observes('controller.model')

Releasing OrbitControls in threejs

I have a Single Page Application with a lot of stuff in it, using durandal. On one page I have a link that leads to a different page where 3d model is rendered. OrbitControls are used to make model turn etc. That takes away my default left click and right click. After leaving that page, it still keeps mouse bindings and my left click and right click become useless for some uses like - selecting an tag meaning that input tags cannot be accessed again.
I could release bindings and reset them if I knew how. There is a deactivate function which is called when that 3d window is closed, but I have no idea what piece of code to write there. Any help would be extremely useful. I doubt any code will be of any use so I won't put any.
Thank you!
Due to request, here is simplified viewmodel:
define(['services/logger'], function (logger) {
var vm = {
attached: attached
};
return vm;
function attached(view) {
var camera, cameraTarget, scene, renderer, controls;
init();
animate();
function init() {
...
controls = new THREE.OrbitControls(camera);
...
}
function animate(){...}
function render(){...}
}
}
View is extremely complicated, but pasted here in full:
<div id="canvasDiv" style="overflow: hidden; width:100%; height:100%">
</div>
Actually, it would probably be helpful to include your code to setup OrbitControls (I'm not familiar with it).
The best approach to this whole issue would probably be to write a Knockout custom binding (google if you don't know them). A custom binding is a great place to abstract away DOM manipulation, in your case for setting up OrbitControls.
Let's assume that you have a div on which you set up OrbitControls. You could then do something like the following:
HTML:
<div data-bind="myOrbitControlsBinding: { someSetting: true; someOtherSetting: false }"></div>
JavaScript:
ko.bindingHandlers.myOrbitControlsBinding = {
init: function (element, valueAccessor) {
var settings = ko.utils.unwrapObservable(valueAccessor());
setupOrbitControlsOnElement(element, settings); // This should be your setup code for OrbitControls
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
// Here, you should unbind the event handlers for mouse clicks. How you do this depends on how OrbitControls sets them up. Please refer to their documentation for this. Maybe there is a generic dispose function?
disposeOrbitControls(element);
});
}
}
Edit:
Ah, I didn't realize you use Three. I've quickly scanned their documentation to see if they use some sort of input module that captures the events. They don't seem to. Which probably means that some where in your code, there is the keyword 'addEventListener' (search for it). This will be where the events are caught.
Your view probably has a viewmodel attached to it since you're using durandal. Inside the viewmodel, add a 'deactivate' method (and return it). In this method, you need to remove the event listener again. You probably already guessed it, but the method is called removeEventListener (see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.removeEventListener for an explanation)
I still highly recommend that you create a custom binding to setup Three. This will give you much more control over creation/deletion than you might have now. If you don't want to, make sure that Three is initialized inside the viewmodel as well, in the activate method.
Let me know if that helped, otherwise some viewmodel / three init code would be helpful.

Removing backbone.js view does not allow me to add another view

It looks like whenever I call view.remove() it not only removes the view from the dom but also the entire body
I'm calling showView after the creation of each view
showView : function(view) {
if (this.currentView) {
this.currentView.remove();
this.currentView.unbind();
if (this.currentView.onClose) {
this.currentView.onClose();
}
}
this.currentView = view;
$('body').html(this.currentView.render().el);
}
Since there is no longer a body element I cannot add another view
Chrome debugger output:
$('html')
<html>​
<script id=​"tinyhippos-injected">​…​</script>​
<head>​…​</head>​
</html>​
Once the view.remove() is ran the screen turns white and doesn't repopulate on $('body').html(this.currentView.render().el);
EDIT:
I changed each view from el: $('.body') to el: '.mainContent' and added a in the index.html file.
In the app.showView I add the mainContent div if it has been removed. Better to remove a div than the entire body.
if($('.mainContent').length == 0)
$('body').append('<div class="mainContent"></div>');
SOLUTION:
I needed to override my view remove method. I didn't want to remove the entire el, just the contents: Recreating a removed view in backbone js
Backbone.View.prototype.remove = function() {
this.undelegateEvents();
this.$el.empty();
return this;
};
Since you are adding the view via $('body').html(this.currentView.render().el); you don't need to set the el property in the view.
When you set the el property in the view, you are telling backbone to find that element and use it as the base element. When doing that, you don't need to add it to the page with $('body').html(), you can just call view.render(). But then when you call remove(), it will remove the el you set (in your case 'body' or '.mainContent').
If you don't specify an el, it generates a new element for you. In that case, you do need to add to the page with $('body').html(this.currentView.render().el);, but then when you call remove() it will only remove the generated element.
So, if you just remove el: '.mainContent' from your views, you can avoid having to check for and re-add that element. Also you will not have to override remove.
After removing el: '.mainContent' from the view, you can simply do this:
$('body').html(view1.render().el);
view1.remove();
$('body').html(view2.render().el);

Can't get rid of the outer div of a backbone.js view

I'm using Backbone 0.9.2 and I have a mustache template that uses twitter bootstrap and looks something like this:
<div class="modal hide something" id="something-modal">
...
</div>
I tried getting rid of the extra <div> that backbone adds because I want the view to be 1-to-1 as my template. My render function looks something like:
render: function(){
var $content = $(this.template()),
existing_spots = $content.find('.spots-list'),
new_spot;
this.collection.each(function (spot) {
new_sweetspot = new SpotView({ model: spot });
existing_spots.append(new_spot.render().el);
});
$content.find("[rel=tooltip]").tooltip();
this.setElementsBindings($content);
//this.$el.html($content).unwrap('div'); // didn't work!
this.$el.html($content);
console.log(this.$el);
return this;
}
I know that by adding:
tagName: "div",
className: "modal",
I'll get rid of it, but I want the control of the view's elements to be of the template, not of the JS code.
this.SetElement will cause the list NOT to be updated (it'll be empty), this.$el = $content; won't work as well.
There was a good thread on this last week on SO.
Backbone, not "this.el" wrapping
tl;dr you can use setElement, but you really need to know when things happen in backbone to make sure everything is wired up correctly.

Categories

Resources