This javascript present on all site pages.
var CategoriesView = Backbone.View.extend({
el: $('.js-categories-view'),
initialize: function () {
...
}
...
templates: {
'category-template': _.template($('#js-category-template').html())
},
});
And if page haven't element $('.js-categories-view'), code like: new CategoriesView(); it will not be called and initialize not be called too.
But what about templates section?
_.template($('#js-category-template').html()) always called and I get the error:
Uncaught TypeError: Cannot read property 'length' of undefined
Because $('#js-category-template') doesn't present on page and and I don't want store template html on page where this View not needed?
You can check if the needed element present on the current page like this:
if( $('.js-categories-view').length ){
new CategoriesView();
}
And if you want to prevent error when calling the _.template() function, you also need to move the definition of the templates object into the initialize method of the view like follows:
var CategoriesView = Backbone.View.extend({
el: $('.js-categories-view'),
initialize: function () {
this.templates = {
'category-template': _.template($('#js-category-template').html())
};
...
}
});
Thanks all! It's good solution. But eventually I implement this:
$(function () {
if (!$('.js-categories-view').length) {
return;
}
var CategoriesPageModel = Backbone.Model.extend({
...
to not to think about issues like this. But I think thant better solution can be found
Related
I have the following js file that handles a widget and I would like to overwrite and add code for custom events function, but when I tried to instantiate, nothing seems to be on the object:
This a reference for the script that I want to overwrite
odoo.define('my_module.my_report', function (require) {
'use strict';
var myWidget = AbstractAction.extend(ControlPanelMixin, {
custom_events: {
},
}
core.action_registry.add('my_report', myWidget );
return myWidget
});
});
I have tried inheriting using the following:
var InheritedWidget = require('my_module.my_report);
and also:
var InheritedWidget = core.action_registry.get('my_report');
and when I tried to override, nothing seems to happen:
InheritedWidget.include({
custom_events: {
//My custom code goes here
}
})
Do you know how to override this widget or method?
You need to extend the custom_events of an existing widget.
var InheritedWidget = require('my_module.my_report');
InheritedWidget.include({
custom_events: _.extend({}, InheritedWidget.prototype.custom_events, {
//My custom code goes here
}),
});
For more details refer to the event system documentation.
I had asked a question here that referred me to use using the backbone events. It works great, except that my event listener onFormSubmit() gets called twice. Normally I wouldn't care, but I have a function that toggles some states, and toggling this twice creates a problem.. I thought my view was being rendered twice (based on other answers on SO), but it does not appear to be so. I am trying to understand the 'why' behind what is happening here.. Here is some code (with non-relevant stuff removed)..
Here is my article form view that calls triggers the events on a form save and it gets triggered once (correct intended behavior) and it gets redirected back to the dashboard..
var PostView = Backbone.View.extend({
el: '#content',
articletemplate: _.template($('#article-template-add').html()),
initialize: function() {
this.render();
},
render: function() {
this.$el.html(this.articletemplate({}));
},
events: {
"click .save": "savePost",
},
savePost: function() {
var mypost = new PostModel();
this.model = mypost;
this.model.save(null, {
success: function(model) {
eventBus.trigger('formSubmitted', {
message: "form submitted"
});
app.navigate("/", false);
},
});
},
});
Here is my Dashboard view() that gets called after the form submit above.. Here is where onFormSubmit() executes twice (the console.log() gets printed twice).
var DashboardView = Backbone.View.extend({
el: '#content',
dashboardtemplate: _.template($('#dashboard-template').html()),
initialize: function() {
this.listenToOnce(eventBus, 'formSubmitted', this.onFormSubmit);
this.render();
},
onFormSubmit: function(datahere) {
console.log("onFormSubmit called"); // *** This gets printed twice
},
render: function() {
$(this.el).empty().html(this.dashboardtemplate(this.model.attributes));
},
});
Now, I am beginning to think that there might be some problem in the main app routing?
var AppRouter = Backbone.Router.extend({
routes: {
"": "dashboard",
"article/add": "addarticle",
},
dashboard: function() {
mydashview = new DashboardView();
},
addarticle: function() {
var articleView = new PostView();
},
});
var eventBus = _.extend({}, Backbone.Events);
var app = new AppRouter();
Backbone.history.start();
EDIT
I updated the savePost() to include that the trigger is called in the callback of the this.model.save()..I've forced it to create a dummy model instead of taking it from a form. The good news is that I was able to recreate the behavior here: http://jsfiddle.net/okswxngv/ If you open your console, you can see the onFormSubmit called printing twice.
Your problem is linked to the ghost view. Some call it zombie. The DashboardView is created every time you enter the root page, but never removed.
That is why he is going to exist, even if you link a new view to the #content div.
You can put a break point on DashboardView ->initialize and see that is called twice.
To better understand I have changed you code and added a name to the view (which is the date when it was created) and printed this name.
To get read of the problem you have to remove the unneeded view when you create a new one.
I am bit new to knockout and jquery mobile, There was a question which is already answered, I need to optimize the PageStateManager class to use generic bindings, currently PageStateManager can only use for one binding,I would really appreciate if someone can guide me to create a generic class to manage page states with knockout bindings Heere is the working code,http://jsfiddle.net/Hpyca/14/
PageStateManager = (function () {
var viewModel = {
selectedHospital: ko.observable()
};
var changePage = function (url, viewModel) {
console.log(">>>>>>>>" + viewModel.id());
$.mobile.changePage(url, {viewModel: viewModel});
};
var initPage = function(page, newViewModel) {
viewModel.selectedHospital(newViewModel);
};
var onPageChange = function (e, info) {
initPage(info.toPage, info.options.viewModel);
};
$(document).bind("pagechange", onPageChange);
ko.applyBindings(viewModel, document.getElementById('detailsView'));
return {
changePage: changePage,
initPage: initPage
};
})();
Html
<div data-role="page" data-theme="a" id="dashBoardPage" data-viewModel="dashBoardViewModel">
<button type="button" data-bind="click: goToList">DashBoard!</button>
</div>
New dashboard model
var dashBoardViewModel = function() {
var self = this;
self.userName = ko.observable('Welcome! ' + "UserName");
self.appOnline = ko.observable(true);
self.goToList = function(){
//I would like to use PageStateManager here
// PageStateManager.changePage($("#firstPage"),viewModel);
ko.applyBindings(viewModel,document.getElementById("firstPage"));//If I click Dashbord button multiple times it throws and multiple bind exception
$.mobile.changePage($("#firstPage"));
}
}
ko.applyBindings(dashBoardViewModel,document.getElementById("dashBoardPage"));
update url : http://jsfiddle.net/Hpyca/14/
Thank you in advance
I would probably go for creating a NavigationService which only handles changing the page and let knockout and my view models handle the state of the pages.
An simple example of such a NavigationService could be:
function NavigationService(){
var self = this;
self.navigateTo = function(pageId){
$.mobile.changePage($('#' + pageId));
};
}
You could then, in your view models just call it when you want it to navigate to a new page. One example would be upon selection of a hospital (which could be done either via a selection function or by manually subscribing to changes to the selectedHospital observable):
self.selectHospital = function(hospital){
self.selectedHospital(hospital);
navigationService.navigateTo('detailsView');
};
Other than the call to the navigationService to navigate, it's just ordinary knockout to keep track of which viewmodel should be bound where. A lot easier than having jquery mobile keeping track of which viewmodel goes where, if you ask me.
I have updated your jsfiddle to show a sample of how this could be done, making as few changes as possible to the HTML code. You can find the updated fiddle at http://jsfiddle.net/Hpyca/15/
I have the following JavaScript code, which works as expected...
/** #jsx React.DOM */
var TreeView = React.createClass({
render: function() {
return <div ref="treeview"></div>;
},
componentDidMount: function() {
console.log(this.props.onChange);
var tree = $(this.refs.treeview.getDOMNode()).kendoTreeView({
dataSource: ...,
dataTextField: "Name",
select: this.props.onChange
}
});
}
});
var MyApp = React.createClass({
onChange: function(e) {
console.log(e);
this.setState({key: e});
},
render: function() {
return (
<div>
<TreeView onChange={this.onChange}/>
<GridView />
</div>
);
}
});
However, with the kendo treeview, on selecting a tree node, the whole node is passed. To get at the underlying key, I would need to process the node as follows:
select: function(e) {
var id = this.dataItem(e.node).id;
this.props.onChange(id);
}
However I've obviously not quite got it right since, and here please excuse my noobness, it seems that in the working instance a reference to the function is being used, whereas in the non-working instance, the function is actually being executed... Or something like that: the error message being returned is:
Cannot call method 'onChange' of undefined.
So what would I need to do to be able to reference the function which extracts the key before calling the onChange method? Note that, if my understanding is correct, onChange needs to be executed in the context of the MyApp class so that any child components will get notified on the change.
EDIT: I've tried using partial application but am still not quite there. I've updated the onChange routine to take a function which returns the key from the node
onChange: function(getKey, e) {
this.setState({Key: getKey(e)});
},
But am not sure how to wire it all up.
Your code looks mostly right. I believe your only problem is that the select callback you're passing to the treeview is being called with the wrong scope. Note that you're using this to mean two different things within the function (once for the actual tree view object and the other for the React component). Easiest solution is probably to store a reference to the onChange function like so:
componentDidMount: function() {
var onChange = this.props.onChange;
var tree = $(this.refs.treeview.getDOMNode()).kendoTreeView({
dataSource: ...,
dataTextField: "Name",
select: function(e) {
var id = this.dataItem(e.node).id;
onChange(id);
}
});
}
Hope this helps.
I just don't have idea what causes problem and need help. Before posting I've came up to alternative solution, but I want to learn why this is not working properly.
I have router that initialize view which initialize entity collection and views like so:
advertiser_manage_campaign: function () {
this.campaignListView = new window.CampaignListView;
this.mainSidebar = new window.MainSidebar;
},
CampaignListView:
window.CampaignListView = Backbone.View.extend({
el: ("#right_column"),
initialize: function () {
this.render();
this.campaignCollection = new Campaign.CampaignCollection;
this.campaignCollectionView = new Campaign.CampaignCollectionView({ model: this.campaignCollection });
this.campaignCollection.fetch();
},
events: {
"click .campaign_dialog": "openCampaignDialog"
},
openCampaignDialog: function (e) {
var that = this;
var itemID = $(e.target).attr("item-id");
var model = {}; //model to populate dialog inputs
if (!isNaN(itemID))
model = this.campaignCollection.get(itemID).toJSON(); //get existing model from collection <- after described procedure, error
Campaign.Dialog.open(model, function (data) {
if (isNaN(itemID)) {//model does not exist, create
that.campaignCollection.create(data, { wait: true,
error: function (model, error) {
dialoger.showErrors(JSON.parse(error.responseText).errors);
},
success: function (mdl, response) { window.Campaign.Dialog.close(); }
});
} else {//model exist, update
model = that.campaignCollection.get(itemID);
model.save(data, { wait: true,
error: function (mdl, error) {
dialoger.showErrors(JSON.parse(error.responseText).errors);
},
success: function (mdl, response) { window.Campaign.Dialog.close(); }
});
}
});
return false;
},
render: function () {
$(this.el).html(window.Templates.getHTML("campaign_list_view", {}));
$(".button", $(this.el)).button();
}
});
-
openCampaignDialog
is for both edit models and creating new. Every view(table row) of model
has button with class ".campaign_dialog" and there is button for adding new model with same class.
Campaign.Dialog.open
shows dialog populated with model and in callback returns JSON from dialog form.
If I create new model via dialog, I can edit it right away, but when I create new model, change view, back to this view, create again new model, change view and then again back, click edit on last added item, I get error on commented line as model with this ID is not in collection, although it is. Response from server is OK. Obviously, I'm doing something wrong and after one day, I don't see what it is.
Alternative solution I've came up to is to create and populate dialog from event of model view (this works), but I thought that CampaingCollectionView or CampaingView should not deal with adding or editing models so I've implemented this in 'higher' view.
Thanks everyone for helping me...
Edit:
var CampaignCollectionView = Backbone.View.extend({
el: (".content_table tbody"),
initialize: function () {
this.model.bind("reset", this.render, this);
this.model.bind("add", this.add, this);
},
render: function () {
$(this.el).empty();
_.each(this.model.models, function (campaign) {
$(this.el).append(new CampaignView({ model: campaign }).render().el);
}, this);
return this;
},
add: function (model) {
window.Appender.AppendAndScroll($(new CampaignView({ model: model }).render().el), this.el);
}
});
I've found solution.
Problems arise, though, when we bind objects together through these
events but we don’t bother unbinding them. As long as these objects
are bound together, and there is a reference in our app code to at
least one of them, they won’t be cleaned up or garbage collected. The
resulting memory leaks are like the zombies of the movies – hiding in
dark corners, waiting to jump out and eat us for lunch.
Source: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
Author suggests unbinding mechanism, but I'm going to reuse same objects if exist.
Router:
advertiser_manage_campaign: function () {
if (!this.campaignListView)
this.campaignListView = new window.CampaignListView;
else
this.campaignListView.initialize();
this.mainSidebar = new window.MainSidebar;
},
If someone thinks this is not best solution, I would like to hear why.
Thank you all who tried to help!