Custom extended dijit/_TemplatedMixin throws "Invalid template Error" - javascript

Here is a simplified example of what I am trying to do:
https://jsfiddle.net/1c5qpke9/2/
require([
'dojo/_base/declare',
'dijit/_WidgetBase',
'dijit/_TemplatedMixin',
'dojo/domReady!'
], function(declare, _WidgetBase, _TemplatedMixin) {
var _CustomTemplatedMixin = declare([_TemplatedMixin], {
_stringRepl: function() {
console.log('_stringRepl');
this.inherited(arguments);
},
_fillContent: function() {
console.log('buildRendering');
this.inherited(arguments);
}
});
var CustomWidget = declare([_WidgetBase, _CustomTemplatedMixin], {
templateString: '<div class="${baseClass}"><span>Custom Widget "${testName}"</span></div>',
testName: 'TestName'
});
new CustomWidget({}, 'MyWidget');
});
If I execute this, I get the following error:
Error: Invalid template: Custom Widget
"${testName}"
When I use _TemplatedMixin instead of _CustomTemplatedMixin in my CustomWidget Class. It works without errors.

That is because _stringRepl returns a value, since you are subclassing it it should also return a value so return the inheritance value
ex.
_stringRepl: function() {
console.log('_stringRepl');
return this.inherited(arguments);
},

Related

Uncaught Type Error: View is not a constructor

I have Uncaught Type Error : UserRegisterView is not a constructor.I dont understand this error.I looked all code but i dont find it.
Sorry of my bad english.Please help me
Thanks for answer
UPDATED
UserRegisterView is here
var UserRegisterView = Backbone.View.extend({
model: User,
el: '#form',
events: {
'click input[id="infoWeek"]': 'infoWeek',
'click input[id="infoMonth"]': 'infoMonth'
},
infoWeek: function() {
this.$el.find("#dayOfMonth").hide();
this.render();
},
infoMonth: function() {
this.$el.find("#dayOfWeek").hide();
this.render();
}
});
var AddUserView = Backbone.View.extend({
el: $(".page"),
events: {
'click #saveUser': 'saveUser'
},
saveUser: function() {
var user = new User();
user.set({
username: $("#username").val(),
lastName: $("#lastName").val(),
regNumber: $("#regNumber").val(),
password: $("#password").val(),
departmentName: $("#departmentName").val(),
email: $("#email").val(),
role: $("#role").val()
});
user.save();
if (document.getElementById('isOpen').checked) {
user.set("isOpen", $("#isOpen").val("1"));
user.save();
} else {
user.set("isOpen", $("#isOpen").val("0"));
user.save();
}
if (document.getElementById('dayOfWeek').checked) {
user.set("dayOfWeek", $("#dayOfWeek").val());
user.save();
} else if (document.getElementById('dayOfMonth').checked) {
user.set("dayOfMonth", $("#dayOfMonth").val());
user.save();
}
$("#username").val("");
$("#firstName").val("");
$("#lastName").val("");
$("#regNumber").val("");
$("#password").val("");
$("#deparmentName").val("");
$("#email").val("");
$("#isOpen").val("");
$("#dayOfWeek").val("");
$("#dayOfMonth").val("");
},
render: function() {
var that = this;
var template = Handlebars.compile(UserRegister);
var myHtml = template(that.model.toJSON());
that.$el.html(myHtml);
return this;
}
});
return {
AddUserView: AddUserView,
UserRegisterView: UserRegisterView
};
});
router user func.
define([
'jquery',
'underscore',
'backbone',
'handlebars',
'spin',
'app/models/LoginModel',
'app/views/LoginView',
'app/views/UserRegisterView'
], function($,
_,
Backbone,
Handlebars,
Spinner,
Login,
LoginView,
UserRegisterView
) {
var Router = Backbone.Router.extend({
routes: {
'search': 'search',
'login': 'login',
'travels': 'travels',
'user': 'user',
'menu': 'menu',
'': 'home'
},
user: function() {
disposeView(new UserRegisterView().render());
}
dispose.view on util.js
function disposeView(view) {
Backbone.View.prototype.close = function() {
this.unbind();
this.undelegateEvents();
};
/* Şu anki viewi yok et */
if (this.currentView !== undefined) {
this.currentView.close();
}
/* Yeni view oluştur. */
this.currentView = view;
this.currentView.delegateEvents();
return this.currentView;
}
What's happening
Your UserRegisterView module returns an object which contains two constructors.
return {
AddUserView: AddUserView,
UserRegisterView: UserRegisterView
};
When using this module, what you're getting is the object above.
define([
// ...
'app/views/UserRegisterView'
], function(
// ...
UserRegisterView // value of the return in the module
) {
So you're kind of misleading yourself by calling it UserRegisterView as it's not the constructor, but the object containing the constructor.
To get a new UserRegisterView view instance with the current way your module is setup, you'd need to call it like so:
var userView = new UserRegisterView.UserRegisterView();
Or to create a AddUserView instance:
var addView = new UserRegisterView.AddUserView();
Solutions
Split up the module, one for each view constructor.
Change the name so at least it's not misleading (like UserViewsModule)
Other improvements
That being said, there are other improvements that could be made to your Backbone code.
var UserRegisterView = Backbone.View.extend({
// that's useless (if not used) and not a view property.
// model: User,
// don't use `el` like that, especially when using the view as a shared Constructor
el: '#form',
events: {
'click input[id="infoWeek"]': 'onInfoWeekClick',
'click input[id="infoMonth"]': 'onInfoMonthClick'
},
initialize: function() {
// Cache jQuery object of the view's element
this.$dayOfMonth = this.$("#dayOfMonth");
this.$dayOfMonth = this.$("#dayOfMonth");
// also use the shortcut function instead of `this.$el.find()`
}
onInfoWeekClick: function(e) {
this.$dayOfMonth.hide();
// calling render here is useless unless your using it as a parent
// view, where the child view overrides the render function.
},
onInfoMonthClick: function(e) {
this.$dayOfMonth.hide();
}
});
The disposeView function could be simplified:
function disposeView(view) {
var current = this.currentView;
if (current) current.close();
current = this.currentView = view;
current.delegateEvents();
return current;
}
Don't change the default Backbone view prototype each time the function is called. Instead, add the function once.
_.extend(Backbone.View.prototype, {
close: function() {
this.unbind();
this.undelegateEvents();
},
// any other function you want to add can go here.
});
In another answer, I go into details on how to extend Backbone's core classes with requirejs transparently.
You're already using jQuery, so don't use JavaScript DOM API document.getElementById('isOpen') interspersed with jQuery selectors $('#isOpen').
I made some improvements to the following view. Take the time to create yourself some utility functions (like reset and getValues) to simplify the flow of the code and encapsulate the complexity.
var AddUserView = Backbone.View.extend({
el: $(".page"),
events: {
'click #saveUser': 'saveUser'
},
// compile the template once while creating the view class
template: Handlebars.compile(UserRegister),
// get the selector string out of the code and place them in one place
// easy to change and maintain.
fields: {
username: "#username",
firstName: "#firstName",
lastName: "#lastName",
regNumber: "#regNumber",
password: "#password",
deparmentName: "#deparmentName",
email: "#email",
isOpen: "#isOpen",
dayOfWeek: "#dayOfWeek",
dayOfMonth: "#dayOfMonth",
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
// cache jQuery object of every field once after a render
this.field = _.reduce(this.fields, function(fields, selector, key) {
fields['$' + key] = this.$(selector);
return fields;
}, {}, this);
return this;
},
reset: function() {
// reset all the fields once without repeating code.
_.each(this.field, function($field) {
$field.val("");
});
return this;
},
getValues: function(keys) {
// get the value of multiple fields returned in a nice object
// ready to be sent to a Backbone model.
return _.reduce(keys, function(data, key) {
data[key] = this.field[key].val();
return data;
}, {}, this);
},
saveUser: function() {
var field = this.field,
user = new User(this.getValues([
'username',
'lastName',
'regNumber',
'password',
'departmentName',
'email',
'role',
]));
user.set({ isOpen: field.$isOpen.is(':checked') });
if (field.$dayOfWeek.is(':checked')) {
user.set("dayOfWeek", field.$dayOfWeek.val());
} else if (field.$dayOfMonth.is(':checked')) {
user.set("dayOfMonth", field.$dayOfMonth.val());
}
user.save();
this.reset();
},
});
In the following snippet, you're putting the context (this) into a local variable. I see that a lot and I could say that 90% of the times I see it on Stack Overflow questions, it makes no sense. It clearly screams copy-pasted.
render: function() {
var that = this;
// ...
that.$el.html(myHtml);
return this;
}
Please tell me you see that you're putting this into that, then using that throughout the function, then you still return this?!
Putting the context into a local variable is useful when the object is needed in a dynamically created callback.
render: function() {
var that = this; // this is available here
setTimeout(function() {
// here this is not available.
that.handleCallback();
}, 10);
// here we are in the same context as the first line.
return this;
}

dojo require and scope

Can anyone explain to me why when drawSection is called 'this' value becomes the global scope ?.
Is there anyway to use require here without having to save the widget in another variable before i lose it ?
define("my/TextBox", [
"dojo/_base/declare",
"dijit/form/ValidationTextBox"
], function(
declare, ValidationTextBox
) {
function drawSection() {
alert(this);
require(["dojo/dom-construct"], function(domConstruct) {
alert(this); // this = window
});
};
return declare([ValidationTextBox], {
postCreate: function() {
this.inherited(arguments);
drawSection.call(this)
}
});
});
It's quit simple use dojo/_base/lang hitch() function to solve the issue .
because the function inside the require(["dojo/dom-construct"], function(domConstruct) {....}) is refering to global context ,
so use lang.hitch function in the current context (by using this) and probleme is solved
Here is a Fiddle
and above working snippet :
define("my/TextBox", [
"dojo/_base/lang",
"dojo/_base/declare",
"dijit/form/ValidationTextBox"
], function(lang,
declare, ValidationTextBox
) {
function drawSection() {
alert(this);
require(["dojo/dom-construct"], lang.hitch(this,function(domConstruct) {
alert(this); // this = window
}));
};
return declare([ValidationTextBox], {
postCreate: function() {
this.inherited(arguments);
drawSection.call(this)
}
});
});
require([
"dojo/parser",
"my/TextBox",
"dojo/domReady!"
], function(
parser,
TextBox
) {
// important: parse document after the ValidationTextBox was extended
parser.parse();
});
<link href="https://ajax.googleapis.com/ajax/libs/dojo/1.8/dijit/themes/claro/claro.css" rel="stylesheet"/>
<script src="//ajax.googleapis.com/ajax/libs/dojo/1.10.4/dojo/dojo.js"></script>
<body class="claro">
<input type="text" data-dojo-type="my/TextBox" />,
</body>
You need to use dojo/_base/lang lang.hitch like this:
require(["dojo/dom-construct"], lang.hitch(this, function(domConstruct) {
alert(this); // this = window
}));
That's a common closure problem.
See https://dojotoolkit.org/reference-guide/1.10/dojo/_base/lang.html#hitch
As a good practice, I would recommend having the drawSection method inside the widget and the dom-construct required on top (you will always need it as you call it from postCreate so a "on-demand" require is overkill)
define("my/TextBox", [
"dojo/_base/declare",
"dijit/form/ValidationTextBox",
"dojo/dom-construct"
], function(declare, ValidationTextBox, domConstruct) {
return declare([ValidationTextBox], {
postCreate: function() {
this.inherited(arguments);
this.drawSection()
},
drawSection: function() {
alert(this);
//domConstruct.whaever you want
};
});
});

How to update part of a template when a collection is updated?

I'm trying to update only a part of a template used by a Backbone view when a collection is updated. The code below successfully executes the search() and render_list() methods. Furthermore, console.log(html) shows the full template's html. But when I execute the replaceWith, it replaces the selector with empty. If I replace $(selector,html) with a string (ie: 'test'), it successfully replaces with 'test'.
So, for some reason, the $(selector, html) selector isn't doing what it's meant to. What is further weird is that the images within the updated html selector are requested by the browser even though none of the updated html is inserted on into the document.
define([
'jquery',
'underscore',
'backbone',
'collections/tracks',
'collections/genres',
'text!templates/search_view_title.html',
'text!templates/search_view.html'
],function($,_,Backbone,Tracks_collection,Genres_collection,Search_view_title_template,Search_view_template){
var Search_view = Backbone.View.extend({
el: $('#app'),
events: {
'change #genre_select': function(){this.search('genre')},
'click #search_btn': function(){this.search('search')}
},
template: _.template(Search_view_template),
initialize: function(){
// SET SOME IMPORTANT LAYOUT SETTINGS
$('#pagetitle').html(_.template(Search_view_title_template));
$('body').css('padding-top','124px');
this.genre_collection = new Genres_collection();
this.listenTo(this.genre_collection,'update',this.render);
this.genre_collection.fetch();
this.collection = new Tracks_collection();
this.listenTo(this.collection,'update',this.render_list);
},
search: function(searchtype){
switch(searchtype){
case 'genre':
console.log('genre changed');
this.collection.fetch({
data: {limit: 30, type:'genre',genre_id:$('#genre_select').val()}
});
break;
case 'search':
console.log('search changed');
this.collection.fetch({
data: {limit: 30, type:'search',keyword:$('#keyword').val()}
});
break;
}
console.log(this.collection);
},
render_list: function(){
var that = this;
console.log('render list');
var html = that.template({genres: this.genre_collection.models,tracks: this.collection.models});
console.log(html);
var selector = '#tracklist';
console.log($(selector,html));
that.$el.find(selector).replaceWith($(selector,html));
return this;
},
render: function(){
// MAKE 'THIS' ACCESSIBLE
var that = this;
console.log('render');
that.$el.find('#container').html(that.template({genres: this.genre_collection.models}));
return this;
}
});
return Search_view;
});
Without the HTML templates in hand, I can just assume things.
This is closer to how I would do it:
var Search_view = Backbone.View.extend({
el: $('#app'),
events: {
'change #genre_select': 'onGenreChange',
'click #search_btn': 'onSearchClick'
},
template: _.template(Search_view_template),
initialize: function() {
// SET SOME IMPORTANT LAYOUT SETTINGS
$('#pagetitle').html(Search_view_title_template);
// Do this in css
$('body').css('padding-top', '124px');
this.genre_collection = new Genres_collection();
this.genre_collection.fetch();
this.collection = new Tracks_collection();
this.listenTo(this.genre_collection, 'update', this.render);
this.listenTo(this.collection, 'update', this.render_list);
},
render: function() {
console.log('render');
this.$('#container').html(this.template({
genres: this.genre_collection.models
}));
return this;
},
render_list: function() {
console.log('render list');
var html = this.template({
genres: this.genre_collection.models,
tracks: this.collection.models
});
console.log(html);
var $selector = this.$('#tracklist');
console.log($selector);
$selector.replaceWith($(html));
return this;
},
////////////
// events //
////////////
onSearchClick: function() {
console.log('search changed');
this.collection.fetch({
data: { limit: 30, type: 'search', keyword: $('#keyword').val() }
});
},
onGenreChange: function() {
console.log('genre changed');
this.collection.fetch({
data: { limit: 30, type: 'genre', genre_id: $('#genre_select').val() }
});
},
});
$('#pagetitle').html(_.template(Search_view_title_template));
The _.template function returns a function, which itself returns the rendered template when called.
It can be confused with this.template which often contains the result of _.template and is ready to be called (this.template(data)).
Split your callbacks, functions are cheap and unnecessary switch are ugly.
I made your search into onGenreChange and onSearchClick.
$('body').css('padding-top','124px');
Try to avoid that, it can be easily done with CSS, or even inline <style> tag or inline style="" attribute. If it's necessary for you as it's related to a dynamic behavior, create a class (e.g. search-class) in a css file, then toggle the class with jQuery, moving the "design" responsability out of the js:
$('body').toggleClass('search-class');
var that = this;
This is only necessary when dealing with callbacks where the context (this) is different in the callback. In Backbone, most of the time, it's avoidable as the context option is often available and automatically set on most (like the events callbacks).
this.$el.find(selector)
This is equivalent to this.$(selector). Just a little shortcut.
.replaceWith($(selector,html));
replaceWith expects a htmlString or Element or Array or jQuery.
$(selector, html) expects a selector and a context. You want $(html) to transform your html string into a jQuery element.

BackboneJS Uncaught Error: A "url" property or function must be specified

I am getting this error . I am able to preform read, and remove functions using BackboneJs , but i am having error when i execute the add method any help will be appreciated.
JSfiddel path is http://jsfiddle.net/2wjdcgky/
BackboneJS Uncaught Error: A "url" property or function must be specified
$(function() {
Model
var modelContact = Backbone.Model.extend({
defaults: function() {
return {
Id: 0,
Name: "",
Address: ""
};
},
idAttribute: "Id"
});
ModelCollection
var contactCollection = Backbone.Collection.extend({
model: modelContact,
url: function() {
return 'api/Contact';
},
add: function(model) {
this.sync("create", model); // Error On create
},
remove: function(model) {
this.sync("delete", model); //Runs Fine
}
});
var contacts = new contactCollection;
View
var contactView = Backbone.View.extend({
tagName: "tr",
events: {
"click a.destroy": "clear"
},
template: _.template($("#newContacttemplate").html()),
initialize: function() {
this.model.on("change", this.render, this);
this.model.on('destroy', this.remove, this);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
clear: function(e) {
contacts.remove(this.model); // runs fine
}
});
Main View
var main = Backbone.View.extend({
el: $("#contactApp"),
events: {
"click #btnsave": "CreateNewContact"
},
initialize: function() {
this.Nameinput = this.$("#contactname");
this.Addressinput = this.$("#contactaddress");
contacts.on("add", this.AddContact, this);
contacts.on("reset", this.AddContacts, this);
contacts.fetch();
},
AddContact: function (contact) {
console.log("AddContact");
var view = new contactView({ model: contact });
this.$("#tblcontact tbody").append(view.render().el);
},
AddContacts: function () {
console.log("AddContacts");
contacts.each(this.AddContact);
},
CreateNewContact: function (e) {
console.log(e);
//Generate an error "BackboneJS Uncaught Error: A "url" property or function must be specified"
contacts.add({ Name: this.Nameinput.val(), Address: this.Addressinput.val() });
}
});
var m = new main;
});
Your JSFiddle was missing Backbone references and all.
Working update: http://jsfiddle.net/apt7hchL/2/
Much simpler code (no need to define those add and remove methods on the collection!). Also more common Javascript coding style conventions.
Please note I had to manually generate an "Id" attribute to allow creating more than one contact. As you are making Id = 0 by default, second model with same is not added, as Backbone sees a model with id=0 is already in the collection.
When you want to save, call the model.save() method. Don't call sync manually, you'll normally don't need to!
For the model to be saved to the database before being added to the collection, use:
createNewContact: function (e) {
e.preventDefault();
var self = this;
var newContact = new ContactModel({
Name: this.$("#name").val(),
Address: this.$("#address").val()
});
newContact.save({ success: function(model){
self.collection.add(model);
});
//clear form
this.$("#name").val("");
this.$("#address").val("");
}
Sync method tries to sync to a server setup to handle it, with CRUD abilities. If thats not what you're looking for, and you just want to display this information on the client side, instead of using sync, you should use Collection.add(model) and Collection.remove(model)

Backbone - Validation not working on create, only update/edit?

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.

Categories

Resources