
I wrote a lot of plugins on jQuery. If you look at the code for all plugins, sorting them by date of publication on github, you can follow the evolution of the code. None of these plug-ins complied with all the recommendations, which will be described below. All that will be described, only my personal experience gained from project to project.
Writing extensions to jQuery is pretty simple, but if you want to learn how to write them so that they are then just maintain and extend, welcome to the cat.
Next comes a set of recommendations. It is assumed that the reader is familiar with the development of plug-ins on jQuery. But I still give an example jQuery plugin, the structure of which is usually recommended in the relevant articles.
(function ($) { $.fn.tooltips = function (opt) { var options = $.extend(true, { position: 'top' }, opt); return this.each(function () { $(this).after('<div class="xd_tooltips ' + options.position + '">' + $(this).data('title') + '</div>'); }); }; }(jQuery));
This is all the functionality of our plugin. It adds a new element after each selection element,
div.xd_tooltips . That's all he does.
All operations must be done in a separate class.
Having thought of a serious plugin that will grow, you do not need to put all the logic in an anonymous function in the each method, as in the example. Create a separate function constructor (class). Then for each element of the sample, create a separate instance of this class, and save a link to it in the data of the original element. When you call the plugin again, check the
data . This solves the problem of reinitialization.
')
(function ($) { var Tooltips = function (elm) { $(elm).after('<div class="xd_tooltips">' + $(elm).data('title') + '</div>'); }; $.fn.tooltips = function (opt) { var options = $.extend(true, { position: 'top' }, opt); return this.each(function () { if (!$(this).data('tooltips')) { $(this).data('tooltips', new Tooltips(this, options)); } }); }; }(jQuery));
All common operations hide, show, destroy do class methods
When a plugin is large and you want to use part of its functionality in another plugin / code, it will be very convenient if the class has open methods available. Methods can be done through prototypes, but there is a minus here - you cannot use private variables. The best option would be to use local functions, which will then return to the hash.
Like that:
Why is the third option the best?
Firstly, now the methods inside the class can be used without the
this prefix.
this - in inept hands is evil. In JS, it can be anything, and I highly recommend using it as little as possible.
Second, when the plugin is large, you decide to skip it through
uglify . Then in the last version, all references to init (except the
self.init hash
key ) will be replaced with a single-letter variable, which, if you use the method frequently, will decently reduce the code.
Third, private methods and variables are available to you, which you declare in the top
var . It is very convenient. Something like
private methods in "adult" PL.
From the last rule you need to squeeze one more:
All object initialization should also be kept in a separate init method.
Do not do as much as possible when creating an instance of a class. This is just an experience. Your plugin will grow up sooner or later, and you will want to use its code from another place, not necessarily from jQuery. For example, your code will have a cool parser, and you will want to use it outside of any DOM elements.
Then:
var tooltips = new Tooltips(); tooltips.parse(somedata);
It will not create anything in the DOM tree, but will do exactly what is needed.
Make a separate instance of settings for each class instance.
In the first example, we made one global
options variable. And jQuery could get several objects into the scope. Feel what it threatens? JS works that way, if one of the elements then changes these options, they will change for all elements. This is not always true. Therefore,
options are served as the second element in the constructor of our class.
From the foregoing, the plugin will look like this:
(function ($) { var Tooltips = function (elm, options) { var self, init = function () { $(elm).after('<div class="xd_tooltips ' + options.position + '">' + $(elm).data('title') + '</div>'); }; self = { init: init }; return self; }; $.fn.tooltips = function (opt) { return this.each(function () { var tooltip; if (!$(this).data('tooltips')) { tooltip = new Tooltips(this, $.extend(true, { position: 'top' }, opt)); tooltip.init(); $(this).data('tooltips', tooltip); } }); }; }(jQuery));
Default settings should be visible globally.
In the example above, we combine the opt settings and the default settings in the form of the hash {}. This is not true - there will always be a user to whom such settings will not work, and it will be unprofitable to call the plugin with its settings every time. Of course, he can get into the plug-in code and correct the settings on his own, but then he will lose the update of the plug-in. Therefore, the default settings should be visible globally and could also be changed globally.
For our plugin, you can use it like this:
$.fn.tooltips = function (opt) { return this.each(function () { var tooltip; if (!$(this).data('tooltips')) { tooltip = new Tooltips(this, $.fn.tooltips.defaultOptions, opt); tooltip.init(); $(this).data('tooltips', tooltip); } }); }; $.fn.tooltips.defaultOptions = { position: 'top' };
Then any developer will be able to declare in his script:
$.fn.tooltips.defaultOptions.position = 'left';
And do not do it with each initialization of the plugin.
Do not always return this
In all the examples above, the initialization code came down to what we immediately returned
this.each . In fact, almost all the methods (plugins) jQuery return
this .
Therefore, such things as:
$('.tooltips') .css('position', 'absolute') .show() .append('<div>1</div>');
It is very convenient. But for example the
val method, if there are no parameters, returns the value of the element. So it is with us, it is necessary to provide a way in which our plugin could return some values.
$.fn.tooltips = function (opt, opt2) { var result = this; this.each(function () { var tooltip; if (!$(this).data('tooltips')) { tooltip = new Tooltips(this, $.fn.tooltips.defaultOptions, opt); tooltip.init(); $(this).data('tooltips', tooltip); } else { tooltip = $(this).data('tooltips'); } if ($.type(opt) === 'string' && tooltip[opt] !== undefined && $.isFunction(tooltip[opt])) { result = tooltip[opt](opt2); } }); return result; };
Please note we have added the second parameter
opt2 . We will need it just for the cases of calling some methods.
For example, for input / output plugins, it is important to change the original
value .
$('input[type=date]').datetimepicker(); $('input[type=date]').datetimepicker('setValue', '12.02.2016'); console.log($('input[type=date]').datetimepicker('getValue'));
Use 'use strict'
No seriously, you still do not use? JS forgives too much, and a huge amount of mistakes grows from here. Using this directive will save you at least from global variables.
(function ($) { 'use strict';
Someone will say that you can declare
'use strict' at the very beginning of the file, but I do not recommend it. When the project grows, and it will be used by other plugins. And the creators of those plugins did not use the 'use script'. When
grunt / gulp / npm collects all the packages in one
build file, then you will have to wait for an unpleasant surprise.
I highly recommend
JSLint for code validation. For many years I have been using
notepad ++ for development, and there is no error highlighting in it. Maybe this is not relevant for the IDE, but JSLint allows me to write better code.
Use your internal reset CSS
At the beginning of your CSS file, reset all CSS for the entire plugin. Those. need some kind of main class, under which will be all the others. When using Less, this will add additional readability:
.jodit { font-family: Helvetica, Arial, Verdana, Tahoma, sans-serif; &, & *{ box-sizing: border-box; padding:0; margin: 0; } }
Use prefixes in CSS class names
CSS in browsers is a global thing. And it is impossible to predict in what environment your plugin will be. Therefore, all, even insignificant classes, write with a prefix. I very often saw the auxiliary classes,
disable, active, hover , completely break the appearance of the plugin.
Use modern ways to build a project.
In the comments correctly suggested that no modern JS project no longer lives without updates, automatic builders, and dependency tracking.
In the code, we naively believed that jQuery was already connected to the project. And what if it is not. Everything will break.
;(function (factory) { if (typeof define === 'function' && define.amd) {
Such a default script in the browser will work just as before, but when using Browserify and similar systems, it will automatically pull up all the necessary dependencies. And there may be not only jQuery. Any other library.
Register your project in bower and npm
Serious guys will not use your plugin if it will not be able to upgrade. Download the plugin archive from github this is already yesterday. Now, just register at
npmjs.comThen in the root of your project execute
npm init
And follow the instructions. Package.json will appear at the root of the project.
Followed by the team
npm publish ./
From the same place, will publish the plugin in npm.
And end users will be able to install it themselves like this:
npm install tooltips
You will need to run
npm publish ./ with each new version. This is not very convenient, but before that you still need to type a bunch of "git" commands.
For myself, I automated this process through
package.json . Added in the
scripts field, such commands:
"version": "1.0.0", "scripts": { "github": "git add --all && git commit -m \"New version %npm_package_version%\" && git tag %npm_package_version% && git push --tags origin HEAD:master && npm publish", },
And then just run:
npm run github
It adds all the files to
git , makes a
commit , creates a tag for the current version, uploads it all to
github , and then also updates the version to
npmjs .
It takes the version from
package.json , and you need to update it before each call. But you can automate this, just add to the beginning, before
git add :
npm version patch
I work on windows, so
package.json variables are used like this -
% npm_package_version% , in other OSs you need to use
$ npm_package_version .
For bower, the situation is almost the same. Only nowhere to register is not necessary.
If you have not yet installed bower.
npm install -g bower
Then
bower init
At the root of the project.
When bower.json is created, publish everything on github, or another hosting.
After that you can register the package:
bower register jodit https://github.com/xdan/jodit.git
That's all for now, write your advice in the comments. Thanks for attention!