Does anyone ever faced the integration of Etch.js in a Backbone.Marionette.js application?
I'm having issues binding the save event. This is the code for my Marionette view:
MyApp.module('Views', function(Views, App, Backbone, Marionette, $, _) {
Views.DetailsView = Marionette.ItemView.extend({
template: '#details',
initialize: function(options) {
_.bindAll(this.model, 'save'); // I think the problem is related to the binding
this.model.bind('save', this.model.save);
},
events: {
'mousedown .editable': 'editableClick'
},
editableClick: etch.editableInit
});
});
and in my template I have something like the following:
<div id="detail-expanded">
<p>Description: <span class="editable">{{ description }}</span></p>
</div>
The plugin is loaded correctly, if I click on the field I can see the Etch buttons bar, I can edit the content of the element made editable and if I click on the save button I'm actually able to trigger the model save() method.
The problem is that the model submitted is the original one, without the edits that I did to the field. I think it's a binding problem, any ideas?
Thanks in advance, as always.
So, the problem here is not really related to marionette, it's that etch doesn't handle moving the data from the editable field to the model. I should be more explicit about that in the docs. What you want to do is create a save function on the view that does this for you like so:
Views.DetailsView = Marionette.ItemView.extend({
template: '#details',
initialize: function(options) {
_.bindAll(this, 'save');
this.model.bind('save', this.save);
},
events: {
'mousedown .editable': 'editableClick'
},
editableClick: etch.editableInit,
save: function() {
// populate model attrs from dom
var title = this.$('.title').text();
var body = this.$('.body').text();
this.model.save({title: title, body: body});
}
});
Sorry for the confusion. I can see how the docs are misleading in this regard.
I believe what you want is
_.bindAll(this, 'save');
instead of
_.bindAll(this.model, 'save');
Related
$el.find(".detail").append("<div class='noImageDetail'>teest</div>");
But not working. Checked in devtools, not found any bug. How can I use append in Backbone? Also, how can I use if - else?
You don't have any errors on your code as it is, now you need to make sure that exists something inside of your "el" with the "detail" class, example
HTML
<div id="book">
<div class="detail">Book:</div>
</div>
JS
var BookView = Backbone.View.extend({
el: '#book',
initialize: function() {
this.render();
},
render: function() {
this.$el.find(".detail").append("<div class='noImageDetail'>teest</div>");
}
});
var bookView = new BookView();
You need to use this.$el.find('selector') you could also use this.$('selector') which will save you some keystrokes since it's basically and alias to this.$el.find
I wrote an example in this plunker
The if - else is basic JavaScript https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else
I want to make a view for html document inside an iframe. For something like:
<div id="some">
<iframe id="other" />
</div>
I want to dynamically load an html document received from server into this iframe. The problem is, that I want to use Backbone View for that document. If I do something like this:
var projectView = Backbone.View.extend(
{
tagName: 'html',
initialize: function()
{
this.model.bind('sync', this.render, this);
},
render: function()
{
this.$el.html(this.model.get('content')); // content is from model, and it
//receives the html document from server
return this;
}
});
Then, when I do:
var iframe = $('#other')[0].contentWindow.document;
iframeDocument.open();
iframeDocument.write(projectView.render().el);
iframeDocument.close();
It does not work. I tryed a lot of different combinations but no success. If I use document.write() with static html it works fine but how to do with backbone view and dynamic content?
The "el" property is a reference to an HTML object. You're passing it into a document.write() call when that function is actually expecting an HTML string. Which is why static HTML works for you.
So you'd probably want to do something like this:
iframeDocument.open();
iframeDocument.write(projectView.render().el.innerHTML);
iframeDocument.close();
I think your approach is unnecessarily complex and using backbone isn't buying you anything useful. Why don't you just set the iframes src attribute with the model's content URL and be done with it?
render: function()
{
this.$el.attr('src', this.model.url()); //Or whatever you need to get the right URL
return this;
}
There is no reason to use an iframe.
You can just replace the iframe with a div and it will render every time your sync event is triggered.
You can also use on instead of bind for the event.
var projectView = Backbone.View.extend(
{
tagName: 'div',
initialize: function()
{
this.model.on('sync', this.render, this);
},
render: function()
{
this.$el.html(this.model.get('content')); // content is from model, and it
//receives the html document from server
return this;
}
});
and then just put the view into a container
$('#some').html(projectView.el);
you may have to remove some unnecessary content(ie. <head> tags) from the model whenever it syncs
I'm Composing this in a view, then trying to call .datepicker() on the result, but nothing happens.
The compose container
<div>
<!--ko compose: { model:'viewmodels/schedule', view: 'views/schedule.html', activate:true} -->
<!--/ko-->
</div>
schedule.html
<div class="schedule-editor">
</div>
And the schedule module
define([], function () {
var vm = {
activate: activate,
};
return vm;
function activate() {
$('.schedule-editor').datepicker();
console.log("activated schedule module");
return true;
}
});
Console logs "activated schedule module", but the datepicker is not created.
If I go to the chrome console and run the jQuery call,
$('.schedule-editor').datepicker(); it brings up the datepicker just fine.
The Durandal docs claim that the activate function is called after the DOM is full composed, so I don't know what else to try.
Like nemesv mentioned you should use viewAttached instead.
define([], function () {
var vm = {
viewAttached: viewAttached,
};
return vm;
function viewAttached(view) {
$(view).find('.schedule-editor').datepicker();
console.log("activated schedule module");
return true;
}
});
Activate happens in the lifecycle before your model has been data-bound to the new view and before the view has been added to the dom. viewAttached happens after the view has been data-bound to your model and attached to the dom.
EDIT
Durandal 2.0 has renamed viewAttached to attached
There is another approach to this that stays true to the declarative UI philosophy that knockout.js and durandal are striving for.
It will allow you to declare the datepicker within the HTML like this:
<div class="schedule-editor" data-bind="
jqueryui: {
widget: 'datepicker',
options: {
// you can set options here as per the jquery ui datepicker docs
}
}">
</div>
Simply include the jquery ui widget bindings found in this gist: https://github.com/SteveSanderson/knockout/wiki/Bindings---jqueryui-widgets
Make sure you include the above javascript after you have loaded jquery, jquery ui and knockout.
I'm developing a web community in CakePHP and started to have doubts about using jQuery to provide useful widgets around the views when needed.
For example, I've wrote a jQuery plugin which searches the data inside specific input text, asks to my database and get the results handled with events.
Basically the plugin is perfect for simple applications but it's basically useless for a community where I use it almost in all the views and every time handling it with different events and methods, making it with huge event declarations and very annoying to be debugged.
I thougt to solve the problem by using default events from the widget and add the possibility set additional events specific for the view, but how can i do that?
this is the situation i thought
The Green area of the image is where I'm not sure, where I should put the default events to be retrived every time i need them? After known that then, in the view, I could add some event to the widget to be more easy to use.
For widget I intend every kind of html portion is loaded via javascript and is interactive, maybe an input search which retrieves a list of results or something like that.
My question is how can I set default events in runtime to the widget without copy and paste every time?
And my second question is, how can I add to them specific events for the view only?
Some tutorial somewhere online would be also nice.
My answer requires backbone.js so I am not sure if this will help you.
You could separate your widget into a wrapper and the real widget.
The wrapper could handle events like your close event:
var WidgetWrapper = Backbone.View.extend({
tagName: 'div',
// This should be a mustache template:
template: '<a class="close" href="#">close</a><div class="content"></div>',
events: {
'.close click': 'close',
'.open click' : 'open'
},
close: {
this.$el.hide();
},
open: {
alert('I am open');
}
render: {
this.$el.html(Mustache.to_html(view.template, view.model.toJSON());
}
});
The real widget could render itself inside the wrapper widget and both views could interact with the data model (this.model).
var SpecialWidget = Backbone.View.extend({
tagName: 'div',
// This should also be a mustache template:
template: '<input> open',
events: {
'input change': 'edit'
},
render: function() {
if(!this.wrapper) {
this.wrapper = new WidgetWrapper();
}
// Hand over the model to the wrapper
this.wrapper = this.model;
// Render the wrapper
this.wrapper.render();
// Insert the widget content inside the wrapper
this.$el.empty().append(this.wrapper.$el);
this.$(".content").html(Mustache.to_html(view.template, view.model.toJSON());
},
edit: function() {
alert("Changed");
},
});
This would allow you to separate your events.
You could also do it the other way round and use a wrapper with a sub 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.