How to avoid destroying backbone view unnecessarily? - backbone.js

I have a view in which two tabs are shown.
Tab visibility is controlled simply by css class. Here is the code:
class PlansView extends Backbone.View
className: 'plans-view tab1-selected'
events:
'click .btn-buy': 'buyItems'
'click .tab1': 'switchToTab1'
'click .tab2': 'switchToTab2'
switchToTab1: (event) =>
this.$el.toggleClass 'tab1-selected', no
this.$el.toggleClass 'tab2-selected', yes
window.location.hash = 'tab1'
switchToTab2: (event) =>
this.$el.toggleClass 'tab1-selected', yes
this.$el.toggleClass 'tab2-selected', no
window.location.hash = 'tab2'
I use the window.location.hash in the functions because when the tab switches, I want the url to reflect this. i.e. When the url is mycompany.com/view#tab1, tab 1 is activated. If it is mycompany.com/view#tab2, i want to show the tab 2.
However what happened is that: when the hash is changed, the router is triggered! The view is then unloaded and then loaded again. It shows a very clear visual jerking.
It is the relevant code in the router:
_showSection: (event) ->
sectionView = new PlansView event
#previousView?.remove()
#previousView = sectionView
#$sectionHolder.append sectionView.el
If I remove the window.location.hash statements, the tab switches very smoothly but the url will stay unchange.
For some reason the pushState is disabled in the project. I don't think I can change this for now.
new Router()
Backbone.history.start pushState: false
Is there anyway I can update the hash without triggering the router code.

Use Backbone.history.navigate instead of window.location.hash.
Backbone.history.navigate '#tab1'
The reason the router is triggered is because Backbone listens to hash changes and triggers route accordingly.
The default behavior of the navigate function is only to change the hash, and if you want to trigger the route, you need to explicitly set the trigger: true option.

Related

eventListener for hashChange

I am trying to add an event listener for my page in ReactJS. I have added it to componentDidMount(). But the is only triggered for the initial page load.
How can I get it trigger whenever there is a change in the address bar.
componentDidMount:function(){
window.addEventListener("hashchange", console.log('hashchange1'));
$(window).bind("hashchange", console.log('$ - hashchange'));
window.onhashchange = console.log('hashchange3');
ReactDOM.findDOMNode(this).addEventListener('hashChange',console.log('ReactDOM - hashchange'));
},
I've tried several different ways to get it to work, but they all work only on the first load. What am I doing wrong?
Thanks.
You're just executing the console.log in every case, and adding its return value as the listener. You need to pass a function as an event listener, for example:
window.addEventListener("hashchange", e => console.log('hashchange1', window.location.hash ));
https://jsfiddle.net/ku6okrp2/
EDIT to make it look more obvious using ES5:
window.addEventListener("hashchange", function(e){
console.log('hashchange1', window.location.hash )
});

Check for css class on this.el inside of Backbone event object

events: {
'click .pdf_preview' : 'onPreviewPdfClick',
'click a.next' : '_nextPointer',
'click a.prev' : '_prevPointer',
'mouseleave .mediapop-item-container' : 'onMouseLeave',
'mouseenter .mediapop-item-container' : 'onMouseEnter',
// Editable events
'click &.editable .dialog-header h3' : 'showEditTitleForm' <--- this
},
I create a popup view that has an edit mode. While I could simply extend this view with an editable version, what I need is very basic, so instead I set it up so if you pass this.options.editable the view.el element will have an "editable" class on it.
I am wondering if there is a way for me to specify that selector inside of the event object. That way it won't event trigger if this.options.editable is not true. If Backbone supported a SASS style selector syntax then the above would work. The only alternative is to check for this.$el.hasClass('ediable') within showEditTitleForm.
I know if you set a selector like this:
'click' : 'myAction',
The click event will be applied to the this.el container. I'm looking to only apply the callback if the additional class is there.
Is it possible to do something similar to the above without adding a check within the callback method?
Edit: typo
You can always modify events before delegateEvents is called, just be careful not to modify the events attached to the view's prototype. Something like this:
initialize: function() {
if(this.options.editable) {
this.events = _({}).extend(this.events, {
'click .dialog-header h3': 'showEditTitleForm'
});
}
}
Note the _({}).extend(...), that will merge this.events (which comes from the prototype) and the extra event into an empty object so that you don't accidentally alter the events in the prototype.
Demo: http://jsfiddle.net/ambiguous/dNYXN/

Backbone.js View events disable enable

If I have a View in backbone.js and it has an event in the events list:
events: {
'click #somebutton': 'clicked'
},
clicked: function () {
console.log('clicked');
}
How can I then disable/enable that event? So for instance if its clicked then
the event is removed (the button remains on screen but is greyed out etc). When some other part of the view is updated or whatever the event
enabled. Sure I can use jquery but I want to know if this functionality is available in backbone.
Thanks for any answers
Paul
You can always use delegateEvents() and undelegateEvents() to redo your event binding between the DOM and your Backbone View. That said, I usually just keep the event handler and add a conditional in the handler.
// .disabled class (CSS) grays out the button
clicked: function(event) {
var buttonEl = $(event.currentTarget);
if (buttonEl.hasClass('disabled')) {
// Do nothing
} else {
// Do something AND...
buttonEl.addClass('disabled');
}
}
Then you can have your other view or code simply removeClass('disabled') when you want to restore functionality.
UPDATE - disabled property
See comments, but a simpler, much better solution is to use the disabled property disabled="disabled" of buttons.
Use delegateEvents and undelegateEvents for binding and unbinding events. Check for reference: delegateEvents

ExtJS MVC Controller ref to Tab Panel's active tab

I have an MVC app which has a Tab Panel rendered as the only item in the viewport. Within the tabs I would like to put the same type of component, for which the event handling is equal within all tabs.
Therefore I need an ExtJS MVC Controller ref to the active tab, that would lead me to execute events on the components in the active tab. Is it possible to have such a ref?
My current approach is this:
get reference to Tab Panel
call getActiveTab()
call the controller events for the component in active tab
Is it possible to encapsulate the above 3 steps in one controller ref? Would be beautiful :-)
I'm using ExtJS 4.1.
Personally, I think controller refs are overrated. This sounds like a simple case where you can use one as you describe, but I prefer to listen to the event, and navigate from the firing component to the one I need.
In your case, assuming your tab panel contains regular ext panels, I'd do something like the following:
onButtonClick: function (button) {
var panel = button.up('panel[tab]');
if (panel) {
panel.someMethod();
}
}
The up method will find the closest ancestor that is of xtype panel and has a property named tab. If you have more specific xtypes or known properties, use those instead.
The panel should be the active one (else how was its button clicked?) but you can check that by going up another level to the tab panel and getting the activeTab there.
onButtonClick: function (button) {
var panel = button.up('panel[tab]'),
tabPanel = button.up('tabpanel'),
activeTab = tabPanel && tabPanel.getActiveTab();
if (panel && panel === activeTab) {
panel.someMethod();
}
}
Say your tabPanel has itemId: 'content'. Inside that panel you will have bunch of tabs and on each tab you will have bunch of buttons (as an example). And you want to handle all these button's click in one controller. Do I understand you correctly?
I believe that simple ref inside your controller would work:
this.control('#content button', {
click: this.buttonClicked
}
});

extjs 3 - Which event is fired when extjs is completely loaded

I am using Extjs for my application. Which event/listener is fired when extjs completely loads the application (images and everything)?
I tried following but none of these worked:
body or window onload (body tag is empty)
viewport render listener
What I am doing currently: When I start the application it displays "loading" mask. Then an ajax request is fired and when it is completed, "loading" mask is removed. Following might give some idea:
Ext.onReady(function(){
Ext.ux.mask = new Ext.LoadMask(Ext.getBody(), {msg: "Loading..."});
Ext.ux.mask.show(); // Show the mask
// All components are loaded eg. viewport, tabpanel, button etc...
ajax_request(); // Somewhere between the code ajax request is called
// All components are loaded eg. viewport, tabpanel, button etc...
function ajax_request() {
// Other processing
Ext.ux.mask.hide(); // Hide the mask
}
});
The problem is the ajax request is taking much time now so i want to change the working something as follows:
Ext.onReady(function(){
Ext.ux.mask = new Ext.LoadMask(Ext.getBody(), {msg: "Loading..."});
Ext.ux.mask.show(); // Show the mask
// All components are loaded eg. viewport, tabpanel, button etc...
ajax_request(); // Somewhere between the code ajax request is called
// All components are loaded eg. viewport, tabpanel, button etc...
function ajax_request() {
// Other processing
//Ext.ux.mask.hide(); // Hide the mask - removed
}
// I want to call this when everything is loaded on the page
function everything_loaded() {
Ext.ux.mask.hide(); // Hide the mask
}
});
Any idea on this? Thanks a lot for help.
What ExtJs version are you referring to? 3.x or 4.x?
If 4.x, consider using/following the MVC Application Architecture guidelines. In that case, you want to override Ext.application.launch as described in MVC Application Architecture or Ext.app.Application
If 3.x, I guess Ext.onReady() is the best they have.
UPDATE
Based on your updated question, this is what you are looking for -
Ext.onReady(function(){
Ext.Ajax.on('beforerequest', showSpinner, this);
Ext.Ajax.on('requestcomplete', hideSpinner, this);
Ext.Ajax.on('requestexception', hideSpinner, this);
...
}); //end of onReady
showSpinner = function(){
//setup and show mask
}
hideSpinner = function(){
//hide mask
}
Reference - Ext.Ajax
base on your update.... i got conclusion that you are using Ext version 3.x.x
When I start the application it displays "loading" mask. Then an ajax request is fired and when it is completed, "loading" mask is removed
how did you call the ajax ?? why don't you hide the mask in ajax callback ?
Ext.Ajax.request({
url : "blabla",
method : "GET",
callback : function(){
Ext.ux.mask.hide();
}
});
or, mybe you want to try this one (this is what i used to show preload)
Try the afterlayout event and specify a method to execute when it's trigered
Thanks to amol and Warung Nasi 49. Although I couldn't find the best way, I manage to get almost expected result:
Ext.onReady(function(){
Ext.ux.mask = new Ext.LoadMask(Ext.getBody(), {msg: "Loading..."});
Ext.ux.mask.show(); // Show the mask
// All components are loaded eg. viewport, tabpanel, button etc...
setTimeout(function(){
Ext.ux.mask.hide();
}, 2000);
});

Resources