Recursion with prototype function - javascript

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;
};

Related

Extend jQuery Plugin with additional function / override function

I need to extend a jQuery Plugin (https://github.com/idiot/unslider) in order to add additional behavior with another public method.
(function(){
// Store a reference to the original remove method.
var originalMethod = $.fn.unslider;
// Define overriding method.
$.fn.unslider = function(){
// Execute the original method.
originalMethod.apply( this, arguments );
console.log( "Override method" );
function test() {
console.log("test called");
}
this.each(function() {
// Operations for each DOM element
console.log("each dom element?");
}).data('unslider', {
// Make test accessible from data instance
test: test
});
return this;
}
})(jQuery);
I already managed to make the public method accessible when calling
var slider = $('#slider');
slider.data('unslider').test();
However, I want to keep the old behavior of unslider anyways, but extend the Plugin with another function. Does anyone have an idea?
I created a fiddle, so you can check whats happening:
My new function gets called, but the old ones are gone:
http://jsfiddle.net/b2os4s7e/1/
If you look at the source of unslider, you can see it stores the Unslider instance inside the data:
// Enable multiple-slider support
return this.each(function(index) {
// Cache a copy of $(this), so it
var me = $(this),
key = 'unslider' + (len > 1 ? '-' + ++index : ''),
instance = (new Unslider).init(me, o);
// Invoke an Unslider instance
me.data(key, instance).data('key', key);
});
In your code you're overwriting this object with your own object. However, the slider expects there to be an Unslider instance. So what you want to do is get this instance and then extend it with your own functions:
var key = $(this).data('key');
var obj = $(this).data(key);
obj.test = function() { console.log('Working!'); };
See http://jsfiddle.net/b2os4s7e/2/
Just define:
$fn.unslider2 = function() { ... }
With any name and behaviour you like.
For extend JQuery should use .fn.extend
(function($){
$.fn.extend({
helloworld: function(message){
return this.each(function(){
$(this).click(function(){
alert(message);
});
});
}
});
})(jQuery)
the object .fn.extend is used for extend funcionality of jQuery
Thanks for your answers! I did it this way:
(function($){
var originalMethod = $.fn.unslider;
$.fn.extend({
unslider: function(o) {
var len = this.length;
var applyMethod = originalMethod.apply( this, arguments );
var key = applyMethod.data('key');
var instance = applyMethod.data(key);
// Cache a copy of $(this), so it
var me = $(this);
if (instance) {
instance.movenext = function (callback) {
return instance.stop().to(instance.i + 1, callback);
};
instance.moveprev = function (callback) {
return instance.stop().to(instance.i - 1, callback);
};
}
return applyMethod.data(key, instance);
}
});
})(jQuery)
The key was to address the data attribute as sroes suggested.
Moreover i needed to apply the original method, since i need the old methods.

Passing variable into object method javascript

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.

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();

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.

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