Jquery livequery 'click' doesn't work - javascript

I've got a problem with my Jquery, the alert doesn't pop.
Here is the code :
HTML (I've this div 4 times, which is a button) :
<div class="grid4 text_center" style = "margin: 20px 20px 0px 0px;">
<input value="Calculer" class="bouton btvalid btlong250 validation" type="submit">
</div>
JS :
$(document).ready(function(){
$('.grid4 text_center .bouton btvalid btlong250 validation').livequery('click', function () {
var sTest = 'Click';
alert('' + sTest);
});
});

Change your selector to this:
$('.grid4.text_center .bouton.btvalid.btlong250.validation')
space separated css class names should be concat with a ., so for div that you can do .grid4.text_center then a space now same with the input's css classes .bouton.btvalid.btlong250.validation.
May be this way you should do it:
$(document).ready(function() {
$('.grid4.text_center .bouton.btvalid.btlong250.validation').livequery(function() {
$(this).click(function() {
var sTest = 'Click';
alert('' + sTest);
});
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="grid4 text_center" style="margin: 20px 20px 0px 0px;">
<input value="Calculer" class="bouton btvalid btlong250 validation" type="submit">
</div>
<script>
(function(factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof exports === 'object') {
factory(require('jquery'));
} else {
factory(jQuery);
}
}(function($, undefined) {
function _match(me, query, fn, fn2) {
return me.selector == query.selector &&
me.context == query.context &&
(!fn || fn.$lqguid == query.fn.$lqguid) &&
(!fn2 || fn2.$lqguid == query.fn2.$lqguid);
}
$.extend($.fn, {
livequery: function(fn, fn2) {
var me = this,
q;
// See if Live Query already exists
$.each($jQlq.queries, function(i, query) {
if (_match(me, query, fn, fn2))
// Found the query, exit the each loop
return (q = query) && false;
});
// Create new Live Query if it wasn't found
q = q || new $jQlq(me.selector, me.context, fn, fn2);
// Make sure it is running
q.stopped = false;
// Run it immediately for the first time
q.run();
// Contnue the chain
return me;
},
expire: function(fn, fn2) {
var me = this;
// Find the Live Query based on arguments and stop it
$.each($jQlq.queries, function(i, query) {
if (_match(me, query, fn, fn2) && !me.stopped)
$jQlq.stop(query.id);
});
// Continue the chain
return me;
}
});
var $jQlq = $.livequery = function(selector, context, fn, fn2) {
var me = this;
me.selector = selector;
me.context = context;
me.fn = fn;
me.fn2 = fn2;
me.elements = $([]);
me.stopped = false;
// The id is the index of the Live Query in $.livequery.queries
me.id = $jQlq.queries.push(me) - 1;
// Mark the functions for matching later on
fn.$lqguid = fn.$lqguid || $jQlq.guid++;
if (fn2) fn2.$lqguid = fn2.$lqguid || $jQlq.guid++;
// Return the Live Query
return me;
};
$jQlq.prototype = {
stop: function() {
var me = this;
// Short-circuit if stopped
if (me.stopped) return;
if (me.fn2)
// Call the second function for all matched elements
me.elements.each(me.fn2);
// Clear out matched elements
me.elements = $([]);
// Stop the Live Query from running until restarted
me.stopped = true;
},
run: function() {
var me = this;
// Short-circuit if stopped
if (me.stopped) return;
var oEls = me.elements,
els = $(me.selector, me.context),
newEls = els.not(oEls),
delEls = oEls.not(els);
// Set elements to the latest set of matched elements
me.elements = els;
// Call the first function for newly matched elements
newEls.each(me.fn);
// Call the second function for elements no longer matched
if (me.fn2)
delEls.each(me.fn2);
}
};
$.extend($jQlq, {
guid: 0,
queries: [],
queue: [],
running: false,
timeout: null,
registered: [],
checkQueue: function() {
if ($jQlq.running && $jQlq.queue.length) {
var length = $jQlq.queue.length;
// Run each Live Query currently in the queue
while (length--)
$jQlq.queries[$jQlq.queue.shift()].run();
}
},
pause: function() {
// Don't run anymore Live Queries until restarted
$jQlq.running = false;
},
play: function() {
// Restart Live Queries
$jQlq.running = true;
// Request a run of the Live Queries
$jQlq.run();
},
registerPlugin: function() {
$.each(arguments, function(i, n) {
// Short-circuit if the method doesn't exist
if (!$.fn[n] || $.inArray(n, $jQlq.registered) > 0) return;
// Save a reference to the original method
var old = $.fn[n];
// Create a new method
$.fn[n] = function() {
// Call the original method
var r = old.apply(this, arguments);
// Request a run of the Live Queries
$jQlq.run();
// Return the original methods result
return r;
};
$jQlq.registered.push(n);
});
},
run: function(id) {
if (id !== undefined) {
// Put the particular Live Query in the queue if it doesn't already exist
if ($.inArray(id, $jQlq.queue) < 0)
$jQlq.queue.push(id);
} else
// Put each Live Query in the queue if it doesn't already exist
$.each($jQlq.queries, function(id) {
if ($.inArray(id, $jQlq.queue) < 0)
$jQlq.queue.push(id);
});
// Clear timeout if it already exists
if ($jQlq.timeout) clearTimeout($jQlq.timeout);
// Create a timeout to check the queue and actually run the Live Queries
$jQlq.timeout = setTimeout($jQlq.checkQueue, 20);
},
stop: function(id) {
if (id !== undefined)
// Stop are particular Live Query
$jQlq.queries[id].stop();
else
// Stop all Live Queries
$.each($jQlq.queries, $jQlq.prototype.stop);
}
});
// Register core DOM manipulation methods
$jQlq.registerPlugin('append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html', 'prop', 'removeProp');
// Run Live Queries when the Document is ready
$(function() {
$jQlq.play();
});
}));
</script>

Change your selector to this.
$('.grid4 .validation');

Related

Issues with event delegation on same container

I have a function, simplified like this:
var fooFunction = function($container, data) {
$container.data('foobarData', data);
$container.on('click', 'a', function(e) {
var data = $(e.delegateTarget).data('foobarData');
var $target = $(e.currentTarget);
if (typeof data.validateFunction === 'function' && !data.validateFunction(e)) {
return;
}
e.preventDefault();
e.stopPropagation();
// Do stuff
console.log(data.returnText);
});
};
fooFunction('.same-container', {
validateFunction: function(event) {
return $(e.currentTarget).closest('.type-companies').length ? true : false;
},
returnText: 'Hello Company!',
});
fooFunction('.same-container', {
validateFunction: function(event) {
return $(e.currentTarget).closest('.type-humans').length ? true : false;
},
returnText: 'Hello Human!',
})
I am using event delegation on the same container (.same-container) with a custom validateFunction() to validate if the code in // Do stuff should run.
For each fooFunction() initiation, I have some different logic that will get called on // Do stuff. The issue is that those two event delegations conflict. It seems that only one of them is called and overwrites the other one.
How can I have multiple event delegations with the option to define via a custom validateFunction which one should be called. I use preventDefault() + stopPropagation() so on click on a <a>, nothing happens as long as validateFunction() returns true.
The problem is that you're overwriting $(e.delegateTarget).data('foobarData') every time.
Instead, you could add the options to an array, which you loop over until a match is found.
var fooFunction = function($container, data) {
var oldData = $container.data('foobarData', data);
if (oldData) { // Already delegated, add the new data
oldData.push(data);
$container.data('foobarData', oldData);
} else { // First time, create initial data and delegate handler
$container.data('foobarData', [data]);
$container.on('click', 'a', function(e) {
var data = $(e.delegateTarget).data('foobarData');
var $target = $(e.currentTarget);
var index = data.find(data => typeof data.validateFunction === 'function' && !data.validateFunction(e));
if (index > -1) {
var foundData = data[index]
e.preventDefault();
e.stopPropagation();
// Do stuff
console.log(foundData.returnText);
}
});
}
}

Chaining function not work when inside another function

I try to create chaining function using vanilla javascript, its work if just chaining, but if inside other function its stop working.
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) {
return doc.getElementById(m[1]);
} else if(m[2]) {
return doc.getElementsByTagName(m[2]);
} else if(m[3]) {
return doc.getElementsByClassName(m[3]);
}
},
$ = function (el) {
this.el = M$(el);
// event function
this.event = function(type,fn) {
this.el.addEventListener(type,fn,false);
return this;
}
// forEach function
this.forEach = function(fn,val) {
for(var i = this.el.length - 1; i >= 0; i--) {
fn.call(val, i, this.el[i]);
}
return this;
}
if(this instanceof $) {
return this.$;
} else {
return new $(el);
}
};
//use
$("button").forEach(function(index, el)
// when i use function event, its not work
el.event("click", function() {
alert("hello");
});
// if i'm using addEventListener its work, but i want use event function
});
My question is, how to be event function working inside forEach function?
Thanks for help!
First off, there is an issue with brackets in your code after $("button").forEach(function(index, el) you are missing {;
Then the problem is that when you try to call click-callback on your elements (buttons), in fact, due to the this issues the elements (buttons) don't have event() property. They are not even defined themselves since this.el = M$(el); goes outside forEach(). I tweaked and cleaned a little your code, check it out. I guess now it does what you want:
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) return doc.getElementById(m[1]); else if(m[2]) return doc.getElementsByTagName(m[2]); else if(m[3]) return doc.getElementsByClassName(m[3]);
},
$ = function(el) {
this.forEach = function(fn,val) {
// assign this.el and this.el[i].event inside forEach(), not outside
this.el = M$(el);
for(var i = this.el.length - 1; i >= 0; i--) {
this.el[i].event = function(type,fn) { this.addEventListener(type,fn,false); };
fn.call(val, i, this.el[i]);
}
}
return this;
};
$("button").forEach(function(index, el) {
el.event("click", function() { alert("hello, " + this.textContent); });
});
<button>btn1</button>
<button>btn2</button>
UPDATE
While the previous solution is fine for the particular purpose of setting click handlers on buttons, I think what you really want is to emulate Jquery and chain function calls. I improved your attempt right in this way:
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) return doc.getElementById(m[1]);else if(m[2]) return doc.getElementsByTagName(m[2]); else if(m[3]) return doc.getElementsByClassName(m[3]);
},
$ = function (el) { //console.log(this);
this.el = M$(el);
this.event = function(type,fn) {
for(var i = this.el.length - 1; i >= 0; i--) this.el[i].addEventListener(type,fn,false);
}
this.forEach = function(fn) {
fn.call(this);
}
return this;
};
$("button").forEach(function() {
this.event("click", function() {
alert("hello, " + this.textContent);
});
});
<button>btn1</button>
<button>btn2</button>
The key to understanding here is that your this object should always be equal to $ {el: HTMLCollection(2), event: function, forEach: function}. So,
calling $("button") you initially set it to $ {el: HTMLCollection(2), event: function, forEach: function} - with HTML Collection and event&forEach functions;
calling $("button").forEach(fn) you keep forEach's context equal to this from previous step;
calling fn.call(this); inside forEach() you call your callback fn and pass the same this to it;
inside the callback fn you call this.event() - it works because your this is always the one from the first step.
in this.event() which is just $.event() we just traverse our HTMLCollection and set handlers for click event on buttons. Inside $.event() this will be equal to a button element because we call it in such a context on click event, so, this.textContent takes the buttons' content.
Thanks, really good question!
First things first.
1.
this.el = M$(el);
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) {
return doc.getElementById(m[1]);
} else if(m[2]) {
return doc.getElementsByTagName(m[2]);
} else if(m[3]) {
return doc.getElementsByClassName(m[3]);
}
}
As you defined M$ you can either have a HtmlCollection if you get elements by tag name or by class name or just one element if you get element by id.
Then you suppose that your el is one when it can be a collection.
this.event = function(type,fn) {
this.el.addEventListener(type,fn,false);
return this;
}
You probably receive a collection if you try to get all buttons.
2.
If you try to run posted code you will receive an Unexpected identifier error because you missed a { after forEach(function(index, el).
3.
If you put that { in there you will receive a el.event is not a function error because you don't have an event function on el, but you have that on $(el).
4.
If you change your code to:
$("button").forEach(function(index, el)
{
// when i use function event, its not work
$(el).event("click", function() {
alert("hello");
});
// if i'm using addEventListener its work, but i want use event function
});
You'll receive an error because you didn't handled multiple elements. See 1 problem.
Have a look at this.
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) {
return Array.apply([],[doc.getElementById(m[1])]);
} else if(m[2]) {
return Array.apply([],doc.getElementsByTagName(m[2]));
} else if(m[3]) {
return Array.apply([],doc.getElementsByClassName(m[3]));
}
},
$ = function (el) {
if(! (this instanceof $)) {
return new $(el);
}
this.els = M$(el);
// event function
this.event = function(type,fn) {
this.forEach(function(index, el){
el.addEventListener(type,fn,false);
});
return this;
}
// forEach function
this.forEach = function(fn,val) {
for(var i = this.els.length - 1; i >= 0; i--) {
fn.call(val, i, this.els[i]);
}
return this;
}
return this;
};
//use
$("button").event("click", function() {
alert("hello");
});
Here the M$ function is made to return an array to keep things consistent.
So, the $().event function is changed to iterate through all the elements in this.els.
Hence, you could simply call $("button").event function instead of $("button").forEach function to register event listeners.
Refer: Demo
This one works. But, Is this what you want? I am not sure.

Callbacks with Require JS

So i'm trying to make two functions that allow a user to move an item from their cart to the "saved cart" and vise versa. These functions depend on the "cart group item" module which also contains the events for the button clicks. My question is, i'm unsure how to correctly call these functions to allow the click event to take place in my current js file. Hopefully someone can help!
Event's in module:
cartGroupItem.prototype.createMoveEvent = function (elem) {
if (undefined !== elem && null !== elem) {
var button = elem.querySelector('.cartGroupItem__move');
if (button !== null) {
button.addEventListener('click', function (e) {
ajax('GET',
'/resources/ajax/cart.aspx?sub=3&Saved=0&Cart_Group_ID='+this.cartGroupId,
true, {}, function () {
this.moveToCartCallback();
}.bind(this), function () {
this.showErrorDiv();
}.bind(this));
}.bind(this));
}
}
};
cartGroupItem.prototype.createSaveEvent = function (elem) {
if (undefined !== elem && null !== elem) {
var button = elem.querySelector('.cartGroupItem__save');
if (button !== null) {
button.addEventListener('click', function (e) {
ajax('GET',
'/resources/ajax/cart.aspx?sub=3&Saved=1&Cart_Group_ID='+this.cartGroupId,
true, {}, this.saveForLaterCallback.bind(this), this.showErrorDiv.bind(this));
}.bind(this));
}
}
};
Move functions:
function moveToSaved(cartGroupId) {
for (var i = 0, l = activeCartList.length; i < l; i++) {
if (activeCartList[i].cartGroupId === cartGroupId) {
activeCartList.remove();
savedCartList.push(activeCartList[i]);
}
}
}
function moveToActive(cartGroupId) {
for (var i = 0, l = savedCartList.length; i < l; i++) {
if (savedCartList[i].cartGroupId === cartGroupId) {
savedCartList.remove();
activeCartList.push(savedCartList[i]);
}
}
}
Your Event module probably define the function cartGroupItem right?
What you need to do is pass this function from its file to your current file, and then "instanciate" a carteGroupItem:
// In your current JS file
var myCartGroupItem = new cartGroupItem();
myCartGroupItem.createMoveEvent();
myCartGroupItem.createSaveEvent();
We also would need to see this function "constructor" (where it is defined) as it probably takes a few callbacks as parameters. Otherwise you can add them manually:
myCartGroupItem.moveToCartCallback = function() {
// do what you want when the item is moved to cart
};
// Same for the other callbacks
myCartGroupItem.saveForLaterCallback = function() { ... };
myCartGroupItem.showErrorDiv = function() { ... };
Finally, the way to pass things with RequireJS is that for instance, your Event module returns cartGroupItem so in your file module:
define(['Event'], function(cartGroupItem) {
// Your code where you can use cartGroupItem
});

custom jQuery plugin: events not behaving as expected

I'm writing a lightweight jQuery plugin to detect dirty forms but having some trouble with events. As you can see in the following code, the plugin attaches an event listener to 'beforeunload' that tests if a form is dirty and generates a popup is that is the case.
There is also another event listener attached to that form's "submit" that should in theory remove the 'beforeunload' listener for that specific form (i.e. the current form I am submitting should not be tested for dirt, but other forms on the page should be).
I've inserted a bunch of console.log statements to try and debug it but no luck. Thoughts?
// Checks if any forms are dirty if leaving page or submitting another forms
// Usage:
// $(document).ready(function(){
// $("form.dirty").dirtyforms({
// excluded: $('#name, #number'),
// message: "please don't leave dirty forms around"
// });
// });
(function($) {
////// private variables //////
var instances = [];
////// general private functions //////
function _includes(obj, arr) {
return (arr._indexOf(obj) != -1);
}
function _indexOf(obj) {
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (obj, fromIndex) {
if (fromIndex == null) {
fromIndex = 0;
} else if (fromIndex < 0) {
fromIndex = Math.max(0, this.length + fromIndex);
}
for (var i = fromIndex, j = this.length; i < j; i++) {
if (this[i] === obj)
return i;
}
return -1;
};
}
}
////// the meat of the matter //////
// DirtyForm initialization
var DirtyForm = function(form, options) {
// unique name for testing purposes
this.name = "instance_" + instances.length
this.form = form;
this.settings = $.extend({
'excluded' : [],
'message' : 'You will lose all unsaved changes.'
}, options);
// remember intial state of form
this.memorize_current();
// activate dirty tracking, but disable it if this form is submitted
this.enable();
$(this.form).on('submit', $.proxy(this.disable, this));
// remember all trackable forms
instances.push(this);
}
// DirtyForm methods
DirtyForm.prototype = {
memorize_current: function() {
this.originalForm = this.serializeForm();
},
isDirty: function() {
var currentForm = this.serializeForm();
console.log("isDirty called...")
return (currentForm != this.originalForm);
},
enable: function() {
$(window).on('beforeunload', $.proxy(this.beforeUnloadListener, this));
console.log("enable called on " + this.name)
},
disable: function(e) {
$(window).off('beforeunload', $.proxy(this.beforeUnloadListener, this));
console.log("disable called on " + this.name)
},
disableAll: function() {
$.each(instances, function(index, instance) {
$.proxy(instance.disable, instance)
});
},
beforeUnloadListener: function(e) {
console.log("beforeUnloadListener called on " + this.name)
console.log("... and it is " + this.isDirty())
if (this.isDirty()) {
e.returnValue = this.settings.message;
return this.settings.message;
}
},
setExcludedFields: function(excluded) {
this.settings.excluded = excluded;
this.memorize_current();
this.enable();
},
serializeForm: function() {
var blacklist = this.settings.excludes
var filtered = [];
var form_elements = $(this.form).children();
// if element is not in the excluded list
// then let's add it to the list of filtered form elements
if(blacklist) {
$.each(form_elements, function(index, element) {
if(!_includes(element, blacklist)) {
filtered.push(element);
}
});
return $(filtered).serialize();
} else {
return $(this.form).serialize();
}
}
};
////// the jquery plugin part //////
$.fn.dirtyForms = function(options) {
return this.each(function() {
new DirtyForm(this, options);
});
};
})(jQuery);
[EDIT]
I ended up fixing this by using jQuery's .on() new namespace feature to identify the handler. The problem was that I was passing new anonymous functions as the handler argument to .off(). Thanks #FelixKling for your solution!
this.id = instances.length
[...]
enable: function () {
$(window).on('beforeunload.' + this.id, $.proxy(this.beforeUnloadListener, this));
},
disable: function () {
$(window).off('beforeunload.' + this.id);
},
Whenever you are calling $.proxy() it returns a new function. Thus,
$(window).off('beforeunload', $.proxy(this.beforeUnloadListener, this));
won't have any effect, since you are trying to unbind a function which was not bound.
You have to store a reference to the function created with $.proxy, so that you can unbind it later:
enable: function() {
this.beforeUnloadListener = $.proxy(DirtyForm.prototype.beforeUnloadListener, this);
$(window).on('beforeunload', this.beforeUnloadListener);
console.log("enable called on " + this.name)
},
disable: function(e) {
$(window).off('beforeunload', this.beforeUnloadListener);
console.log("disable called on " + this.name)
},

Ajax - but only when a user has stopped typing

I have a table listing with a 'notes' field in each row. I'd like to be able to update these using ajax and display a little message once they have been updated, but I'm struggling to figure out the correct code.
My plan was to capture a key press, and pass the note ID into a timer, which would be reset every time the user presses a key so it will only run once they've stopped typing for 1 second. The problem is, with multiple notes on the page I need to pass it into an array and reset the timer on each one, if this is even possible?
Here's my code:
var waitTime = 1000;
var click = false;
var timers = new Array();
$('.notes').keyup(function(){
var timerVariable = $(this).attr('id').split("-");
timerVariable = timerVariable[0];
timerVariable = timerVariable.replace('note', '');
timers.push(timerVariable);
timers[timerVariable] = timerVariable;
if(click==false){
var id = $(this).attr('id');
if(click==false){
click= true;
timerVariable = setTimeout(function(){doneTyping(id)}, waitTime);
}
}
});
$('.notes').keydown(function(){
for (var timer in timers) {
clearTimeout(timer);
}
click = false;
});
function doneTyping (id) {
var staffNo = id.split("-");
staffNo = staffNo[0];
staffNo = staffNo.replace('note', '');
var data = 'data='+id+'&note='+$('#'+id).val();
$.ajax({
url: "update-notes.php",
type: "GET",
data: data,
cache: false,
success: function (html) {
jGrowlTheme('mono', 'Updated ' + staffNo, 'Thank you, the note has been updated.', 'tick.png');
}
});
}
I'm wondering if the problem is maybe with the way I'm calling the for loop, or something else? Any advice would be very welcome, thank you!
This is how I do it:
var t;
$(document).ready(function() {
$('#search_string').keyup(function() {
clearTimeout (t);
t = setTimeout('start_ajax()', 3000);
});
});
start_ajax() {
// Do AJAX.
}
It is not a direct answer to your problem but I would personally make a jquery plugin out of your code that you would use like this:
$('.note-fields').myNoteAjaxPlugin({ waitFor: '1000' });
Each "note field" would have it's instance of the plugin encapsulating a timer dedicated for each field. No need to worry about storing in arrays and such.
There are plenty of plugin patterns and boilerplates out there like this one and this other one.
Here is a sample implementation. I've used the one boilerplate and merged it with the jquery ui bridge code (which checks for private methods, re-using a previous plugin instance or instantiating it correctly):
;(function ( $, window, document, undefined ) {
// Create the defaults once
var pluginName = 'myNoteAjaxPlugin',
defaults = {
waitFor: "1000",
};
// The actual plugin constructor
function Plugin( element, options ) {
this.element = element;
this.$element = $(element);
this.options = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this._name = pluginName;
this._timer = null;
this._click = false;
this._init();
}
Plugin.prototype._init = function () {
var self = this;
this.$element.keyup(function(e){
if( self._click === false ){
var id = self.element.id;
if( self._click === false ){
self._click = true;
self._timer = setTimeout(function(){self._doneTyping(id)}, self.options.waitFor);
}
}
});
this.$element.keydown(function(e) {
if (self._timer) {
clearTimeout(self._timer);
}
self._click = false;
});
};
Plugin.prototype._doneTyping = function(id) {
alert('done typing');
};
$.fn[pluginName] = function( options ) {
var isMethodCall = typeof options === "string",
args = Array.prototype.slice.call( arguments, 1 ),
returnValue = this;
// allow multiple hashes to be passed on init
options = !isMethodCall && args.length ?
$.extend.apply( null, [ true, options ].concat(args) ) :
options;
// prevent calls to internal methods
if ( isMethodCall && options.charAt( 0 ) === "_" ) {
return returnValue;
}
if ( isMethodCall ) {
this.each(function() {
var instance = $.data( this, pluginName ),
methodValue = instance && $.isFunction( instance[options] ) ?
instance[ options ].apply( instance, args ) :
instance;
if ( methodValue !== instance && methodValue !== undefined ) {
returnValue = methodValue;
return false;
}
});
} else {
this.each(function() {
var instance = $.data( this, pluginName );
if ( instance ) {
instance.option( options || {} )._init();
} else {
$.data( this, pluginName , new Plugin( this , options) );
}
});
}
return returnValue;
};
})( jQuery, window, document );
$('#myinput').myNoteAjaxPlugin({waitFor: '1500'});
Working DEMO
The problem could very well be with this section of code:
$('.notes').keyup(function(){
var timerVariable = $(this).attr('id').split("-");
timerVariable = timerVariable[0];
timerVariable = timerVariable.replace('note', '');
timers.push(timerVariable);
timers[timerVariable] = timerVariable;
if(click==false){
var id = $(this).attr('id');
if(click==false){
click= true;
timerVariable = setTimeout(function(){doneTyping(id)}, waitTime);
}
}
});
I'm not really sure why you're doing timers.push(timerVariable); followed immediately by timers[timerVariable] = timerVariable; - they both add timerVariable into the array, just in (potentially?) different positions.
Also, while I know Javascript allows it, I still think changing the type of a variable is bad practice. Keep timerVariable as the index for your array, and create a new variable when calling setTimeout, rather than reusing timerVariable. It makes your code easier to follow, and reduces the possibility of errors being introduced.
And, finally, call setTimeout then add to your array. Your code isn't doing what you think it is - you're never actually adding the references created by your setTimeout calls to the array. Take a look at this jsFiddle to see what's actually happening.
Consider a more streamlined version of your code:
$('.notes')
.each(function () {
$(this).data("serverState", {busy: false, date: new Date(), val: $(this).val() });
})
.bind("keyup cut paste", function() {
var note = this, $note = $(this), serverState = $note.data("serverState");
setTimeout(function () {
var val = $note.val();
if (
!serverState.busy
&& new Date() - serverState.date > 1000 && val != serverState.val
) {
$.ajax({
url: "update-notes.php",
type: "POST",
data: { data: note.id, note: val },
cache: false,
success: function (html) {
var staffNo = note.id.split("-")[0].replace('note', '');
serverState.date = new Date();
serverState.val = val;
jGrowlTheme('mono', 'Updated ' + staffNo, 'Thank you, the note has been updated.', 'tick.png');
},
error: function () {
// handle update errors
},
complete: function () {
serverState.busy = false;
}
});
}
}, 1000);
});
Initially, the current state of each <input> is saved as the serverState in the .data() cache.
Every event that can change the state of the input (i.e. keyup, cut, paste) triggers a delayed function call (1000ms).
The function checks whether there already is a request in progress (serverState.busy) and backs off if there is (there is no need to hammer the server with requests).
When it's time to send the changes to the server (1000ms after the last event) and the value actually has changed, it posts the new value to the server.
On Ajax success it sets serverState to the new value, on error it doesn't. Implement error handling for yourself.
So every key press triggers the function, but only 1000ms after the last key press that actually made a change to the value change is pushed to the server.

Categories