Reputation: 734
I was trying on the basics of Jquery plugin and the prototype concept, but ended up in an unusual behavior.
HTML :
<div>
<span>
<textarea>Text Area with 500 characters. Adding Some text.</textarea>
<span class="cl"></span>
</span>
<span>
<textarea>Text Area with 100 characters</textarea>
<span class="cl"></span>
</span>
</div>
JQuery :
(function ($) {
var tisCharsLeftCntxt = null;
function fnCharsLeft(ele, genStngs) {
this.jqe = $(ele);
this.maxChars = genStngs.maxChars;
tisCharsLeftCntxt = this;
this.fnInit();
}
fnCharsLeft.prototype = {
fnInit: function () {
tisCharsLeftCntxt.fnUpdateRemainingChars();
tisCharsLeftCntxt.jqe.keyup(function (event) {
key = event.keyCode ? event.keyCode : event.which;
if ((37 != key) && (38 != key) && (39 != key) && (40 != key)) {
tisCharsLeftCntxt.fnUpdateRemainingChars();
}
});
},
fnUpdateRemainingChars: function () {
var charsLft = tisCharsLeftCntxt.maxChars - tisCharsLeftCntxt.jqe.val().length,
jqeDestToUpdt = tisCharsLeftCntxt.jqe.siblings('.cl');
charsLft = (charsLft < 0) ? 0 : charsLft;
if (charsLft) {
jqeDestToUpdt.text(charsLft + ' more of ' + tisCharsLeftCntxt.maxChars + ' characters');
} else {
tisCharsLeftCntxt.jqe.val(tisCharsLeftCntxt.jqe.val()
.substring(0, tisCharsLeftCntxt.maxChars));
tisCharsLeftCntxt.jqe.scrollTop(tisCharsLeftCntxt.jqe[0].scrollHeight);
jqeDestToUpdt.text("Maximum limit of " + tisCharsLeftCntxt.maxChars + " characters reached");
return false;
}
}
};
$.fn.fnCharsLeftPlgn = function (genStngs) {
return $(this).data("charsleft", new fnCharsLeft(this, genStngs));
};
})(window.jQuery);
$('div span:nth-child(1) textarea').fnCharsLeftPlgn({maxChars: 500});
$('div span:nth-child(2) textarea').fnCharsLeftPlgn({maxChars: 100});
Fiddle : http://jsfiddle.net/5UQ4D/ & http://jsfiddle.net/5UQ4D/1/
Requirement is, the plugin should show the number of characters that can be added in a text-area. If there is only one text-area in a page this is working good. But if there are more than one, only the text-area which is last associated with the plugin is working properly.
With respect to code here, In both the text-area number of characters left is updated correctly during initialization (only for the first time). But later when the text area content is changed, only the second with 100 chars (or the most recent text-area associated with the plugin) is working properly.
Seems like, I'm failing to restrict the plugin context independently to a text-area. Please Advice,..
Upvotes: 1
Views: 153
Reputation: 19560
Problem 1:
As mentioned in the comments, you're creating a variable named tisCharsLeftCntxt
outside of the other contexts, then assigning this
to it in your constructor. Every time you run your plugin you stomp on tisCharsLeftCntxt
with a new this
.
There is no reason to use a reference to this
in the wholesale fashion in which you have. There is only one place in your code where the scope changes such that this
is no longer your instance. That place is inside of the keyup
event handling function. You should localize your aliasing of this
to just the method which contains that event handler.
Problem 2:
I believe another part of your problem (this would be seen if you ran the plugin against a selector which matched more than one element) is inside of the plugin function (the one which lives off of $.fn
).
$.fn.fnCharsLeftPlgn = function (genStngs) {
return $(this).data("charsleft", new fnCharsLeft(this, genStngs));
};
It should be:
$.fn.fnCharsLeftPlgn = function (genStngs) {
return this.each(function () {
$(this).data("charsleft", new fnCharsLeft(this, genStngs));
});
};
When directly inside of a method which has been added to the jQuery prototype ($.fn
), this
refers to the entirety of the current collection, not an element. A plugin should each
itself in order to run element specific logic against its individual members.
Without using .each()
you are calling .data()
against an entire collection, setting all of their charsleft
data properties to the one instance of fnCharsLeft
. By using .each()
you create a new instance of fnCharsLeft
for each of the elements in the collection.
Since the .each()
then returns the collection, and a plugin should be chainable, you simply return it.
A rule of thumb is that if you're passing this
into the jQuery factory ($()
) directly inside of a plugin, function then you're doing something wrong since it is already the collection. As a second rule of thumb, almost all plugin definitions except those which are intended to return info about an element (such as .val()
, .html()
, or .text()
when not given a param) should start with return this.each(function() {...
Solutions:
Bringing those changes together results in this fiddle: http://jsfiddle.net/5UQ4D/4/
And this code:
(function ($) {
var fnCharsLeft = function (ele, genStngs) {
this.jqe = $(ele);
this.maxChars = genStngs.maxChars;
this.fnInit();
};
fnCharsLeft.prototype = {
fnInit: function () {
var instance = this;
this.fnUpdateRemainingChars();
this.jqe.on('keyup', function (e) {
key = e.keyCode ? e.keyCode : e.which;
if (37 != key && 38 != key && 39 != key && 40 != key) {
instance.fnUpdateRemainingChars();
}
});
},
fnUpdateRemainingChars: function () {
var charsLft = this.maxChars - this.jqe.val().length,
jqeDestToUpdt = this.jqe.siblings('.cl');
charsLft = charsLft < 0 ? 0 : charsLft;
if (charsLft) {
jqeDestToUpdt.text(charsLft + ' more of ' + this.maxChars + ' characters');
} else {
this.jqe
.val(this.jqe.val().substring(0, this.maxChars))
.scrollTop(this.jqe[0].scrollHeight);
jqeDestToUpdt.text("Maximum limit of " + this.maxChars + " characters reached");
return false;
}
}
};
$.fn.fnCharsLeftPlgn = function (genStngs) {
return this.each(function () {
$(this).data('charsleft', new fnCharsLeft(this, genStngs));
});
};
}(window.jQuery));
Upvotes: 1