I want to do stuff when ember bindings have synchronized and the DOM is again up to date.
I have tried with a callback from the function that manipulates the binded model, DOM is not updated when callback is executed.
I have tried with an observer directly on the model, DOM is not updated when the observer is executed.
I have tried with an observer on the binding, DOM is not updated when the observer is executed.
e.g.
App.view = Ember.View.extend({
modelBinding: 'App.model',
modelChanged : function() {
window.scrollTo(0, document.body.scrollHeight);
}.observes('model'),
getMore: function(event) {
App.set('model', "somethingnew");
}
});
When I fire the "gotMore", I update the model, and when the model is updated and its changes have been rendered I want to scroll down.
In none of the ways I've tried have I been able to get the new scrollHeight. It is set a few ms after these events.
Here's an example on jsFiddle:
http://jsfiddle.net/kcjzw/15/
The correct way to do this is documented here:
http://emberjs.com/api/classes/Ember.run.html#method_next
modelChanged : function() {
Ember.run.scheduleOnce('afterRender', this, function() {
window.scrollTo(0, document.body.scrollHeight);
$('body').append('New scroll height: '+document.body.scrollHeight);
});
}.observes('content')
Use Ember.run.next
https://github.com/emberjs/ember.js/blob/master/packages/ember-metal/lib/run_loop.js#L531-566
App.view = Ember.View.extend({
modelBinding: 'App.model',
modelChanged : function() {
Ember.run.next(myContext, function(){
// code to be executed in the next RunLoop, which will be scheduled after the current one
window.scrollTo(0, document.body.scrollHeight);
});
}.observes('model'),
getMore: function(event) {
App.set('model', "somethingnew");
}
});
Update
Take a look at this: http://jsfiddle.net/ud3323/hZ7Vx/
What you need is to run your code after the runloop that renders the Ember.CollectionView that the {{each}} helper would create.
JavaScript:
App = Ember.Application.create();
App.model = Ember.Object.create({
items: [1]
});
App.view = Ember.Handlebars.EachView.extend({
contentBinding: 'App.model.items',
itemViewClass: Ember._MetamorphView.extend({
templateName: 'the_template'
}),
modelChanged : function() {
Ember.run.next(this, function(){
window.scrollTo(0, document.body.scrollHeight);
$('body').append('New scroll height: '+document.body.scrollHeight);
});
}.observes('content'),
theAction: function(event) {
App.controller.doStuffToModel();
}
});
App.controller = Ember.Object.create({
doStuffToModel : function() {
App.model.set('items', [1,2,3,4,5]);
}
});
Handlebars:
<script type="text/x-handlebars" data-template-name="the_template">
<div style="height:200px;"></div>
</script>
<script type="text/x-handlebars">
{{view App.view}}
</script>
Related
I have menu item that when clicked should open a chat panel that is part of a different view. Today is my first day looking at Backbone and im a little confused about how I can call a function on another view when the element is clicked.
I want something like the following on the menu view:
view.$('.wkVideoCall')
.on('click', function() {
wk.ui.videoChat.videoChatView.open();
wk.ui.videoChat.openForChatUser();
});
Where wk.ui.videoChat.videoChatView is the view that has .open() as a method.
But the above is not working, is there an event like change but for click that i can be using? or some sort of bind function?
I know have something like this:
View 1 (menu):
events: {
'click .wkVideoCall': 'openVideoChat'
},
openVideoChat: function() {
var videoChatView = new wk.ui.videoChat.videoChatView();
videoChatView.render();
},
View 2 (chat panel):
(function() {
var self = wk.ui.videoChat;
self.VideoChatView = Backbone.View.extend({
el: function() {
[0];
return wk.core.template.load('videoChatTmpl')[0];
},
initialize: function() {
var view = this,
model = view.model,
$el = view.$el;
.on('myEvent', this.open);
view.$('.wkUnmuteVideo')
.on('click', function() {
self.muteMyVideo(false);
});
view.$('.wkUnmuteAudio')
.on('click', function() {
self.muteMyAudio(false);
});
view.$('.wkOptions')
.on('focus', function() {
self.showOptions();
})
.on('blur', function() {
self.hideOptions();
});
},
render: function() {
console.log("inside");
}
});
})();
But nothing is logged
Basically when you click you should render or re-render a subview as muistooshort wrote.
This is a pretty good example : http://todomvc.com/examples/backbone/
And there is plenty of tutorials out there.
I am trying to trigger the event of parent view from its child view. But it seems that the parent view's event did not gets triggered.
Following is the sample for my code:
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/underscore.js/1.1.4/underscore-min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/backbone.js/0.3.3/backbone-min.js"></script>
<script>
MySubView = Backbone.View.extend({
id : "MySubView",
initialize: function() {
console.log("pro1");
this.trigger("testGo", "test");
}
});
MyView = Backbone.View.extend({
id : "MyView",
initialize: function() {
console.log("pro");
this.subview = new MySubView();
this.subview.listenTo("testGo", this.addFoo, this);
},
addFoo: function() {
console.log("ok");
alert("ok");
}
});
new MyView();
</script>
</head>
<body>
</body>
</html>
I tried to get hint from many solutions found via google search, but it seems I am got struck somewhere. Some of the options which I found are :
1/ How to trigger an event from child view to parent view in backbonejs and requirejs
2/ Backbone: How to trigger methods in a parent view
The problem is that you are using listenTo wrong.
anObject.listenTo(anotherObject, 'forSomeEvent', function () { console.log('do something'); });
So, in your case, you should do it like this:
MyView = Backbone.View.extend({
id : "MyView",
initialize: function() {
console.log("pro");
this.subview = new MySubView();
this.listenTo(this.subview, 'testGo', this.addFoo);
},
addFoo: function() {
console.log("ok");
alert("ok");
}
});
Hope it helps!
Your listenTo usage is slightly off
The signature is object.listenTo(otherObject, event, callback) so you want something like:
this.listenTo(this.subview, "testGo", this.addFoo);
Which tells this to listen to this.subview for the event testGo and call this.addFoo when the event is triggered
try this
this.listenTo(this.subview, "testGo",this.addFoo);
signature:
object.listenTo(other, event, callback)
Triggering event:
Backbone.Events.trigger('<eventname>', {data1 to be passed with event}, {data2 to be passed with event}...);
Listener:
Backbone.Events.bind('<eventname>', callback, context);
In this code sample, a Backbone view is bound to a pre-existing DOM element. The scroll event triggers as expected.
In this alternate sample, the Backbone view renders the HTML instead of using a pre-existing DOM element. The scroll event doesn't fire.
Why?
The primary difference is the second sample does this:
this.$el.html(template);
This works:
http://jsfiddle.net/hKWR9/1/
$(function(){
var MyView = Backbone.View.extend({
className: 'scrollbox',
events: {
'click': 'onClick',
'scroll': 'onScroll'
},
initialize: function() {
this.render();
},
render: function() {
var template = '<div class="filler"></div>';
$('body').append(this.$el);
this.$el.html(template);
},
onClick: function() {
console.log('click');
},
onScroll: function() {
console.log("scroll");
}
});
var App = new MyView();
}());
Your fiddle doesn't work, because you defined your el with classname .scrollbox, while it should have been scrollbox. There doesnt seem to be a benefit in creating another 'scrollbox' within this '.scrollbox'.
I am writing some integration tests for my Backbone views/models/collections. When I call render on my View, it simply renders a template to it's own el property, hence the html is simply stored in memory rather than on the page. Below is a simple model, and a view with a click event bound to a DOM element:
var model = Backbone.Model.extend({
urlRoot: '/api/model'
});
var view = Backbone.View.extend({
events: {
'click #remove': 'remove'
}
render: function () {
var html = _.template(this.template, this.model.toJSON());
this.$el.html(html);
},
remove: function () {
this.model.destroy();
}
});
I am using Jasmine to write my tests. In the test below all I want to do is spy on the remove function to see if it is called when the click event is fired for the element #remove which is present in the template I pass to the view.
// template
<script id="tmpl">
<input type="button" value="remove" id="remove"/>
</script>
// test
describe('view', function () {
var view;
beforeEach(function () {
view = new view({
template: $('#tmpl').html(),
model: new model()
});
});
it('should call remove when #remove click event fired', function () {
view.$('#remove').click();
var ajax = mostRecentAjaxRequest();
expect(ajax.url).toBe('/api/model');
expect(ajax.method).toBe('DELETE');
});
});
However, as the #remove element is in memory, and it hasn't actually been added to the DOM, I'm not sure how you would simulate the click event. In fact I'm not even sure if it's possible?
It may seem a bit strange to want to do this in a test, but with my tests I am trying to test behaviour rather than implementation, and this way I don't care what is happening in between - I just want to test that if the user clicks #remove a DELETE request is sent back to the server.
Looks to me like you forgot to call render() on the view before click()ing the button. And the model needs to have an id or backbone won't actually try to make a delete call to the server. I've tested plenty of views just like that before with no problems.
I just ran a similar test against jasmine 2.0 and jasmine-ajax 2.0.
live code:
var MyModel = Backbone.Model.extend({
urlRoot: '/api/model'
});
var MyView = Backbone.View.extend({
events: {
'click #remove': 'remove'
},
initialize: function(options) {
this.template = options.template;
},
render: function () {
var html = _.template(this.template, this.model.toJSON());
this.$el.html(html);
},
remove: function () {
this.model.destroy();
}
});
specs:
describe("testing", function() {
var view;
beforeEach(function() {
jasmine.Ajax.install();
view = new MyView({
template: '<input type="button" value="remove" id="remove"/>',
model: new MyModel({id: 123})
});
view.render();
});
it('should call remove when #remove click event fired', function () {
view.$('#remove').click();
var ajax = jasmine.Ajax.requests.mostRecent();
expect(ajax.url).toBe('/api/model/123');
expect(ajax.method).toBe('DELETE');
});
});
In my app, the <body> tag contains just a single <script type="text/x-handlebars> tag which contains all my views. Sproutcore 2.0 nicely adds a jQuery on-document-ready handler that parses those templates and renders them back into the DOM.
I'd like to call a function on one of the views as soon as it's rendered. The problem is that the re-insertion happens asynchronously, so I don't know when the view is available.
Example
Page
<body>
<script type="text/x-handlebars">
...
{{view "MyApp.TweetInputView"}}
...
</script>
</body>
View:
MyApp.TweetInputView = SC.View.extend({
init: function() {
// act like a singleton
MyApp.TweetInputView.instance = this;
return this._super();
},
focus: function() {
...
this.$().focus();
}
});
Initializer
// if the URL is /tweets/new, focus on the tweet input view
$(function() {
if (window.location.pathname === '/tweets/new') {
// doesn't work, because the view hasn't been created yet:
MyApp.TweetInputView.instance.focus();
}
});
I've also tried SC.run.schedule('render', function() { MyApp.TweetInputView.instance.focus(); }, 'call'); in the hopes that Sproutcore would run that after all the view rendering and insertion, but that does not seem to be the case.
Try this:
MyApp.TweetInputView = SC.View.extend({
didInsertElement: function() {
console.log("I've been rendered!");
}
});