I am writing javascript code with revealing prototype pattern for the first time. I am having problems. When I call add function when user clicks add button then it shows me this error in the console.
Uncaught TypeError: Cannot call method 'add' of undefined
How can I solve this problem?
here is my script.js code
$(function () {
var todo = Todo('contents');
$('.addBtn').on('click', function() {
var name = $(this).parent().find('input[type="text"]').val();
todo.add(name);
});
$('.contents').on('click', '.remove', function() {
var el = $(this).parent();
todo.remove(el);
});
$('.contents').on('click', '.update', function() {
var dom = $(this);
todo.addUpdateField(dom);
});
$('.contents').on('click', '.updateBtn', function() {
var el = $(this);
todo.update(el);
});
});
here is my todo.js code
var Todo = function(c) {
this.contents = $('.' + c);
};
Todo.prototype = function() {
var showel = function (d) {
this.contents.prepend(d);
},
add = function (name) {
if(name != "") {
var div = $('<div class="names"></div>')
.append('<span>' + name + '</span>')
.append("<button class='update' class='update'>Edit</button>")
.append("<button class='remove' name='remove'>Remove</button>");
}
return showel(div);
},
addUpdateField = function (dom) {
var name = dom.parent().find('span').text(),
field = $('<input type="text" value="' + name + '" />'),
update = $('<button class="updateBtn">Update</button>');
dom.parent().html('').append(field).append(update);
return;
},
update = function(el) {
var val = el.parent().find('input').val();
el.parent().html('<span>' + val + '</span>')
.append('<button class="update" class="update">Edit</button>')
.append('<button class="remove" class="remove">Remove</button>');
return;
},
remove = function (el) {
return el.remove();
};
return {
add : add,
update : update,
remove : remove,
addUpdateField : addUpdateField
};
}();
Update
After changing
var todo = Todo('contents');
to
var todo = new Todo('contents');
I get this error
Object [object Object] has no method 'add'
update 2
here is my on jsfiddle
You're not properly constructing your object, so it does not have any of the prototypes:
var todo = Todo('contents');
should be:
var todo = new Todo('contents');
Here is an SO question explaining what is happening when you forget the new.
Edit: the way you are defining your prototype functions is messing up the context (what this points to). Try a pattern like this instead:
Todo.prototype = {
method1: function () { ... },
method2: function () { ... }
};
fixed fiddle: http://jsfiddle.net/BagmY/3/
You're trying to assign Todo's prototype to a self-calling function. However, the prototype is just getting assigned to a function and not the expected return object.
Here's your working fiddle. I assigned the prototype directly to an object with your methods.
Also, JS parses from top to bottom. Therefore, define your new instance of Todo after you declare what Todo is.
This is what a self-calling function should look like:
Todo.prototype = (function() {
// ...
return {
add: add,
update: update,
remove: remove,
addUpdateField: addUpdateField
};
})();
Related
at the company where Im at we use jquery and a lot of the code is very spaghetti haphazard code. So in an effort to organize it better im researching implementing the pub sub model described in this article
So I made a really basic version of it like so:
var topics = {};
jQuery.Topic = function( id ) {
var callbacks, method,
topic = id && topics[ id ];
if ( !topic ) {
callbacks = jQuery.Callbacks();
topic = {
publish: callbacks.fire,
subscribe: callbacks.add,
unsubscribe: callbacks.remove
};
if ( id ) {
topics[ id ] = topic;
}
}
return topic;
};
$(function() {
var testService = new TestService();
testService.subscribe();
var testView = new TestView(testService);
testView.initEvents();
});
/* ---------------------VIEW----------------- */
var TestView = function(testService) {
this.testService = testService;
};
TestView.prototype.initEvents = function () {
this.publishers();
};
TestView.prototype.publishers = function() {
$("#search").on("click", function () {
var isValid = this.testService.validateForm("#container");
if(isValid){
$.Topic( "search" ).publish();
}
})
};
/* ---------------------SERVICE----------------- */
var TestService = function() {
this.testIdea = [];
};
TestService.prototype.validateForm = function (section) {
var referralValid = true;
$(section).find('input,select').filter('[required]:visible').each(function (i, requiredField) {
if(requiredField.value === '') {
//'breaks' the loop out
referralValid = false;
return referralValid;
}
});
return referralValid;
};
TestService.prototype.search = function() {
};
TestService.prototype.subscribe = function() {
var self = this;
$.Topic("search").subscribe( function() {
self.search()
});
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div>
<div id="container">
<input type="text">
</div>
<button id="search">Search</button>
</div>
However when I put that in jsfiddle I get the error that Uncaught TypeError: TestService is not a constructor
in the stackoverflow snippet and on my local version I get a different error of Uncaught TypeError: Cannot read property 'validateForm' of undefined. I cant see what Im doing wrong. Any pointers?
You can declare constructor functions in the way you are doing it (assigning constructor to variable):
var TestView = function(testService) {
this.testService = testService;
};
Like in this simple example:
var myClass = function(name) {
this.name = name;
}
myClass.prototype = {
hello: function() {
console.log('Hello ' + this.name);
}
}
var me = new myClass('Andrew');
me.hello();
But you must remember to declare them before they are used. If you use function statement(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function) as suggested by Chad Watkins it helps only because of hoisting(http://adripofjavascript.com/blog/drips/variable-and-function-hoisting.html) not because of function statement being mandatory for constructors.
The error in your code is in line:
$("#search").on("click", function () {
var isValid = this.testService.validateForm("#container");
you are referencing jQuery object inside a callback not TestView instance, you probably wanted something like this(pun not intended):
...
var self = this;
$("#search").on("click", function () {
var isValid = self.testService.validateForm("#container");
...
trying to get my head around objects, methods, closures, etc... in Javascript.
Can't see why this isn't working, some fundamental flaw in my thinking I guess. I'm expecting the val variable to be passed through to the addNote() function but it isn't. I thought that any variables declared outside of a function are available to that function, as long as they're not within another function. Is that not correct?
if(typeof(Storage) !== "undefined") {
console.log(localStorage);
var $input = $('#input'),
$submit = $('#submit'),
$list = $('#list'),
val = $input.val();
var noteApp = {
addNote : function(val) {
var item = val.wrap('<li />');
item.appendTo($list);
clearField();
},
clearField : function() {
$input.val = '';
},
delNote : function(note) {
}
};
$submit.on('click', function(){
noteApp.addNote();
});
} else {
}
I'm trying to learn how the pros manage to get their code so clean, concise and modular. I figured a note app would be a perfect start, shame I got stuck at the first hurdle...
Cheers.
There are several issues with the code in the question
defining an argument named val and not passing an argument to the function
when calling clearField() inside the object literal it's this.clearField()
You're only getting the value once, not on every click
val is a string, it has no wrap method
$input.val = ''; is not valid jQuery
I would clean it up like this
var noteApp = {
init: function() {
if (this.hasStorage) {
this.elements().events();
}
},
elements: function() {
this.input = $('#input');
this.submit = $('#submit');
this.list = $('#list');
return this;
},
events: function() {
var self = this;
this.submit.on('click', function(){
self.addNote();
});
},
hasStorage: (function() {
return typeof(Storage) !== "undefined";
})(),
addNote: function() {
this.list.append('<li>' + this.input.val() + '</li>');
this.clearField();
return this;
},
clearField: function() {
this.input.val('');
},
delNote : function(note) {
}
}
FIDDLE
Remember to call the init method
$(function() { noteApp.init(); });
In your call to addNote(), you don't pass any argument for the val, so it will be undefined:
noteApp.addNote();
// ^^ nothing
Pass the input (seems you want the jQuery object not the string value because of your val.wrap call):
noteApp.addNote($input);
When you declare the val in the function, it is scoped to that function and will only be populated if the function call passes a value for that argument. Even if you have another variable in an upper scope with the same name val, they are still differentiated. Any reference to val in the function will refer to the local val not the upper scope.
So I bind my Knockout template as follows:
First ajax, get data then I pass the data can call a function named bindKo:
function bindKo(data) {
var length = data.length;
var insertRecord = {};
if (length > 0) {
insertRecord = data[data.length - 1]; //last record is an empty PremlimViewModel for insert
insertRecord.Add = true;
data.splice(data.length - 1, 1); //remove that blank insert record
}
function prelims(data) {
var self = this;
var model = ko.mapping.fromJS(data, { copy: ["_destroy"] }, self);
self.BidPriceFormatted = ko.computed({
read: function () {
var bidPrice = this.BidPrice();
if (bidPrice) {
if (!isNaN(bidPrice)) {
var input = '<input type="text" value="' + bidPrice + '"/>';
return $(input).currency({ decimals: 0 }).val();
}
}
},
write: function (value) {
value = value.replace(/\D/g, '');
this.BidPrice(value);
},
owner: this
});
return model;
}
var mapping = {
create: function (options) {
return new prelims(options.data);
}
};
function viewModel(prelimData) {
var self = this;
self.prelims = ko.mapping.fromJS(prelimData, mapping);
self.remove = function (prelim) {
self.prelims.destroy(prelim);
};
self.addOption = function () {
var clone = jQuery.extend(true, {}, insertRecord);
self.prelims.push(ko.mapping.fromJS(clone));
};
}
ViewModel = new viewModel(data);
ko.applyBindings(ViewModel);
}
I have a template defined where you can add and remove records, and user does just that:
<script type="text/html" id="PrelimsTemplate">
<!--Template Goodness-->
</script>
Then, ajax call, records updated in datanbase, latest results returned and I do:
ko.mapping.fromJS(newestData, ViewModel)
But this does not work because my ViewModel is complex.
So I would just like to reBind the template entirely. Make is disappear and reappear with latest data.
Wrap your template in a container than you can hook onto with jQuery.
When you need to trash it use ko.cleanNode and jQuery .empty()
emptyTemplate: function(){
ko.cleanNode($('#template-container')[0]);
$('#template-container').empty();
}
Load your template back up
fillTemplate: function(){
$('#template-container').html('<div data-bind="template: {name:\'templateId\', data: $data}"></div>');
ko.applyBindings(data,$('#template-container')[0])
},
See my fiddle
I'm having an issue with a helper function inside my Backbon.js View. When it's run, it dies with the following error message about the first line of the "addCalc" function:
TypeError: this.getCalcValue is not a function
It's really puzzling because in the "initialize" function defined just above, all the functions seem to be defined. It feels like I'm calling the sibling method wrong, and the "initialize" method is an exception where "this" can be used to reference the object.
Is there something wrong/missing with the following code, or something I missed with the backbone documentation?
CalcView = Backbone.View.extend({
el: $("#calcView"),
initialize: function () {
this.resetCalc();
},
addCalc: function (model) {
var cost = this.getCalcValue(model.get('currentCost'));
var custom = this.getCalcValue(model.get('customProgram'));
var variables = { id: model.get('id'),
category: model.get('category'),
shortDesc: model.get('shortDescription'),
description: model.get('description'),
currentCost: cost,
customProgram: custom,
};
var template = _.template($('#calc_template').html(), variables);
$("#calc_payload").append(template);
},
resetCalc: function(models) {
$("#calc_payload tr").remove();
},
removeCalc: function(model){
$("#calc_payload #" + model.get('id')).remove();
},
updateCalcs: function(model) {
var cost = model.get('currentCost');
var custom = model.get('customProgram');
$("#" + model.get("id") + " .currentCost").text(this.getCalcValue(cost));
$("#" + model.get("id") + " .customProgram").text(this.getCalcValue(custom));
/*var currentCostSum = 0;
var customProgramSum = 0;
$("#calc_payload .currentCost").each(function() {
var temp = Number(($(this).text()).replace(/[^0-9\.]+/g, ""));
if (!isNaN(temp))
currentCostSum += temp;
});
$("#calc_payload .customProgram").each(function() {
var temp = Number(($(this).text()).replace(/[^0-9\.]+/g, ""));
if (!isNaN(temp))
customProgramSum += temp;
});
$("#calc_footer .currentCost").text("$" + ((currentCostSum == 0) ? " -- " : CurrencyFormatted(currentCostSum.toFixed(2))));
$("#calc_footer .customProgram").text("$" + ((customProgramSum == 0) ? " -- " : CurrencyFormatted(customProgramSum.toFixed(2))));*/
},
getCalcValue: function(value) {
if (typeof value == 'string' || value instanceof String)
return value.toString();
else if (isNaN(value))
return "$ -- ";
else
return "$" + value.toFixed(2);
},
});
The code that executes the "addCalc" function is driven by a backbone collection. Basically, when the collection is added to, the CalcView.addCalc is called
Calculations = Backbone.Collection.extend({
model: Calculation,
//This is our Friends collection and holds our Friend models
initialize: function (models, options) {
this.on("add", options.onAdd);
this.on("remove", options.onRemove);
this.on("reset", options.onReset);
//Listen for new additions to the collection and call a view function if so
}
});
//This is where addCalc is used.
var calcview = new CalcView();
var calc_collection = new Calculations( null, {
onAdd: calcview.addCalc,
onRemove: calcview.removeCalc,
onReset: calcview.resetCalc
});
In your initialize function add this line of code:
_.bindAll(this,'addCalc');
This will bind this to be your CalcView for the addCalc function. You can put multiple comma separated method names in there if you need to bind more than one function...
See Underscore's documentation on it here.
When you bind events on collection you can send the context as third argument. Try sending one more option property as your calcview and pass it as context.
this.on("add", options.onAdd, options.calcview);
this.on("remove", options.onRemove, options.calcview);
this.on("reset", options.onReset, options.calcview);
I cannot find an proper example for the love of my life on how to do this or even if this is possible. Based on my pieced together understanding from fragments of exmaples, I have come up with the following structure
var t = function()
{
this.nestedOne = function()
{
this.nest = function()
{
alert("here");
}
}
}
t.nestedOne.nest();
However this is not working (obviously). I would greatly appreciate if someone could point me in the right direction!
That is simply done with:
var t = {
nestedOne: {
nest: function() {
alert('here');
}
}
};
Your code otherwise doesn't make sense. this inside function doesn't refer to the function itself, it refers to the object context that the function is invoked in. And you are not even invoking the functions in your code.
If I say obj.func() then this inside func will be obj for that call. So assigning this.asd = true will assign true to that object's "asd" property.
If you wanted to do a nested class, it looks very different:
ClassA = (function() {
function ClassA() {
}
ClassA.prototype.method1 = function() {
};
function ClassB() {
}
ClassB.prototype.method1 = function() {
};
return ClassA;
}())
only ClassA can now make instances of ClassB. This should achieve same goals as nested classes in java.
See http://jsfiddle.net/CstUH/
function t(){
function f(){
this.nest = function()
{
alert("here");
}
}
this.nestedOne = new f();
}
var myt=new t();
myt.nestedOne.nest()
Edit 1:
You can also use
new t().nestedOne.nest()
instead of
var myt=new t();
myt.nestedOne.nest()
(http://jsfiddle.net/CstUH/1/)
Edit 2:
Or even more condensed:
function t(){
this.nestedOne = new function(){
this.nest = function(){
alert("here");
}
}
}
new t().nestedOne.nest()
http://jsfiddle.net/CstUH/2/
In JS functions are prime class objects, and you can access them directly in the code [i.e. without using reflection or so].
The code you put inside t body would be performed when actually executing t:
t();
You wrote t.nestedOne,nest(), but t has no nestedOne property - you should do like this:
var t = {
nestedOne : {
nest : function()
{
alert("here");
}
}
};
t.nestedOne.nest();
I advice you to have a trip on John Resig's Learning Advanced JavaScript tutorial, it was very enlightening for me.
A simple callback handler I wrote today as an example of how I do deep nesting. I apologize if it's not the bees knees when it comes to code style, it made the concept a little clearer for me.
function test () {
this.that = this;
this.root = this;
this.jCallback = new Array(new Array()); // 2d
this.jCallbackCount = -1;
this.str = "hello";
// Callback handler...
this.command = {
that : this, // let's keep a reference to who's above us on the food chain
root : this.root, // takes us back to the main object
// add : function() { var that = this; console.log(that.that.str); },
add : function(targetFnc, newFunc) {
var that = this;
var home = that.that; // pretty much root but left in as an example of chain traversal.
var root = this.root; // useful for climbing back up the function chain
// console.log(that.that.str);
home.jCallbackCount++;
// target, addon, active
home.jCallback[home.jCallback.length] = { 'targetFunc' : targetFnc, 'newFunc' : newFunc, 'active' : true, 'id': home.jCallbackCount};
console.log('cbacklength: ' + home.jCallback.length);
console.log('added callback targetFunction:[' + targetFnc + ']');
return home.jCallbackCount; // if we want to delete this later...
},
run : function(targetFnc) {
var that = this;
var home = that.that;
console.log('running callback check for: ' + targetFnc + ' There is : ' + (home.jCallbackCount + 1) + 'in queue.');
console.log('length of callbacks is ' + home.jCallback.length);
for(i=0;i < home.jCallback.length - 1;i++)
{
console.log('checking array for a matching callback [' + targetFnc + ']...');
console.log('current item: ' + home.jCallback[i]['targetFunc'] );
if( home.jCallback[i]['targetFunc'] == targetFnc )
{
// matched!
home.jCallback[i]['newFunc']();
}
// console.log(that.that.jCallback[i].targetFunction);
}
}
};
}
test.prototype = {
say : function () {
var that = this;
console.log('inside');
// that.command('doSay');
that.command.run('doSay');
console.log(that.str);
}
} // end proto
// BEGIN TESTING **************************************************************************
// BEGIN TESTING **************************************************************************
// BEGIN TESTING **************************************************************************
var testing = new test();
testing.command.add('doSay', function () { console.log('213123123'); } );
testing.command.add('doSay', function () { console.log('12sad31'); } );
testing.command.add('doSay', function () { console.log('asdascccc'); } );
testing.say();
live:
http://jsfiddle.net/Ps5Uf/
note: to view console output, just open inspector in chrome and click on the "console" tab.