This is a code fragment of my ItemView:
class List.GridRow extends Backbone.Marionette.ItemView
tagName: 'tr'
triggers:
'click': 'row:clicked'
Then in my Composite view I am doing this:
class List.GridView extends Backbone.Marionette.CompositeView
template: 'mapping/list/templates/grid'
itemView: List.GridRow
itemViewContainer: 'tbody'
initialize: (options) ->
#listenTo #, 'itemview:row:clicked', (itemView, data) -> #rowClicked(itemView, data)
rowClicked: (clickedItemView, data) =>
# I need the original event information to check of the ctrl or shift key was pressed?
#if !e.ctrlKey && !e.shiftKey
What I am trying to do here is to pass the original event information into the trigger handler but I haven't figure it out yet? Is there a way to do this with Marionette? Am I missing something?
Thanks!
The intent of the triggers is to provide a minimal event configuration for minimal needs within the view, while also preventing leaky abstractions. Passing the original event args out of the view breaks the abstraction of the view, which is what should be in control of the event args.
If you need the control and shift key information, you'll want to avoid the triggers configuration and use standard events that publish the info you need.
class List.GridRow extends Backbone.Marionette.ItemView
tagName: 'tr'
events:
'click': 'rowClicked'
rowClicked (e) ->
#trigger "row:clicked",
control: e.ctrlKey,
shift: e.shiftKey
Related
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.
I am using Marionette's CollectionView to render a list of items with ItemViews. Whenever a new item is added, I want to run a short fade-in animation. But not when the collection is rendered initially (or the collection is reset).
Before using Marionette, I handled the reset and add events slightly differently, but I can not figure out how to do this here. I looked at the source code and it seems that addItemView is responsible for adding the child view and both addChildView (called when add is triggered on the collection) and render (for reset events) call this method.
Maybe I am missing something obvious.
This is one way of doing it:
Include these functions in your CompositeView declaration:
onBeforeRender: function(){
this.onBeforeItemAdded = function(){};
},
onRender: function(){
this.onBeforeItemAdded = myAnimation;
}
This is similar to the solution I present in my book on Marionette (https://leanpub.com/marionette-gentle-introduction/)
How it works: Marionette triggers the "before:render" before it renders the entire collection, so you can set the the onBeforeItemAdded function to do nothing. Once the collection has been rendered, set that function to animate the new item view.
Since each time the collection view adds an item view it also triggers the "before:item:added", you can define an onBeforeItemAdded function that will automatically be called when that event is triggered. This matching happens thanks to triggerMethod.
This solution should solve your problem, without your needing to add flags on the model.
David Sulc answer is pretty hacky, fadeIn should be defined within item it self, not within parent view.
Another thing is that onBeforeItemAdded() is not mentioned in documentation, so it could be for internal use and may change over time.
What I suggest is to add following to parent view, note flag parentRendered:
itemViewOptions: function() {
return {
collection: this.collection,
parentRendered: this.rendered
};
},
onRender: function() {
this.rendered = true;
}
and use that flag in onShow function inside item view:
onShow: function() {
// show visual effect on newly added items
if (this.options.parentRendered) {
this.$el.css('opacity', 0).slideDown(200).animate(
{ opacity: 1 },
{ queue: false, duration: 400 }
);
}
else {
this.$el.show();
}
}
I think that your best choice is to bind your event after the CollectionView has been rendered.
myCollectionView.on( "render", function() {
this.on( "after:item:added", executeMyAnimation );
});
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/
I have a Marionette.ItemView that uses a CSS class to style new items differently:
class Happenator.Views.Option extends Backbone.Marionette.ItemView
tagName: 'li'
className: =>
return 'new' if #model.isNew()
initialize: ->
#bindTo #model, "change", -> #render()
When the model is saved and updated, everything refreshes but the 'new' class remains on the 'li'. Is there a good way to update the enclosing tags' class on updates?
Yes, the 'new' class remains on the 'li', because in fact Backbone uses the className property only right before an initialize method call. Take a look at this answer for more explanations.
But why don't use jQuery .toggleClass or .removeClass? Something like
render: =>
#$el.html(#template(#model.toJSON()))
unless #model.isNew()
#$el.removeClass('new')
http://jsfiddle.net/GX8WJ/21/
I have the following backbone view:
class Observation extends Backbone.Model
class Observations extends Backbone.Collection
model: Observation
constructor: ->
#url = _observationsUrl
class ObservationsView extends Backbone.View
el: $('#observations')
initialize: ->
_.bindAll #
#model.bind 'changed', #render
#model.view = #
that = #
#model.fetch {
success: ->
alert('success')
that.model.trigger 'changed'
}
render: =>
alert('rendering baby')
class ObservationsController extends Backbone.Controller
initialize: ->
observations = new Observations()
observationsView = new ObservationsView(model: observations)
I am binding the model's changed event to the render method of the ObservationsView. The model is a backbone collection.
The fetch is working successfully but the changed event is not being fired. I am trying the manual trigger out of desperation.
Can anyone see what I am doing wrong?
The event isn't called 'changed'. The event triggered after the model's collection has been refreshed from the server is 'refresh'.
The 'change' event is actually more complicated. It's an event on a model that's triggered whenever you call .set(), and it always includes the attribute, so you'd write things like:
this.model.bind('change:username', _.bind(this.update_username_display, this))
As always, the backbone.js source code is eminently readable.