Ajax - but only when a user has stopped typing - javascript

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.

Related

append method to jquery plugin

i write this plugin and now i want add method to this plugin such as this
$.createMessage().removeMessage()
how can i do it?
my code is
$(function () {
$.extend({
createtext: function (options) {
var setting = {
holder: "",
text: "",
}
if (options != null) {
$.extend(setting, options)
}
var $this = $(setting.holder)
$this.find("div#CreatetextHolder").remove()
$this.append("<div id='CreatetextHolder'><span></span><p class='Createtext'>" + setting.text + "</p></div>")
$this.find("div#CreatetextHolder").fadeIn('slow')
}
})
})
thank you for your help
$(selector).createMessage().removeMessage() would require you to write two plugins - one for 'create' and the other for 'remove'.
It's far better to do everything in one plugin and you can do so by targeting the syntax ...
$(selector).createMessage('remove');
Then it's a matter of testing options in the plugin code, and branching accordingly.
Currently you test if (options != null) assuming options to be a javascript plain object and that the only action is initianisation.
But with my suggestion to allow $.createMessage('remove'), you need to perform more extensive testing/branching depending on what parameter(s) are actually passed.
For example:
$(function () {
$.extend({
createtext: function ( method, options ) {
var settings = {
holder: "",
text: ""
};
var methods = {
'init': function(options) {
var _settings = $.extend({}, settings, options);//this leaves `settings` unaffected and available for reuse in future inits.
//initialize here
},
'remove': function() {
//uninitialize here
}
}
// These tests allow `init' to be passed explicitly,
// or assumed if an options object is the only argument.
// Otherwise, a method such as 'remove' may be passed,
// with or without further parameters.
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || !method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist in jQuery.createtext');
}
}
});
});

Creating a jQuery callback function

I have problem understanding how to create a callback function which I can use to extend the options, as stated in this guide. Here's the excerpt of code that I want to use for callback;
var chart = {};
chart.data = $('.liselected').attr("data");
chart.command = $('.liselected').attr("cmd");
chart.option = "option"; // Category of event request
chart.sessionid = docCookies.getItem("sessionid");
chart.ageType = selectedAgeType;
chart.showData = showUnderlyingData;
var action = function(result, status) {
$('#thumbnails .error').remove();
var chart_list = "";
$.each(result, function(i, val){
chart_list += //Custom HTML Output
});
$('#chart_view').html(chart_list);
};
$.post("jsoncommand", JSON.stringify(chart), action);
So that I can call using $("a").on("click", postcommand(eventrequest)), I tried creating a function like this;
$.fn.postcommand = function(){
var settings = $.extend({
item : {},
data : $('.liselected').attr("data"),
command : $('.liselected').attr("cmd"),
option : "specify query",
sessionid : docCookies.getItem("sessionid"),
ageType : selectedAgeType,
showData : showUnderlyingData,
}, options );
return //How do I make the output of HTML result is customizable?
};
But of course, my attempt is a failure. Spoon feeding is good, but you can always give me a hint and I'll try to explore on my own. Thanks!
It might be a good idea to check out the jQuery plugin section: http://learn.jquery.com/plugins/advanced-plugin-concepts/. You could do something like this:
$.fn.postcommand = function (options) {
// define some default values for your plugin
var default = {
callback: function () {}
}
// merge default settings, with the ones given
var settings = $.extend( {}, defaults, options );
return this.each(function() {
var $this = $(this);
$this.on('click', function(event) {
settings.callback();
event.preventDefault();
});
}
});
And then use your plugin on some links:
$('a.useCallback').postcommand();

Multiple Timers with setTimeInterval

I am facing a problem with setInterval being used in a loop.
I have a function subscribeFeed( ) which takes an array of urls as input.
It loops through the url array and subscribes each url to getFeedAutomatically() using a setInterval function.
so if three URL's are there in the array, then 3 setInterval's will be called.
The problem is
1)how to distinguish which setInterval is called for which URL.
2)it is causing Runtime exception in setInterval( i guess because of closure problem in javascript)
//constructor
function myfeed(){
this.feedArray = [];
}
myfeed.prototype.constructor= myfeed;
myfeed.prototype.subscribeFeed =function(feedUrl){
var i=0;
var url;
var count = 0;
var _this = this;
var feedInfo = {
url : [],
status : ""
};
var urlinfo = [];
feedUrl = (feedUrl instanceof Array) ? feedUrl : [feedUrl];
//notifyInterval = (notifyInterval instanceof Array) ? notifyInterval: [notifyInterval];
for (i = 0; i < feedUrl.length; i++) {
urlinfo[i] = {
url:'',
notifyInterval:5000,// Default Notify/Refresh interval for the feed
isenable:true, // true allows the feed to be fetched from the URL
timerID: null, //default ID is null
called : false,
position : 0,
getFeedAutomatically : function(url){
_this.getFeedUpdate(url);
},
};
urlinfo[i].url = feedUrl[i].URL;
//overide the default notify interval
if(feedUrl[i].NotifyInterval /*&& (feedUrl[i] !=undefined)*/){
urlinfo[i].notifyInterval = feedUrl[i].NotifyInterval;
}
// Trigger the Feed registered event with the info about URL and status
feedInfo.url[i] = feedUrl[i].URL;
//Set the interval to get the feed.
urlinfo[i].timerID = setInterval(function(){
urlinfo[i].getFeedAutomatically(urlinfo[i].url);
}, urlinfo[i].notifyInterval);
this.feedArray.push(urlinfo[i]);
}
}
// The getFeedUpate function will make an Ajax request and coninue
myfeed.prototype.getFeedUpdate = function( ){
}
I am posting the same on jsfiddle
http://jsfiddle.net/visibleinvisibly/S37Rj/
Thanking you in advance
After some prototyping i found a answer ,which has the answer,move the closure outside
function myclass(){
}
myclass.prototype.funone= function(){
var counter =0;
var timerID;
timerID = setInterval( function(){
alert(counter++);
},1000);
}
myclass.prototype.funtwo= function(){
var timerID2;
var counter2 =50;
timerID2 = setInterval( function(){
alert(counter2++);
},2000);
}
myclass.prototype.funthree = function( ){
var urlArray =["google.com","yahoo.com"];
var timeArray =[15000,6000];
var timerID ;
for(var i=0;i<2; i++){
var url = urlArray[i];
var timerinterval = timeArray[i];
timerID = this.register( url,timerinterval);
}
}
myclass.prototype.register = function(url,timerInterval){
var myUrl =url;
var myTimer = timerInterval;
var timerID = setInterval( function(){
alert(myUrl+"with"+ myTimer);
},myTimer);
}
var m = new myclass( );
m.funthree( );
http://jsfiddle.net/visibleinvisibly/Q4SBG/13/
The move the index binding from the setInterval and pass the url and time interval.
It works perfectly
You might want to have a look at this answer (under "The this variable" at the bottom) about what the this value means.
The error in your code may have something to do with using a counter in a loop and creating closures depending on the counter. The simplest way to create such closures is.
for(i=0;i<len;i++){
object.myCallback = (function(counter){
return function(){
doSomethingWith(counter);
}
}(i));
}
When creating closures on the fly like that you should be careful not dragging large or large amounts of variables into the closure scope. The link above and code below shows how to do this safely.
I've changed some of the code to make it simpler and not copy stuff that doesn't need to be copied, the setInterval is setTimeout so it only does it once but it's the same idea.
//constructor
function MyFeed(){
this.feedArray = [];
}
MyFeed.prototype.subscribeFeed =function(feedUrl){
var i=0,urlInfo=[];
feedUrl = (feedUrl instanceof Array) ? feedUrl : [feedUrl];
for (i = 0; i < feedUrl.length; i++) {
feedUrl[i].isEnable=true;
feedUrl[i].called=false;
feedUrl[i].position=0;//not sure what this is supposed to do
//Set the interval to get the feed.
feedUrl[i].timerID = setTimeout(this.closures//changed this to timeout
.getFeedUpdate(this)
,feedUrl[i].notifyInterval||100//changed default value
);
this.feedArray.push(feedUrl[i]);
}
};
// The getFeedUpate function will make an Ajax request and coninue
MyFeed.prototype.getFeedUpdate = function( index ){
console.log("in getFeedUpdate, this is now:",this);
console.log("my feed url object:",this.feedArray[index].url);
};
//limit closure scope, define closure creators here
MyFeed.prototype.closures={
//this.closures.getFeedUpdate(this)
// will return a closure that calls this.getFeedUpdate
// with correct parameters
getFeedUpdate:function(me){
var index = me.feedArray.length;
return function(){
me.getFeedUpdate(index);
};
}
};
//code to test adding single feed
var mf = new MyFeed();
mf.subscribeFeed({
url:"I am last",
notifyInterval:1000
});
//add another single feed
mf.subscribeFeed({
url:"first.com"
});
//and another
mf.subscribeFeed({
url:"second.com"
});
//and add some more feeds in an array of feeds
mf.subscribeFeed([
{
url:"third"
},
{
url:"fifth"
},
{
url:"no, I am last",
notifyInterval:1500
}
]);
Try FireFox with the FireBug plugin or Chrome and press F12 to see the console, when the log statements log something you can click on it to see the details of the logged item. Very helpful to log objects like this or simple values like index

Recursion with prototype function

I would like to create a new jQuery function, in which I could pass one or many value in the option. Then, For all matched elements of the DOM, iterate on all the different options, but one option at a time. So, for all the elements not treated, replay the prototype function. All the treatment should be in the same function, so I thought at recursion, but since I am not really experience with prototyping in javascript, I am not sure how to do that.
For now, There is no error, but nothing happen even.
Is that correct ?
Thanks for your enlightenment.
(function($) {
$.fn.foo = function (prop) {
var options = $.extend({
"elt" : "", // Declaration of my options
"callback" : function(){}
}, prop)
var optionsTab = options.elt.split('#');
var current = optionsTab.slice(0,1),
var remaining = optionsTab.slice(1);
var result = this.each(function() {
var el = $(this);
// Code handling the first element in the option
});
if (remaining.length > 0) {
$({ "elt": remaining.join("#") }); // Trial of recursion of foo
return result;
}
}
I think you're trying to call recursively in a timeout am I correct?
var result = this.each(function() {
var el = $(this);
// Code handling the first element in the option
});
if (remaining.length > 0) {
$({ "elt": remaining.join("#") }); // Trial of recursion of foo
var me = this;
setTimeout(function(){
me.foo(parameters);//not sure what the parameters should be
},500);
}
return this;
Some helpful info: https://stackoverflow.com/a/16063711/1641941 (under "The this variable")
Created this fiddle and it works:
$.fn.foo = function (prop) {
var options = $.extend({
"elt" : "", // Declaration of my options
"callback" : function(){}
}, prop);
var optionsTab = options.elt.split('#');
var current = optionsTab.slice(0,1);
var remaining = optionsTab.slice(1);
this.each(function() {
var el = $(this);
// Code handling the first element in the option
});
if (remaining.length > 0) {
this.foo({ "elt": remaining.join("#") }); // Trial of recursion of foo
}
return this;
};

jquery plugin method ands callbacks

I have a basic plugin that populates an array within the plugin. How can I get that array via a method call with parameters. This is my first plugin so please go easy on me if this is a dumb question.
basic Plugin
(function($) {
$.fn.myPlugin = function() {
return this.each(function(){
tagArray = []; // my array that is populated
//code that does stuff to populate array
});
}
})(jQuery);
I would like to get the tagArray like so...
var arr = $('.className').myPlugin("getArray");
Where I can then use that array elsewhere. How can I accomplish this?
Thank you for any help.
I don't see why you would need the "getArray" parameter. In any case you need to define only 1 array and make your function return it:
(function($) {
$.fn.myPlugin = function() {
var tagArray = [];
this.each(function(){
// add something to tagArray
});
return tagArray;
}
})(jQuery);
That's a rather strange requirement, but an easy way to do that if there is only parameter would be something like :
(function($) {
$.fn.myPlugin = function(param) {
var tagArray = [],
elems = this.each(function(){
tagArray.push( $(this).text() ); // whatever you do ??
});
return param == 'getArray' ? tagArray : elems;
} // ^^ if the parameter is passed, return the array, otherwise the elems
})(jQuery);
FIDDLE
It's a bit hackish, but it works. You could also just return this.map(function() {... to always return an array etc, or read up on how to pass multiple parameters to a plugin and do different things etc. instead of the hardcoded check for 'getArray' used above.
Try
(function($) {
function Plugin($el, opts){
this.tagArray = [];
this.tagArray.push($el.attr('id')) //for testing the retuned instance
$el.data('myPlugin', this);
}
Plugin.prototype.getTagArray = function(){
return this.tagArray;
}
$.fn.myPlugin = function(opts) {
if($.type(opts) == 'string'){
var plugin = this.data('myPlugin');
return plugin[opts]();
}
return this.each(function(){
var $this = $(this);
new Plugin($this);
});
}
})(jQuery);
jQuery(function(){
$('#e1, #e2, #e3').myPlugin();
console.log($('#e1').myPlugin('getTagArray'))
console.log($('#e2').myPlugin('getTagArray'))
console.log($('#e3, #e1').myPlugin('getTagArray'))
})
Demo: Fiddle
I just finished writing a JQuery Plugin myself and here is the basic structure I settled on:
(function (window, document, $, undefined) {
//Local Methods
var methods = {
init : function(options){
//stuff you want to do when your plugin initializes i.e. when you do $('selector').myPlugin(options)
},
getArray: function(){
//your getArray method. Put your get array logic here
}
}
//Plugin Initialize
$.fn.myPlugin = function(args){
if ( methods[args] )
{
//execute JQuery Plugin Method
return methods[ args ].apply( this, Array.prototype.slice.call( arguments, 1 ));
}
else if ( typeof args === 'object' || ! args )
{
//Process JQuery Plugin Options
var opts = $.extend({}, $.fn.myPlugin.defaults, args);
var new_args = new Array(opts);
return methods.init.apply( this, new_args );
}
else
{
$.error( 'Method ' + args + ' does not exist on myPlugin' );
}
};
//Define Default Options
$.fn.myPlugin.defaults = {
option_1: '',
option_2: '',
option_n: ''
}
//API Methods
var M = $.myPlugin = function(){};
$.extend(M, {
getArray: function(){
return methods.getArray();
}
});
}(window, document, jQuery));
Doing this allows you to start your plugin like this (as usual):
$('.className').myPlugin(options);
and/or call your getArray function like this:
$.myPlugin.getArray();
I hope this helps you get closer to where you want to be.

Categories