Marionette best way to do self render - javascript

Hello here is my little code :
i don't know how to make this more marionette ... the save function is too much like backbone...
self.model.save(null, {
success: function(){
self.render();
var vFormSuccess = new VFormSuccess();
this.$(".return").html(vFormSuccess.render().$el);
}
var VFormSuccess = Marionette.ItemView.extend({
template: "#form-success"
} );
http://jsfiddle.net/Yazpj/724/

I would be using events to show your success view, as well as using a layout to show your success view, if it's going into a different location.
MyLayout = Marionette.Layout.extend({
template: "#layout-template",
regions: {
form: ".form",
notification: ".return"
}
initialize: function () {
this.listenTo(this.model,'sync',this.showSuccess);
this.form.show(new FormView({model: this.model}));
},
showSuccess: function () {
this.notification.show(new VFormSuccess());
}
});
Or, you could do the same with just the one region, and having the FormView be the layout itself. You just need to ensure there is an element matching the notification region exists in the layout-template.
MyLayout = Marionette.Layout.extend({
template: "#layout-template",
regions: {
notification: ".return"
}
initialize: function () {
this.listenTo(this.model,'sync',this.showSuccess);
},
showSuccess: function () {
this.notification.show(new VFormSuccess());
}
});
What this allows you to do:
You can then show an error view quite easily, if you wanted. You could replace initialize with
initialize: function () {
this.listenTo(this.model,'sync',this.showSuccess);
this.listenTo(this.model,'error',this.showError);
},
and then add the following, ensuring you create a VFormError view.
showError: function () {
this.notification.show(new VFormError());
}

You should be able to write
self.model.save(null, {
success: function(){
self.render();
}
...
Why are you doing this
this.$(".return").html(vFormSuccess.render().$el);
If you define that template as the view template you could simply refer to it with $el, if you need two different templates then you might think about using a Controller, to decide what to use and who to use it.

If you use Marionette, you don't call render directly but instead use Marionette.Region to show your views.

Related

Marionette CompositeView undelegating childview events on setElement

I have a marionette compositeview which I am using to create a item list for a profile page on an app. For the child view, I extend from an already existing ItemView.
When I use this.setElement(this.el.innerHTML) in the compositeview onRender function, all the events set in the child view no longer are triggered and even more so, triggering them in the console on the inspector tool in the browser, does nothing.
However when I do not use setElement, the container div is added to my markup, but now all the events in the child view work.
Can someone help me understand this please.
The Collection I am using has a custom clone method.
I am using a global collection which is updated and stored in cache on each fetch.
When I actually instantiate my view, the collection has already been used and a region in the main layout view has been populated with a item list similar to the one I want to render.
This is how I instantiate my view:
var currentUser = Profile.get('username');
// Perform changes to global collection
Items.url = API + '/items/search?q=' + currentUser + '&size=20';
Items.parse = function (response) {
if (!response.results) {
return response;
} else {
return response.results;
}
};
Items.fetch(
{success: function (collection, response, options) {
this.listOfItems = new View.itemListProfilePage({
template: TemplIds.profilePagePostedItems,
parentClass: 'profile-cols',
collection: Items, // global collection
filterAttr: {user: currentUser},
isFiltered: true,
lazyLoad: true,
childViewContainer: '#profile-items',
childView: View.itemProfilePage.extend({
template: TemplIds.item
})
});
Backbone.trigger('main:show', this.listOfItems); //'main:show' is an event in layoutview which calls region.show
},
remove: false
});
My compositeview:
View.itemListProfilePage = Marionette.CompositeView.extend({
collection: null, //original collection cloned later for filtering
fetch: null, //promise for fetched items
lazyView: null,
options: {
parentClass: '',
filterAttr: {},
isFiltered: false,
lazyLoad: false
},
initialize: function () {
this.stopListening(this.collection);
//Change collection property and re-apply events
this.collection = this.collection.clone(this.options.filterAttr, this.options.isFiltered);
this._initialEvents();
this.collection.reset(this.collection.where(this.options.filterAttr), {reset: true});
this.listenTo(Backbone, 'edit:profileItems', this.addEditClassToSection);
},
onRender: function () {
this.setElement(this.el.innerHTML, true);
},
onShow: function () {
if (this.options.parentClass) {
this.el.parentElement.className = this.options.parentClass;
}
},
addEditClassToSection: function (options) {
if ( options.innerHTML !== 'edit' ) {
this.el.classList.add('edit-mode');
} else {
this.el.classList.remove('edit-mode');
}
},
}
The parent ItemView:
View.Item = Marionette.ItemView.extend({
model: null,
numLikes: null, //live set of DOM elements containing like counter
modalItem: null, //view class with further details about the item to be used within a modal
events: {
'click img.highlight': 'showModal'
},
initialize: function (options) {
var itemWithHeader; //extended item view class with header at the top and no footer
var addToCart;
//Set up all like-related events
this.listenTo(this.model, "change:numLikes", this.updateNumLikes);
this.listenTo(this.model, "change:liked", this.updateLiked);
//Set up the view classes to be used within the modal on click
itemWithHeader = View.ItemWithHeader.extend({
template: this.template,
model: this.model //TODO: move to inside itemDetails
});
itemAddToCart = View.ItemAddToCart.extend({
template: TemplIds.itemAddCart,
model: this.model //TODO: move to inside itemDetails
});
this.modalItem = View.ItemDetails.extend({
template: TemplIds.itemDetails,
model: this.model,
withHeader: itemWithHeader,
addToCart: itemAddToCart
});
},
onRender: function () {
var imgContainerEl;
var likeButtonEl;
//Get rid of the opinionated div
this.setElement(this.el.innerHTML);
this.numLikes = this.el.getElementsByClassName('num');
//Add the like button to the image
likeButtonEl = new View.LikeButton({
template: TemplIds.likeButton,
model: this.model
}).render().el;
this.el.firstElementChild.appendChild(likeButtonEl); //insert button inside img element
},
showModal: function (evt) {
var modalView = new View.Modal({
views: {
'first': {view: this.modalItem}
}
});
Backbone.trigger('modal:show', modalView);
},
});
The itemView for each individual item in my list:
View.itemProfilePage = View.Item.extend({
events: _.extend({},View.Item.prototype.events, {
'click .delete-me': 'destroyView'
}
),
onRender: function () {
View.Item.prototype.onRender.call(this);
this.deleteButtonEl = new View.itemDeleteButton({
template: TemplIds.deleteButton
}).render().el;
this.el.firstElementChild.appendChild(this.deleteButtonEl);
},
destroyView: function (evt) {
this.model.destroy();
}
});
The short answer is that you should not be using setElement.
Backbone specifically uses the extra container div to scope/bind the view's events. When you use setElement you are changing what the parent element is. Since you are doing this in the onRender function, which is called after the template has been rendered and the events have already been bound, you are losing your event bindings.
The correct thing to do if you are going to use Marionette and Backbone is to expect and utilize the "extra" div wrapper that is generated when you render a view. You can take control of the markup for that "wrapper" div by using className, id, and tagName view properties on your view classes.

ember.js custom component or view

I'm attempting to create a reusable typeahead component(?) for my app. I'm using twitter's typeahead javascript library and trying to create a custom component/view out of it.
I would like to be able to define the typeahead in my templates like so:
{{view App.TypeAhead name=ta_name1 prefretch=prefetch1 template=template1 valueHolder=ta_value1}}
I was thinking those variables would be located in the controllers:
App.ApplicationController = Ember.Controller.extend({
ta_name1: 'movies',
prefetch1: '../js/stubs/post_1960.json',
template1: '<p><strong>{{value}}</strong> - {{year}}</p>',
ta_value1: null
});
I don't know what i need to use to accomplish this, a component or a view. I would imagine it would something like this.
App.Typeahead = Ember.View.extend({
templateName: 'typeahead',
didInsertElement: function() {
$('.typeahead').typeahead([{
name: this.getName(),
prefetch: this.getPrefetch(),
template: this.getTemplate(),
engine: Hogan,
limit: 10
}]);
$('.typeahead').on('typeahead:selected', function(datum) {
this.set('controllers.current.' + this.getValueHolder()), datum);
});
}
});
With a template like
<script type="text/x-handlebars" data-template-name='typeahead'>
<input class="typeahead" type="text">
</script>
I don't know how to get away from the jQuery class selector. In reality, i will have more than one typeahead on a form so this class selection isn't going to cut it.
I also don't know how to get the values from the controller in the View. Obviously the getPrefetch(), getTemplate(), getValueHolder(), etc methods don't exist.
I know this is a TON of pseudo code but hopefully i can get pointed in the right direction.
You probably want to use a component for this.
The secret afterwards is that Ember components (and View) expose a this.$ function which is a jQuery selector scoped to the current view. So you only need to do this:
didInsertElement: function() {
this.$(".typeahead"); // ... etc
}
Take a look at my Twitter TypeAhead implementation for Ember.
you can use it like this:
APP.CardiologistsTypeAhead = Bootstrap.Forms.TypeAhead.extend({
dataset_limit: 10,
dataset_valueKey: 'id',
dataset_engine: Bootstrap.TypeAhead.HandlebarsEngine.create(),
dataset_template: '<strong>{{lastName}} {{firstName}}</strong>',
dataset_remote: {
url: '%QUERY',
override: function (query, done) {
$.connection.cardiologists.server.getAllByLastNameLike(query) //SignalR
.then(function (cariologists) {
done(cariologists);
});
}
},
didInsertElement: function () {
this._super();
var self = this;
Em.run.schedule('actions', this, function () {
var cardiologistFullName = this.get('controller.content.cardiologistFullName');
self.get('childViews')[1].$().val(cardiologistFullName);
});
},
valueChanged: function () {
this._super();
this.get('childViews')[1].$().typeahead('setQuery', this.get('controller.content.cardiologistFullName'));
}.observes('value'),
selected: function (cardiologist) {
var cardiologistFullName = '%# %#'.fmt(Em.get(cardiologist, 'lastName'), Em.get(cardiologist, 'firstName'));
this.set('controller.content.cardiologistFullName', cardiologistFullName);
this.set('value', Em.get(cardiologist, 'id'));
}
});
and the handlebars:
{{view APP.CardiologistsTypeAhead
classNames="col-sm-6"
label="Cardiologist:"
valueBinding="controller.content.referrerCardiologist"}}

Backbone: Navigate function not working

So for some reason navigate won't work in one of my views. I'm doing everything in one file for now, so that may be the problem. Also I know the code is horrible, I'm just messing around with backbone right now.
EDIT: I put a console.log() in MarketingPage's function route and it never gets called, so there must be something wrong with the view.
Also, this is the error I'm getting from chrome dev tools:
Error in event handler for 'undefined': IndexSizeError: DOM Exception 1 Error: Index or size was negative, or greater than the allowed value.
at P (chrome-extension://mgijmajocgfcbeboacabfgobmjgjcoja/content_js_min.js:16:142)
at null.<anonymous> (chrome-extension://mgijmajocgfcbeboacabfgobmjgjcoja/content_js_min.js:18:417)
at chrome-extension://mgijmajocgfcbeboacabfgobmjgjcoja/content_js_min.js:1:182
at miscellaneous_bindings:288:9
at chrome.Event.dispatchToListener (event_bindings:390:21)
at chrome.Event.dispatch_ (event_bindings:376:27)
at chrome.Event.dispatch (event_bindings:396:17)
at Object.chromeHidden.Port.dispatchOnMessage (miscellaneous_bindings:254:22)
Here's my code:
/*global public, $*/
window.public = {
Models: {},
Collections: {},
Views: {},
Routers: {
},
init: function () {
console.log('Hello from Backbone!');
}
};
var App = Backbone.Router.extend({
routes: {
'': 'index',
'register': 'route_register',
},
index: function(){
var marketing_page = new MarketingPage();
},
route_register: function(){
var register_view = new RegisterView();
}
});
window.app = new App();
var User = Backbone.Model.extend({
url: '/user',
defaults: {
email: '',
password: ''
}
});
var MarketingPage = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
var template = _.template($("#marketing-page").html());
$('.search-box').after(template);
},
events: {
'dblclick': 'route'
},
route: function(e){
e.preventDefault();
console.log("In route");
window.app.navigate('register', {trigger: true});
this.remove();
}
});
var RegisterView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function(){
var template = _.template($("#register-template").html());
$('.search-box').after(template);
}
});
$(document).ready(function () {
Backbone.history.start();
});
When I type host/#register into the browser directly, the register view gets rendered, but no matter what I do the click event won't seem to work...
Since the handler function route isn't being called, it's likely that the event delegation isn't working.
One thing to note is that the event handling that is set up in a Backbone View is scoped to only that view's el. I don't see where yours is set up explicitly, so it might be creating an empty div, then handling events inside that empty div (which you don't want).
One trick I use for quick prototypes is to set the view's el with a jQuery selector pointing to something that exists on the page already, then in the render, show it with a .show().
Since you're not really doing that, here's one thing you could try. What we're doing is setting the $el content and then calling delegateEvents to make sure that the events and handlers are being bound.
var MarketingPage = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
this.$el.html(_.template($("#marketing-page").html()));
$('.search-box').after(this.$el);
this.delegateEvents();
},
events: {
'dblclick': 'route'
},
route: function(e){
e.preventDefault();
console.log("In route");
window.app.navigate('register', {trigger: true});
this.remove();
}
});
Backbone.js views delegateEvents do not get bound (sometimes)
http://backbonejs.org/#View-delegateEvents

Backbone LayoutManager Re-Render SubViews

I'm using BBB with the great LayoutManager for the views.
Unfortunately, i can't find a way to re-render specific subviews. Here is my setting:
Home.Views.Layout = Backbone.Layout.extend({
template: "home/home",
el: "#main",
views: {
"#left-menu-container": new Home.Views.Leftmenu(),
"#searchbox": new Home.Views.Searchbox(),
"#content": new Home.Views.Content()
}
});
Home.HomeView = new Home.Views.Layout();
Home.HomeView.render();
Home.Views.AddEditPatient = Backbone.View.extend({
template: "......",
events: {
'click .dosomething': 'dosomething'
},
dosomething: function(){
// [dosomething]
// Only Render Sub-View, e.g. #content here...
}
});
I don't want to re-render the whole layout, what would be possible by calling Home.HomeView.render() again, but how can i render only the sub-view in this setting?
I think you want to add to do something like this with backbone.layoutmanager
thisLayout.setView("#content", new View()).render();
The backbone.layoutmanager v0.6.6 documentation might be helpful
http://documentup.com/tbranyen/backbone.layoutmanager/#usage/nested-views
Also check
http://vimeo.com/32765088
If I understand your question correctly, you can do this in your dosomething function:
this.$("#divToRenderTo").html(new subView().render().$el);
Be sure to have "return this;" at the end of your sub-view's render function.
There are two ways I generally do this with layoutmanager:
Instantiate views in your initialize function and then drop them into the view in beforeRender. This gives your view access to the subview so you can render it directly.
initialize: function() {
this.subview = new SubView();
},
beforeRender: function() {
this.insertView(this.subview);
},
doSomething: function() {
this.subview.render();
}
You can use view.getView(#selector) to return the embedded view and then call render on that.
doSomething: function() {
this.getView('#content').render();
}

How to pass a model(data) from one view to another in Backbone and edit/delete it?

I have a web application using BackboneJS. In this application, I have a LayoutView.js file in which there is a Backbone View (called LayoutView). LayoutView has other functions (methods) that call other views. I am fetching some data in the initialize function of LayoutView, and I need to get this same data (model) in another view and work (update/delete) on it. Below is how I am passing data from LayoutView to myView:
var LayoutView = Backbone.View.extend({
el: $("#mi-body"),
initialize: function () {
var that = this;
this.ConfigData = new Configurations(); //Configurations is a collection
this.ConfigData.fetch({
success: function () {
alert("success");
},
error: function () {
alert("error");
}
});
this.render();
Session.on('change:auth', function (session) {
var self = that;
that.render();
});
},
render: function () {
// other code
},
events: {
'click #logout': 'logout',
'click #divheadernav .nav li a': 'highlightSelected'
},
myView: function () {
if (Session.get('auth')) {
this.$el.find('#mi-content').html('');
this.options.navigate('Myview');
return new MyLayout(this.ConfigData);
}
}
});
Still, I do not know how to "get"/access this data as my current data/model/collection (I am not sure which term is correct) in myView and work on it using Backbone's "model.save(), model.destroy()" methods. Also, whenever an edit/delete happens, the data of ConfigData should be modified and the update should reflect in the html displayed to the user.
Below is the code from MyView:
var MyView = Backbone.View.extend({
tagName: 'div',
id: "divConfigurationLayout",
initialize: function (attrs) {
this.render();
},
render: function () {
var that = this;
},
events: {
"click #Update": "update",
"click #delete": "delete"
},
update: function(){
//code for updating the data like model.save...
},
delete: function(){
//code for deleting the data like model.destroy...
}
});
Now the data I passed is in attrs in the initialize function. How to get this done..?
The syntax for instantiating a Backbone view is new View(options) where options is an Object with key-value pairs.
To pass a collection to your view, you'd instantiate it like so:
new MyLayout({
collection : this.configData
});
Within your view, this.collection would refer to your configData collection.

Categories