how to expand jquery.plugin over multiple files - javascript

my current plugin is getting really big (just over 8000 lines right now) and i would like to know if there is a way to sort it into different files.
Just liike the require function in node.js, but for jquery, so it would be sorted into more files and thus, be more clearly arranged.

As #jcubic mentioned you need to have your code separated into individual modules / functionality purposes.
Store all of your methods in a method object of some sort (this could also be within the plugins namespace of course). This can also easily be added to, or even extended from a different file.
var methods = (function ($) {
return {
init : function () { initMethod(); },
another : function () { anotherMethod(); },
thirdThing : function () { thirdThing(); },
etcEtc : function () { etcEtc(); }
};
}(jQuery));
I highly recommend the method calling way of creating jQuery plugins, which would utilize this object.
$.fn.pluginName = function (method) {
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 on jQuery.tooltip');
}
};
Now everything is separated, and you call your modules by doing $('whatever').pluginName('methodhere'); etc

Related

Create an instance of a jQuery plugin

I have several pages which I wish to allow the the user to inline edit many fields and update the server DB. To implement this, my intent is to create a jQuery plugin which I can do the typical passing of the configuration options and uses ajax to save the results.
(function($){
var methods = {
init : function (options) {return this.each(function () {/* ... */});},
method1 : function () {return this.each(function () {/* ... */});},
method2 : function () {return this.each(function () {/* ... */});}
};
$.fn.myEditPlugin= function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); //Line 10
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments); //Line 12
} else {
$.error('Method ' + method + ' does not exist on jQuery.myEditPlugin');
}
};
}(jQuery)
);
For each individual page, there are several options which are common to all (i.e. the url endpoint, the record's primary key, etc) and I would rather not duplicate each when applying the plugin.
Originally, I was just going to define a function on each page which takes some input and applies the common options to each.
function wrapEdit(e,options) {
options.url='/page1/etc';
options.pk=document.getElementById('pk').value;
return $(e).myEditPlugin(options);
}
wrapEdit('.someclass',{foo:123});
It doesn't seem all that professional to me, so in my obsessive quest, thought I would make a class which I could pass the common options to and it would apply the plugin.
class WrapEdit(options)
{
constructor(options) {
this.options = options;
}
this.applyIndividualOptions=function(e, options) {
return $(e).myEditPlugin(Object.assign({}, this->options, options));
}
}
var wrapEdit=new WrapEdit({url: '/page1/etc', pk: document.getElementById('pk').value});
wrapEdit.applyIndividualOptions('.someclass',{foo:123});
Better, but not very jQueryish as I will be passing the select element instead of directly applying the plugin to elements typical of jQuery.
Is it possible to create an instance of a jQuery plugin which keeps previously defined data? Maybe something like the following:
$.myEditPlugin({url: '/page1/etc', pk: document.getElementById('pk').value});
$('.someclass').myEditPlugin({foo:123}); //Will also pass previously defined url and pk to myEditPlugin
Or maybe best to create a custom jQuery plugin per page which just adds the extra options and initiates the real plugin...
$.fn.myEditPluginInstance = function(options) {
return this.myEditPlugin(Object.assign({url: '/page1/etc', pk: document.getElementById('pk').value}, options));
};
Creating a function to be called against a jquery collection
The basic idea is to define a new property (function) in jQuery.fn, before any call to your plugin is made (In other words, any code related to the application is executed). You can use an "Immediately Invoked Function Expressions" (a.k.a. IIFEs) to fence your plugin API in. Then you have to loop over the collection and execute any code your plugin needs to apply on the collection items.
Basic skeleton:
(function ($) {
// Enclosed scope (IIFE)
// You can define private API/variables in here
// …
// Once your plugin API is ready, you have to apply the magic to each item
// in the collection in some ways. You must add a property to jQuery.fn object.
$.fn.myAwesomePlugin = function(Opt) {
var defaultConfig = {option1: 'someValue' /*, …*/};
// Eval supplied Opt object (Validate, reject, etc.)
// If all goes well, eventually merge the object with defaults.
$.extend(defaultConfig, Opt);
// Apply the magic against each item in the jQuery collection
// (Your plugin may not need to use "each" function though)
// Return the jQuery collection anyway to keep chaining possible.
// Once again, this is not required, your plugin may return something else depending on the options passed earlier for instance.
return this.each(function(el, idx) {
// Your plugin magic applied to collection items…
});
}
})(jQuery);
You should be able to call your plugin $('someSelector').myAwesomePlugin(); right after the declaration.
Simple implementation example:
(function ($) {
let required = {url: null, pk: null}
// Function to be executed upon first call to the plugin
, populateCommons = () => {
let ep = $('#someNode').data('endpoint')
, pk = document.querySelector('#pk')
;
// Basic tests to alert in case the page
// doesn't comply with the plugin requirements
if( typeof ep !== 'string' || !/^\/[a-z]+/.test(ep) || !pk) {
throw ` "myEditPlugin" init phase error:
Detected endpoint: '${ep}'
Is PK value found: ${!!pk}
`;
}
[required.url, required.pk] = [ep, +pk.value];
};
$.fn.myEditPlugin = function(Opt) {
let allOpts;
// First call will trigger the retrival of common data
// that should be available as static data somewhere every page source.
!required.url && populateCommons();
allOpts = $.extend({}, Opt, required);
return this.each(function(el, idx) {
// Your logic here, request
console.log("Payload is", allOpts);
});
}
})(jQuery);
function debounce(fn, time) {
debounce.timer && (clearTimeout(debounce.timer));
debounce.timer = setTimeout(() => (fn(), debounce.timer = null), time);
}
$('[type="text"]').keydown(function(e){
debounce(() => this.value && $(this).myEditPlugin({foo:this.value, bar: 'Contextual value'}), 2000);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="pk" type="hidden" value="5">
<div id="someNode" data-endpoint="/api/endpoint">
Editing the below input will trigger the plug-in code
</div>
<input type="text" title="Edit me"/>
Related documentation here

Reason for passing function to self executing function

I got js code from a design company, but I do not understand the reason for passing a function to a self executing function.
Here is the outline of the code.
(function(core) {
if (typeof define === "function" && define.amd) {
define("abc", function() {
var abc;
abc = window.Abc || core(window, window.jQuery, window.document);
abc.load = function(res, req, onload, config) {
var base, i, load, resource, resources;
resources = res.split(",");
load = [];
base = (config.config && config.config.abc && config.config.abc.base ? config.config.abc.base : "").replace(/\/+$/g, "");
if (!base) {
throw new Error("Please define base path to Abc in the requirejs config.");
}
i = 0;
while (i < resources.length) {
resource = resources[i].replace(/\./g, "/");
load.push(base + "/components/" + resource);
i += 1;
}
req(load, function() {
onload(abc);
});
};
return abc;
});
}
if (!window.jQuery) {
throw new Error("Abc requires jQuery");
}
if (window && window.jQuery) {
core(window, window.jQuery, window.document);
}
})(function(global, $, doc) {
var _c = {};
...
return _c;
});
Is there benefit of writing code such way over something like below?
(function( core, $, undefined) {
...
} (window.core= window.core|| {}, jQuery )};
Is this some advanced technique?
Basically, ....kinda.
In Javascript, functions are treated as first-class objects. This means you can pass them around in variables and whatnot. The first part, (function(core) { ... }), creates an anonymous function, taking a single argument called core. The parentheses around the function basically just resolve to a function. The second part, (function(global, $, doc) { ... }), is creating another function, which is passed immediately into a call to the first function as the value of core.
Put this way, here's what's happening.
// Define the first function (the one that takes core)
var firstFunc = function (core) { /* ... */ };
// Define the second function (the one that takes global, $, and doc)
var secondFunc = function (global, $, doc) {
var _c = {};
/* ... */
return _c;
};
// Call the first, passing in the second.
firstFunc(secondFunc);
The above code and the code you posted accomplish the same thing. One purpose for writing something like this would be to sandbox the second function so that the first can specify its own local versions of variables global, $, and doc, which avoids conflicts with, say, active-running versions of jQuery (which typically declares its own globally-scoped $ variable).
EDIT: Now that the code in the first function has been filled in, we can say for sure that the reason for doing this is to resolve dependencies and ensure they are present before manually passing them into the second function. From the look of the code provided, it appears that this is enforcing the presence of abc (which I'm assuming is some dependency) via require.js, as well as ensuring that jQuery is present. In addition, it looks like the values in _c returned from the function are used as a part of that dependency enforcement process, though I can't tell exactly how by looking at it.

Durandal: one compositionComplete to rule them all

With Durandal we have the compositionComplete event for last minutes processing on the view.
Currently my view composition hierarchy is quite complex, and I need to do various UI processing at different point in the app.
To avoid calling multiple times the same UI related code, and to guarantee it will be called at least once when some view needs it, I need a finalCompositionComplete() hook somewhere.
I didn't find such an event in the current Durandal implementation so I was thinking of adding it to the composition.js file (I figured out the endComposition() function would be a good place to start...)
It's obviously a bad idea modifying original durandal files for maintenance reasons.
Is there a better solution? One that is more maintenance friendly..
What I've done so far (and seems to be working):
created a lifecycle plugin in the plugins directory of durandal (and injected it in the composition module)
modified the composition.js file's endComposition() function
Now I can register callbacks through my lifecycle plugin and it will be called once at the very end
The endComposition() function in the composition.js file:
function endComposition() {
compositionCount--;
if (compositionCount === 0) {
setTimeout(function(){
var i = compositionCompleteCallbacks.length;
while(i--) {
try{
compositionCompleteCallbacks[i]();
}catch(e){
system.error(e);
}
}
// my modification is right here:
lifecycle.finalCompositionComplete();
compositionCompleteCallbacks = [];
}, 1);
}
}
lifecycle plugin
define(['durandal/system', 'underscore'], function (system, _) {
var _nameCounter = 0;
var _processes = {};
return {
registerFinalProcess: register,
finalCompositionComplete: processFinal
};
function register(processName, callback) {
processName = processName || '_noname' + _nameCounter++;
_processes[processName] = callback;
}
function processFinal() {
_.each(_.pairs(_processes), function(pair) {
try {
if (typeof pair[1] === 'function') {
pair[1]();
}
delete _processes[pair[0]];
} catch(e) {
system.log('error:could not call ' + pair[0]);
}
});
_processes = {};
_nameCounter = 0;
}
});
Anywhere, anytime in my app:
lifecycle.registerFinalProcess('someCallbackId', someFunction);

Jquery plugin using methods needs to call itself

I'm following the tutorial here: http://docs.jquery.com/Plugins/Authoring
I just wanted to create a simple plugin for a project we are working on that would add +/- signs next to an input box that could be used to increment the value of the box.
So I'm reading the tutorial and doing the section that talks about having multiple methods and everything is going fine, except for one small hitch.
So here is the code:
(function( $ ) {
var methods = {
init: function(options) {
if (options) {
$.extend(settings, options);
}
this.css('float', 'left');
this.after('<div class="increment-buttonset"><div class="increment-button" style="float: left;">+</div><div class="decrement-button">-</div></div><br style="clear: both" />');
$('.increment-button').click(function() {
$.increment('change');
});
return this;
},
change: function() {
// Increment Decrement code would go here obviously
return this;
}
};
$.fn.increment = function(method) {
var settings = {
'step': 1
};
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);
}
};
})( jQuery );
The issue is the $.increment('change');
I want to bind the +/- buttons on click to call the increment('change'). I get errors when I do that.
Uncaught TypeError: Object function (a,b){return new c.fn.init(a,b)} has no method 'increment'
I've tried it without the $. but that just tells me increment isn't defined yet. Am I messing some syntax up here or just going about this completely wrong?
The solution is pretty simple, you were calling the method the wrong way. It should be
$('.increment-button').click(function() {
$(this).increment('change');
});
That is, because function added to $.fn.functionName can only be called on a jQuery elements like $(selector).functionName.

Can the Javascript Module Pattern be used for singletons and also for objects that are instantiated mutliple times?

I have one page with two types of forms. I have a single form of type A at the top, and then I have 1 or more forms of type B below it.
I use the Module pattern + jQuery to wire up all the events on my forms, handle validation, ajax calls, etc.
Is this the preferred/valid way to define a singleton, as in Form A, and a reusable object class, as in Form B? They are very similar and I'm not sure if I need to be using object the prototype property, new, or a different pattern. Everything seems to work for me but I'm afraid I'm missing some key error.
Form A javascript looks like this:
var MyProject.FormA = (function() {
var $_userEntry;
var $_domElementId;
var validate = function() {
if($_userEntry == 0) {
alert('Cannot enter 0!');
}
}
var processUserInput = function() {
$_userEntry = jQuery('inputfield', $_domElementId).val();
validate();
}
return {
initialize: function(domElementId) {
$_domElementId = domElementId;
jQuery($_domElementId).click(function() {
processUserInput();
}
}
}
})();
jQuery(document).ready(function() {
MyProject.FormA.initialize('#form-a');
});
Form B, which is initialized one or many times, is defined like so:
var MyProject.FormB = function() {
var $_userEntry;
var $_domElement;
var validate = function() {
if($_userEntry == 0) {
alert('Cannot enter 0!');
}
}
var processUserInput = function() {
$_userEntry = jQuery('inputfield', $_domElement).val();
validate();
}
return {
initialize: function(domElement) {
$_domElement = domElement;
jQuery($_domElement).click(function() {
processUserInput();
}
}
}
};
jQuery(document).ready(function() {
jQuery(".form-b").each(function() {
MyProject.FormB().initialize(this);
});
});
Both of your modules explicitly return objects which precludes the use of new.
Prototype inheritance isn't really compatible with the method hiding your achieving with this pattern. Sure you could re-write this with a prototype form object with your validate method defined on it, but then this method would be visible and you'd loose the encapsulation.
It's up to you whether you want the low memory footprint and speedy object initialization of prototypes (Shared methods exist only once, Instantiation runs in constant time) or the encapsulation of the module pattern which comes with a slight performance penalty (Multiple defined identical methods, Object instantiation slowed as every method has to be created every time)
In this case I would suggest that the performance difference is insignificant, so pick whatever you like. Personally I would say there is too much duplication between them and I would be inclined to unify them. Do you really need A to be a singleton? What are the dangers of it being accidentally instantiated twice? Seems like this is maybe over-engineering for this problem. If you really must have a singleton I'd wrap the non-singleton (B) class like this:
var getSingleton = function() {
var form = MyProject.FormB();
form.initialize("#form-a");
console.log("This is the original function");
getSingleton = function() {
console.log("this is the replacement function");
return form;
}
return form;
}
I think you just need to write a kind of jQ plugin:
(function($) {
$.fn.formValidator = function() {
return $(this).each(function() {
var $_domElement = $(this);
$_domElement.click(function() {
if($('inputfield', $_domElement).val() == 0) {
alert('Cannot enter 0!');
}
});
});
};
})(jQuery);
In this case you'll extend jQ element methods module and will be able to use it for any amount of elements at the page (for single or multiple elements collection). Also it will be chainable.
Usage:
$('#form-a').formValidator();
$('.form-b').formValidator();
Or
$('#form-a, .form-b').formValidator();
Ofcourse you can use a module to store this function:
ProjectModule.formValidator = function(selector) {
return $(selector).each(function() {
var $_domElement = $(this);
$_domElement.click(function() {
if ($('inputfield', $_domElement).val() == 0) {
alert('Cannot enter 0!');
}
});
});
};
Usage:
ProjectModule.formValidator('#form-a, .form-b');

Categories