I have this view in Backbone that changes its events depending on a users action.
So I have three views, all have been initialized.
var view1 = new MainView({el : '#view1'});
var view2 = new ProductsView({el : '#prodcuts'});
var view3 = new StoresView({el : '#stores'});
Now what I want to do is copy events from one view to another and then updating the views el. I've tried:
if($option == 'products') {
view1.events = view2.events
$("div[data-role='content']", view1.el).html(content);
} else {
view1.events = view3.events
$("div[data-role='content']", view1.el).html(content);
}
The problem is that even now the content is the same, and the elements are there that correspond with the events BUT the events are no longer firing. Why is this and how can I fix it?
You need to call the delegateEvents method.
For example something like the following (given the code you provided).
if($option == 'products') {
view1.delegateEvents(view2.events)
$("div[data-role='content']", view1.el).html(content);
} else {
view1.delegateEvents(view3.events);
$("div[data-role='content']", view1.el).html(content);
}
Also note that for removing events you use the undelegateEvents() method.
Related
I have some trouble with adding event listener to element after DOM updating.
I have some page, that sort two lists and save the stage.
I can move elements between this lists by d&d and by clicking special button. And it work fine for me.
https://jsfiddle.net/bmj32ma0/2/
But, I have to save stage of this lists, and after reloading I have to extract stage, so I write code below.
function saveFriendsLists(e) {
if(e.target.classList.contains("b--drugofilter--save-button")){
var vkFriends = document.querySelector('.b--friends-from-vk .js--friends-container').innerHTML;
var choosenFriends = document.querySelector('.b--friends-choosen .js--friends-container').innerHTML;
localStorage.setItem('vkFriends', vkFriends);
localStorage.setItem('choosenFriends', choosenFriends);
}
}
function loadFriensListFromStorage() {
if(localStorage&&localStorage.choosenFriends&&localStorage.vkFriends){
document.querySelector('.b--friends-from-vk .js--friends-container').innerHTML = localStorage.vkFriends;
document.querySelector('.b--friends-choosen .js--friends-container').innerHTML = localStorage.choosenFriends;
}
}
document.addEventListener("DOMContentLoaded", loadFriensListFromStorage);
But after adding this, the preview functionality like D&D doesn't work. And I can't provide you valid jsfidle because, as I can understand, localStoradge reason or something.
When I tried to move my addEventListener to loadFriensListFromStorage function, like this:
function loadFriensListFromStorage() {
if(localStorage&&localStorage.choosenFriends&&localStorage.vkFriends){
document.querySelector('.b--friends-from-vk .js--friends-container').innerHTML = localStorage.vkFriends;
document.querySelector('.b--friends-choosen .js--friends-container').innerHTML = localStorage.choosenFriends;
}
[].forEach.call(friends, function(friend) {
friend.addEventListener('dragstart', handleDragStart, false);
});
}
But that doesn't have any effect.
How can I fix this issue? Thx.
Below is my WinJS.UI.ListView definition. However, onselectionchanged is never called when right clicking or doing Ctrl+Click. I have setup my ListView identically to the samples(which work). Am I missing something? Or could something be interfering, just with the click selection?
this.m_listView = new WinJS.UI.ListView(this.domNode,
{
layout : {type: WinJS.UI.GridLayout},
itemTemplate : this.itemTemplate.bind(this),
selectionMode: 'multi',
tapBehavior: 'invokeOnly',
swipeBehavior: 'select'
});
this.m_listView.oniteminvoked = this.itemInvoked.bind(this);
this.m_listView.onselectionchanged = this.selectionChanged.bind(this);
EDIT: I Assign my datasource in a separate function with these lines
var itemList = new WinJS.Binding.List(this.m_nodes);
this.m_listView.itemDataSource = itemList.dataSource;
This ListView is wrapped in a javascript class. So my template function is a prototype of my EntriesList class. This is that function(I pulled out the real content for simplicity, I still have this issue with the content though):
EntriesList.prototype.itemTemplate = function(itemPromise)
{
return itemPromise.then
(
function (item)
{
var entry = document.createElement("div");
entry.className = "tile";
entry.style.height = "120px";
entry.style.width = "340px";
return entry;
}
);
The issue was something in our internal API was blocking pointer events. We were able to resolve the problem. The code/configuration in the question works.
I'm building a backbone app using backbone-relational models (but that shouldn't matter for this question).
Basically, I have an edit button that will display a hidden div. Inside the hidden div id a sub-view (called DetailsView) that renders table elements to populate a list of users. My model (for the whole app) looks roughly like this:
{ 'id': 'foo',
'users':
{
'username': 'bobby',
'password': 'peanuts'
},
{
'username': 'sally',
'password': 'watermellon'
}
}
In my main view (that is fed by a collection of the above models), when the user clicks the edit button, this is triggered:
edit: function(){
var userModels = this.model.get('users'),
detailViewContainer = this.$('tbody.users');
console.log(name + ' has ' + userModels.length + ' models');
//clear out eveything in tbody.users to prevent dupes
detailViewContainer.html('');
//check if there are actually user models
if(userModels.length > 0){
userModels.forEach(function(user) {
var details = new DetailsView({model: user});
details.render();
detailViewContainer.append(details.el);
});
}
The code smell comes from the fact that I have to declare that detailViewContainer explicitly.
Originally, in my forEach loop would call another function in the view that contained the code to declare and render the DetailsView. However, I would loose the context of this.
My original code looked something like this:
edit: function() {
var userModels = this.model.get('users'),
detailViewContainer = this.$('tbody.users');
console.log(name + ' has ' + userModels.length + ' models');
//clear out eveything in tbody.users to prevent dupes
detailViewContainer.html('');
//check if there are actually user models
if(userModels.length > 0){
userModels.forEach(function(user) {
this.renderDetailsView;
});
}
},
renderDetailsView: function(user) {
var details = new DetailsView({model: user});
this.$('tbody.users').append(details.render());
},
In the renderDetailsView, I would loose context of this and could not append the DetailsView to the proper DOM element (the view would append to all of the tbody.users DOM elements , as the this context became the window since it was in a loop).
Having to explicitly declare a detailsViewContainer seems hacky to me, and I'd like to be able to keep the this context pointing to the main view, not the entire window.
The DetailsView template just a set of <tr><td></td></tr> elements. Is there a better way to embed this view without having to resort to creating the detailViewContainer?
(One possible option was having the DetailView loop through the collection returned from this.model.get('users') all by itself... is that a good idea?)
If you're doing what you're doing because of the loss of 'this,' you can pass your context to forEach.
userModels.forEach(function(user) {
this.renderDetailsView();
},this);
Now you have the proper 'this' available to you. Hope this helps.
I'm trying to use the yahoo ui history library. I don't see a great way to avoid wrapping all my function contents with the Y.use so that I can get access to the history object. I tried declaring it globally outside of the use() command, but this didn't seem to work. If you look at my showDashboard() and showReport1() methods, you can see I'm wrapping the contents, which seems redundant to have to do this for every function that uses the history. Is there a better way to do this?
All of the yahoo examples I've seen don't se functions at all and keep the entire sample inside a single use method.
<div>
Dashboard |
Report 1
</div>
<script type="text/javascript">
// Global reference to Yahoo UI object
var Y = YUI();
function showDashboard() {
Y.use('*', function (Y) {
var history = new Y.HistoryHash();
history.addValue("report", "dashboard");
});
}
function showReport1() {
Y.use('*', function (Y) {
var history = new Y.HistoryHash();
history.addValue('report', "report1");
//var x = { 'report': 'report1', 'date': '11/12/2012' };
//history.addValue("report", x);
});
}
Y.use('history', 'tabview', function (Y) {
var history = new Y.HistoryHash();
var tabview = new Y.TabView({ srcNode: '#demo' });
// Render the TabView widget to turn the static markup into an
// interactive TabView.
tabview.render();
// Set the selected report to the bookmarked history state, or to
// the first report if there's no bookmarked state.
tabview.selectChild(history.get('report') || 0);
// Store a new history state when the user selects a report.
tabview.after('selectionChange', function (e) {
// If the new tab index is greater than 0, set the "tab"
// state value to the index. Otherwise, remove the "tab"
// state value by setting it to null (this reverts to the
// default state of selecting the first tab).
history.addValue('report', e.newVal.get('index') || 0);
});
// Listen for history changes from back/forward navigation or
// URL changes, and update the report selection when necessary.
Y.on('history:change', function (e) {
// Ignore changes we make ourselves, since we don't need
// to update the selection state for those. We're only
// interested in outside changes, such as the ones generated
// when the user clicks the browser's back or forward buttons.
if (e.src === Y.HistoryHash.SRC_HASH) {
if (e.changed.report) {
// The new state contains a different report selection, so
// change the selected report.
tabview.selectChild(e.changed.report.newVal);
} else if (e.removed.report) {
// The report selection was removed in the new state, so
// select the first report by default.
tabview.selectChild(0);
}
}
if (e.changed.report) {
alert("New value: " + e.changed.report.newVal);
alert("Old value: " + e.changed.report.prevVal);
}
});
});
</script>
</form>
</body>
</html>
Instead of using plain function on click, attach handlers with YUI.
If you can change the HTML code - add id or class to the links, for example
<a id="btnShowDashboard" href="#">Dashboard</a>
Then in your use() add click handler to the buttons
Y.use('history', 'tabview', 'node', 'event', function (Y) {
var bntShowDashboard = Y.one('#btnShowDashboard');
if (bntShowDashboard) {
bntShowDashboard.on('click', function(e) {
e.preventDefault();
var history = new Y.HistoryHash();
history.addValue("report", "dashboard");
});
}
...
})
That way you will be sure than on the moment of execution "history" is loaded.
BUT there is one drawback - until YUI modules are loaded, if you click the links nothing will happen.
I'm pulling my hair out, I cannot seem to get mouse events to work on my backbone view after the view is re-rendered unless i do the most ridiculous thing:
$("a").die().unbind().live("mousedown",this.switchtabs);
I actually had this in there but decided to update to the latest backbone and try to use the new delegateEvents()function.
Here is the way my project id structured:
Appview / AppRouter
|
----->PageCollection
|
------->PageView/PageModel
------->PageView/PageModel these page view/models are not rendered
------->PageView/PageModel
|
------->PageView/PageModel
|
----->render() *when a pageview is rendered*
|
-----> Creates new
Tabcollection
|
--->TabModel/TabView <-- this is where the issue is
What happens is that the tabcollection has a main tabview to manage all of the tabs, then creates a new model/view for each tab and puts a listener to re-render the tabview whenever a tab is loaded. If the tabview is re-rendered, no mouse events work anymore unless I put that contrived jQuery statement in there.
Heres the tabview and render (ive stripped it down quite a bit)
var TabPanelView = Backbone.View.extend({
className: "tabpanel",
html: 'no content',
model: null,
rendered: false,
events:{
'click a.tab-nav': 'switchtabs'
},
initialize: function(args)
{
this.nav = $("<ol/>");
this.views = args.items;
this.className = args.classname?args.classname:"tabpanel";
this.id = args.id;
this.container = $("<section>").attr("class",this.className).attr("id",this.id);
_.bindAll(this);
return this.el
},
/*
This render happens multiple times, the first time it just puts an empty html structure in place
waiting for each of the sub models/views to load in (one per tab)
*/
render: function(args){
if(!args)
{
//first render
var nav = $("<aside/>").addClass("tab-navigation").append("<ol/>").attr("role","navigation");
var tabcontent = $("<section/>").addClass("tab-panels");
for(i = 0;i<this.views.length;i++)
{
$("ol",nav).append("<li><a rel='"+this.views[i].id+"' href='javascript:;' class='tab-nav'></a></li>");
tabcontent.append(this.views[i].el);
}
this.$el.empty().append(nav).append(tabcontent);
}
else if(args && args.update == true){
// partial render -- i.e. update happened inside of child objects
var targetid = args.what.cid;
for(i = 0;i<this.views.length;i++)
{
var curcontent = this.$el.find("div#"+this.views[i].id);
var curlink = this.$el.find("a[rel='"+this.views[i].id+"']")
if(this.views[i].cid == targetid)
{
curcontent.html($(this.views[i].el).html());
curlink.text(this.views[i].model.rawdata.header);
}
if(i>0)
{
// set the first panel
curcontent.addClass("tab-content-hide");
}
if(i==0)
{
curcontent.addClass("tab-content-show");
curlink.addClass("tab-nav-selected");
}
// this ridiculous piece of jQuery is the *ONLY* this i've found that works
//$("a[rel='"+this.views[i].id+"']").die().unbind().live("mousedown",this.switchtabs);
}
}
this.delegateEvents();
return this;
},
switchtabs: function(args){
var tabTarget = args.target?args.target:false
if(tabTarget)
{
this.$el.find("aside.tab-navigation a").each(function(a,b)
{
$(this).removeClass("tab-nav-selected")
})
$(tabTarget).addClass("tab-nav-selected");
this.$el.find("div.tab-content-show").removeClass("tab-content-show").addClass("tab-content-hide");
this.$el.find("div#"+tabTarget.rel).removeClass("tab-content-hide").addClass("tab-content-show");
}
}
});
Can anyone think of why backbone mouse events simply don't fire at all, is it because they are not on the DOM? I thought that this was where backbone was particularly useful?...
This line of code is likely your problem:
this.delegateEvents();
Remove that and it should work.
The only time you need to call delegateEvents yourself, is when you have events that are declared separately from your view's events hash. Backbone's view will call this method for you when you create an instance of the view.
When the view is being re-rendered, are you reusing the same view and just calling render() on it again, or are you deleting the view and creating a whole new view?
Either way, it looks like the cause is that the view events are not being unbound before the view is re-rendered. Derick Bailey has a great post about this.
When you re-render, 1) make sure you unbind all the events in the old view and 2) create a new view and render it
When using $(el).empty() it removes all the child elements in the selected element AND removes ALL the events (and data) that are bound to any (child) elements inside of the selected element (el).
To keep the events bound to the child elements, but still remove the child elements, use:
$(el).children().detach(); instead of $(.el).empty();
This will allow your view to rerender successfully with the events still bound and working.