I created an application and it worked fine, most of the functionality is just the application reacting to different events. I would like to implement a router so that users would be able to show their search results with other users etc. The problem is the router appears to be set up correctly, as when i use it to append things directly to the body everything works as expected. When I try to use the router to trigger events though nothing happens, any help would be greatly appreciated. It is worth mentioning I suppose that this is not the complete code but just the parts that seemed relevant to the issue I am experiencing.
IEG = new Backbone.Marionette.Application();
IEG.addRegions({
searchBox: '#searchBox',
resultBox: '#resultBox',
modalBox: '#modalBox',
recipientBox: '#recipientBox',
confirmBox: '#confirmToggleActive'
});
IEG.vent = _.extend({}, Backbone.Events);
IEG.Router = Backbone.Marionette.AppRouter.extend({
routes: {
'': 'index'
},
index: function () {
IEG.vent.trigger("default"); ////TRIGGER EVENT DOES NOT WORK
//$(document.body).append("Index route has been called..");
}
});
SearchBoxView = Backbone.Marionette.ItemView.extend({
template: Handlebars.templates['search'],
events: {
'click #addGroup': 'addGroup',
'keyup #searchStr': 'evaluateSearch'
},
addGroup: function () {
IEG.vent.trigger("addGroup");
},
clearValidationMsg: function () {
$('#searchErrors').html("");
},
evaluateSearch: function (e) {
console.log("keyup DO SOMETHING> ", e.keyCode);
if (e.keyCode === 13) {///press enter execute search
var searchStr = $('#searchStr').val().trim();
if (searchStr) {
IEG.vent.trigger("searchGroups", searchStr);
}
}
else if (e.keyCode === 8 || e.keyCode === 46) {//backspace and delete keys
var searchStr = $('#searchStr').val().trim();
if (!searchStr) {//when searchbar is cleared show all groups
IEG.vent.trigger("searchGroups", null)
}
}
},
validateEmail: function (searchStr) {
return /^.+#.+\..+$/.test(address);
}
});
$(document).ready(function () {
IEG.start();
new IEG.Router;
Backbone.history.start();
IEG.vent.on("default", function () {
var SBV = new SearchBoxView();
IEG.searchBox.show(SBV);
IEG.searchColl = new GroupEntries();
IEG.searchColl.fetch({
data: {
cmd: 0, //search groups
searchStr: null //if null show all groups
},
success: function (data) {
searchResults = new SearchResultsView({ collection: IEG.searchColl });
IEG.resultBox.show(searchResults);
}
});
});
});
Make sure your event listeners are defined before the event is triggered. Most likely, the event trigger is working fine, but your event listener is registered after the router has triggered the event and nothing happens...
Related
I made this JS to add a functionality on a form (backend) that computes a field when the event click is triggered. So far the code recomputes when I use ".include" but the whole JS in all views fail since I'm using ".include". When I try to use extend my code does nothing. Looks like Odoo doesn't add the extended code to the JS engine so my question is, what am I doing wrong here? Is there something else I need to add so my code works as extended?
odoo.define('med_care.TestRenderer', function (require) {
"use strict";
var viewRegistry = require('web.view_registry');
var FormRenderer = require('web.FormRenderer');
var FormView = require('web.FormView');
var TestFormRenderer = FormRenderer.extend({
events: _.extend({}, FormRenderer.prototype.events, {
'click .sign_selector': '_onSignSelectorClicked',
}),
init: function (parent, state, params) {
this._super.apply(this, arguments);
this.fields = state.fields;
this._onSignSelectorClicked = _.debounce(this._onSignSelectorClicked, 300, true);
},
confirmChange: function (state, id, fields, e) {
var self = this;
if (state.model == 'med.test') {
return this._super.apply(this, arguments).then(function () {
self.canBeSaved(self.state.id);
});
}
},
_onSignSelectorClicked: function (event) {
this.state.data.telephone = '333';
if (this.state.model == 'med.test') {
var info_test = {
dataPointID: this.state.id,
changes: {telephone: '333'},
viewType: "form",
notifyChange: true
};
var odoo_event = this.trigger_up('field_changed', info_test);
this.confirmChange(this.state, this.state.id, "telephone",
odoo_event)
}
},
});
var TestFormView = FormView.extend({
config: _.extend({}, FormView.prototype.config, {
Renderer: TestFormRenderer,
}),
});
viewRegistry.add('test_form', TestFormView);
return TestFormView;
});
Hy, so I'm trying to catch a triggered event, but it looks like i'm missing something:
So i have backbone view, and two plane old javascript objects, one is a "controller" and one is my "app" object, and it looks like this:
// View
SearchView = Backbone.View.extend({
...
events: {
'keyup .input' : 'search'
},
search: function (e) {
var keyCode = e.keyCode || e.which;
this.trigger("search:auto", keyCode);
}
});
// Controller
SearchController = function (options) {
...
this.view = options.view;
this.view.on("search:auto", this.search, this);
this.search = function () {
console.log('great');
};
};
// App
App = function() {
...
this.views = {
searchView : new SearchView()
};
new SearchController({
view: this.views.searchView
});
this.start = function() {
...
};
}
new App().start();
And the problem is, that i want to catch the event triggered by the view in the controller, but somehow its not working, i knew to Backbone, maybe i missed something.
Thanks for any help.
Ok, so at last i figured it out, the problem was the order, the event was registered before i declared my function, this is the correct order:
// Controller
SearchController = function (options) {
...
this.view = options.view;
this.search = function () {
console.log('great');
};
this.view.on("search:auto", this.search, this);
};
I've got a collection view with two filter methods, and a render method which takes a parameter. The problem I'm stuck with is that when rendering the view for the first time it returns me an error. Here's my collection:
var ResumeCollection = Backbone.Collection.extend({
url: 'http://localhost:3000',
filterActive: function () {
var active = this.where({interviewed: false});
return new ResumeCollection(active);
},
filterInterviewed: function () {
var interviewed = this.where({interviewed: true});
return new ResumeCollection(interviewed);
}
});
And my view:
var ResumeList = Backbone.View.extend({
events { // hash array of filter events },
initialize: function () {
this.collection.fetch();
},
render: function (filtered) {
var self = this;
var data;
if (!filtered) {
data = this.collection.toArray();
} else {
data = filtered.toArray();
}
_.each(data, function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
return this;
},
showActive: function (ev) {
var filtered = this.collection.filterActive();
this.render(filtered);
},
showInterviewed: function (ev) {
var filtered = this.collection.filterInterviewed();
this.render(filtered);
},
showAll: function (ev) {
this.render(this.collection);
}
});
This view gets rendered for the first time in my router by passing a collection:
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home'
},
initialize: function () {
this.layout = new LayoutView();
}
home: function () {
this.layout.render(new ResumeList({
collection: new ResumeCollection()
}));
}
});
And this is the layout view within which all the other views are rendered:
var LayoutView = Backbone.View.extend({
el: $('#outlet'),
render: function (view) {
if (this.child && this.child !== view) {
this.child.undelegateEvents();
}
this.child = view;
this.child.setElement(this.$el).render();
return this;
}
});
When I just refresh my page, I get filtered.toArray is not a function error and nothing is rendered respectively. After inspecting everything in the debugger, I found out that when the view gets rendered for the first time, the filtered attribute receives an empty collection, assigns it to data variable, which becomes an empty array and goes to the body of render function, becoming undefined after that. The mysteries go here: whenever I click items, that are bound to my show* events, they act exactly as expected and render either models where interviewed === false, or true or the whole collection. This looks kinda magic to me and I haven't got the faintest idea what can I do with that.
ADDED: GitHub repo with this project
Your home function on the AppRouter has a typo. You have an extra semi-colon.
home: function () {
this.layout.render(new ResumeList({
collection: new ResumeCollection();
}));
}
Should be
home: function () {
this.layout.render(new ResumeList({
collection: new ResumeCollection()
}));
}
I needed to remove it to get the JSFiddle working: https://jsfiddle.net/4gyne5ev/1/
I'd recommend adding some kind of linting tool into your IDE or Build process (http://eslint.org/)
You need to add home url content to your db.json file like this
"" : [
{
'somthing': 'somthing'
}
]
After a piece of advice from my mentor I realized that the core of the problem was in asynchronous origin of fetch method -- as I passed this.collection.fetch in my initialize function, it executed after my render method, not before it, so my render method had just nothing to render when the view was called for the first time. So, this fix worked:
var ResumeList = Backbone.View.extend({
initialize: function (options) {
this.collection = options.collection();
// removed .fetch() method from here
},
render: function (filtered) {
var self = this;
var data;
// and added it here:
this.collection.fetch({
success: function (collection) {
if (!filtered) {
data = collection.toArray();
} else {
data = filtered.toArray();
}
self.$el.html(self.template(collection.toJSON()));
_.each(data, function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
})
}
});
}
});
And this worked perfectly and exactly as I needed.
I have a router on my application now it respond as expected when I type in the url. For exapmple if I type in www.example.com#search/groupa, I get the appropriate result back. I have attempted in the search feature to call navigate to set the url so that a user could cut and paste that and send it to another user. The issue is it doesnt work I get the following error when attempting to do so: "Uncaught TypeError: Object function (){return i.apply(this,arguments)} has no method 'navigate'"
IEG = new Backbone.Marionette.Application();
IEG.addRegions({
searchBox: '#searchBox',
resultBox: '#resultBox',
modalBox: '#modalBox',
recipientBox: '#recipientBox',
confirmBox: '#confirmToggleActive'
});
IEG.vent = _.extend({}, Backbone.Events);
IEG.vent.on("default", function () {
var SBV = new SearchBoxView();
IEG.searchBox.show(SBV);
IEG.searchColl = new GroupEntries();
IEG.searchColl.fetch({
data: {
cmd: 0, //search groups
searchStr: null //if null show all groups
},
success: function (data) {
searchResults = new SearchResultsView({ collection: IEG.searchColl });
IEG.resultBox.show(searchResults);
}
});
});
IEG.vent.on("searchGroups", function (searchStr) {
IEG.Router.navigate("search" + searchStr); // CALLING NAVIGATE HERE
IEG.searchColl.fetch({
data: {
cmd: 0, //search groups
searchStr: searchStr
},
success: function (data) {
searchResults = new SearchResultsView({ collection: IEG.searchColl });
IEG.resultBox.show(searchResults);
}
});
});
IEG.Router = Backbone.Router.extend({
routes: {
'': 'index',
'search/:str': 'search',
'edit/:grp': 'edit'
},
index: function () {
IEG.vent.trigger("default");
},
search: function (str)
{
IEG.vent.trigger("searchGroups",str);
}
});
$(document).ready(function () {
IEG.start();
new IEG.Router;
Backbone.history.start();
});
You need to call navigate on the instance of the Router class and not on its definition (as you're currently doing). Try updating the code in your document ready handler like this:
$(document).ready(function () {
IEG.start();
IEG.router = new IEG.Router(); // Store an instance of the router on the Application
Backbone.history.start();
});
And your searchGroups handler like this:
IEG.vent.on("searchGroups", function (searchStr) {
IEG.router.navigate("search" + searchStr); // call navigate on the instance
// Fetch code .....
});
So, I am able to validate just fine when I am editing an existing item. However, if I want to create, validation for some reason is not getting kicked off. Instead, I am seeing the errors below:
//this is if the field I want to validate is empty
Uncaught TypeError: Object #<Object> has no method 'get'
//this is if everything in the form is filled out
Uncaught TypeError: Cannot call method 'trigger' of undefined
Here is(what I think is) the relative portion of my js. Sorry if its an overload, I wanted to add as much as I can to be as specific as possible:
Comic = Backbone.Model.extend({
initialize: function () {
this.bind("error", this.notifyCollectionError);
this.bind("change", this.notifyCollectionChange);
},
idAttribute: "ComicID",
url: function () {
return this.isNew() ? "/comics/create" : "/comics/edit/" + this.get("ComicID");
},
validate: function (atts) {
if ("Name" in atts & !atts.Name) {
return "Name is required";
}
if ("Publisher" in atts & !atts.Publisher) {
return "Publisher is required";
}
},
notifyCollectionError: function (model, error) {
this.collection.trigger("itemError", error);
},
notifyCollectionChange: function () {
this.collection.trigger("itemChanged", this);
}
});
Comics = Backbone.Collection.extend({
model: Comic,
url: "/comics/comics"
});
comics = new Comics();
FormView = Backbone.View.extend({
initialize: function () {
_.bindAll(this, "render");
this.template = $("#comicsFormTemplate");
},
events: {
"change input": "updateModel",
"submit #comicsForm": "save"
},
save: function () {
this.model.save(
this.model.attributes,
{
success: function (model, response) {
model.collection.trigger("itemSaved", model);
},
error: function (model, response) {
model.trigger("itemError", "There was a problem saving " + model.get("Name"));
}
}
);
return false;
},
updateModel: function (evt) {
var field = $(evt.currentTarget);
var data = {};
var key = field.attr('ID');
var val = field.val();
data[key] = val;
if (!this.model.set(data)) {
//reset the form field
field.val(this.model.get(key));
}
},
render: function () {
var html = this.template.tmpl(this.model.toJSON());
$(this.el).html(html);
$(".datepicker").datepicker();
return this;
}
});
NotifierView = Backbone.View.extend({
initialize: function () {
this.template = $("#notifierTemplate");
this.className = "success";
this.message = "Success";
_.bindAll(this, "render", "notifySave", "notifyError");
comics.bind("itemSaved", this.notifySave);
comics.bind("itemError", this.notifyError);
},
events: {
"click": "goAway"
},
goAway: function () {
$(this.el).delay(0).fadeOut();
},
notifySave: function (model) {
this.message = model.get("Name") + " saved";
this.render();
},
notifyError: function (message) {
this.message = message;
this.className = "error";
this.render();
},
render: function () {
var html = this.template.tmpl({ message: this.message, className: this.className });
$(this.el).html(html);
return this;
}
});
var ComicsAdmin = Backbone.Router.extend({
initialize: function () {
listView = new ListView({ collection: comics, el: "#comic-list" });
formView = new FormView({ el: "#comic-form" });
notifierView = new NotifierView({el: "#notifications" });
},
routes: {
"": "index",
"edit/:id": "edit",
"create": "create"
},
index: function () {
listView.render();
},
edit: function (id) {
listView.render();
$(notifierView.el).empty();
$(formView.el).empty();
var model = comics.get(id);
formView.model = model;
formView.render();
},
create: function () {
var model = new Comic();
listView.render();
$(notifierView.el).empty();
$(formView.el).empty();
formView.model = model;
formView.render();
}
});
jQuery(function () {
comics.fetch({
success: function () {
window.app = new ComicsAdmin();
Backbone.history.start();
},
error: function () {
}
});
})
So, shouldnt my create be getting validated too? Why isnt it?
When creating a new instance of a model, the validate method isn't called. According to the backbone documentation the validation is only called before set or save.
I am also struggling with this problem and found solutions in related questions:
You could make a new model and then set its attributes (see question 9709968)
A more elegant way is calling the validate method when initializing the model (see question 7923074)
I'm not completely satisfied with these solutions because creating a new instance of the model like described in the backbone documentation shouldn't happen when an error is triggered. Unfortunately, in both solutions you're still stuck with a new instance of the model.
edit: Being stuck with a new instance of the model is actually quite nice. This way you can give the user feedback about why it didn't pass the validator and give the opportunity to correct his/her input.
OK. So, I'm having some mild success here.
First, I wrote my own validation framework, Backbone.Validator since I didn't like any of the ones out there that I found.
Second, I am able to get the validation framework to set off the validation routine by setting silent: false with in the object provided during the new Model creation.
Along with using the use_defaults parameter from my validation framework I am able to override bad data during setup in initial testing. I'm still working on doing some more tests on this, but it seems to be going OK from from the Chrome browser console.