Good Tone Rules for Writing a JQuery Plugin
I wrote a ton of jQuery plugins. If you look at the code of all plugins, sorting them by date of publication on github, you can trace the evolution of the code. None of these plugins comply with all the recommendations that will be described below. All that will be described is only my personal experience gained from project to project.
Writing extensions in jQuery is quite simple, but if you want to learn how to write them so that it is easy to maintain and extend them later, welcome to cat.
Next will be a set of recommendations. The reader is expected to be familiar with jQuery plugin development. But I will still give an example of a jQuery plugin, the structure of which is usually recommended in the relevant articles.
This is all the functionality of our plugin. It adds after each selection item, div.xd_tooltips a new item. That is all he does.
Having conceived a serious plugin that will grow, you do not need to put all the logic into an anonymous function in the each method, as in the example. Create a separate constructor function (class). Then, for each element from the selection, create a separate instance of this class, and save the link to it in the data of the original element. When you call the plugin again, we check data . This solves the problem of reinitializing.
When the 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 public methods available. Methods can be done through prototypes, but there is a minus - you cannot use private variables. A better option would be to use local functions, which will then return in the hash.
More or less like this:
Why is the third option the best?
Firstly, now there are methods inside the class that can be used without this prefix . this - in evil hands it is evil. In JS, it can be anything, and I highly recommend using it as little as possible.
Secondly, when the plugin is big, you decide to pass it through uglify . Then in the latter case, all references to init (except for the hash key self.init ) will be replaced by a one-letter variable, which, when the method is used frequently, decently reduces the code.
Third, private methods and variables are available to you, which you declare in the top var . It is very comfortable. Something like private methods in "adult" YaP.
From the last rule you need to squeeze one more:
Do not do anything possible when creating an instance of the class. This is just an experience. Your plugin will grow sooner or later, and you will want to use its code from another place, not necessarily from jQuery. For example, you will have a cool parser in your code, and you will want to use it outside of any DOM elements.
Then:
It will not create anything in the DOM tree, but will do exactly what it needs.
In the first example, we made one global options variable. And in scope jQuery some objects could get. Feel what it threatens? JS works so that if one of the elements then changes these options, then 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:
In the example above, we combine the opt settings and the default settings in the form of a hash {}. This is not true - there will always be a user to whom such settings will not work, and it will be unnecessary to call the plug-in with its settings each time. Of course, he can get into the plugin code and fix the settings on his own, but then he will lose the plugin update. Therefore, the default settings should be visible globally, and they could also be changed globally.
For our plugin, you can use it like this:
Then any developer will be able to declare in his script:
And do not do this every time the plugin is initialized.
In all the examples above, the initialization code came down to the fact that we immediately returned this.each . In fact, almost all jQuery methods (plugins) return this .
Therefore, things like this are possible:
It is very comfortable. But for example, the val method , if there are no parameters, returns the value of the element. So with us, we must provide a way in which our plugin could return some values.
Please note we have added the second opt2 parameter . We will need it precisely for cases when some methods are called.
For example, for I / O plugins, it is important to change the original value .
No seriously, are you not using? JS forgives too much, and a huge amount of errors grows from here. Using this directive will save you at least from global variables.
Someone will say that you can declare 'use strict' at the very beginning of the file, but I do not recommend doing this. When the project grows, and other plugins will use it. And the creators of those plugins did not use the 'use script'. When grunt / gulp / npm collects all the packages into a single build file, you will get an unpleasant surprise.
I highly recommend JSLint for code validation. I have been using notepad ++ for development for many years , and it has no error highlighting. It may not be relevant for the IDE, but JSLint allows me to write better code.
At the beginning of your CSS file, zero out all CSS for the entire plugin. Those. we need some main class, under which everyone else will be. When using Less, this will give extra readability:
CSS in browsers is a global thing. And it is impossible to predict in what environment your plugin will be. Therefore, all, even minor classes, write with a prefix. I very often saw how the auxiliary classes disable, active, hover , completely break the plugin's appearance.
The comments correctly prompted that no modern JS project can live without updates, automatic builders, and dependency tracking.
In the code, we naively believed that jQuery was already connected to the project. But what if this is not so. Everything will break.
Such a script by default in the browser will work just like before, but when using Browserify and similar systems, it automatically tightens all the necessary dependencies. And here it can be not only jQuery. Any other library.
Serious guys will not use your plugin unless it has the ability to upgrade. Download the plugin archive with github is already yesterday. Now, just register on npmjs.com
Then, at the root of your project, execute
And follow the instructions. At the root of the project appears package.json
After which the command
From the same place, will publish the plugin in npm.
And end users will be able to install it for themselves like this:
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 scripts to the field , such commands:
And then just run:
It adds all the files to git , makes a commit , creates the tag of the current version, uploads it all to github , and then also updates the version to npmjs .
It takes the version from package.json , and it needs to be updated before each call. But you can automate this, just add to the beginning, before git add :
I work on windows, so the package.json variables are used like this - % npm_package_version% , in other OS you need to use $ npm_package_version .
For bower, the situation is almost the same. Only nowhere to register.
If you have not already installed bower.
Then
At the root of the project.
When bower.json is created, publish everything on github, or another hosting service.
After that, you can register the package:
That's all for now, write your tips in the comments. Thanks for attention!
Writing extensions in jQuery is quite simple, but if you want to learn how to write them so that it is easy to maintain and extend them later, welcome to cat.
Next will be a set of recommendations. The reader is expected to be familiar with jQuery plugin development. But I will still give an example of a 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('
');
});
};
}(jQuery));
This is all the functionality of our plugin. It adds after each selection item, div.xd_tooltips a new item. That is all he does.
All operations must be done in a separate class.
Having conceived a serious plugin that will grow, you do not need to put all the logic into an anonymous function in the each method, as in the example. Create a separate constructor function (class). Then, for each element from the selection, create a separate instance of this class, and save the link to it in the data of the original element. When you call the plugin again, we check data . This solves the problem of reinitializing.
(function ($) {
var Tooltips = function (elm) {
$(elm).after('' + $(elm).data('title') + '');
};
$.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));
Do all the general operations of hide, show, destroy with class methods
When the 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 public methods available. Methods can be done through prototypes, but there is a minus - you cannot use private variables. A better option would be to use local functions, which will then return in the hash.
More or less like this:
// лишний код, и нужно следить за this
var PluginName = function () {
this.init = function () {
// инициализация
};
};
// нормально, но нет локальных приватных переменных
// и также есть проблема с this
var PluginName = function () {};
PluginName.prototype.init = function () {
// инициализация
};
// идеально
var PluginName = function () {
var self,
init = function () {
// инициализация
};
self = {
init: init
};
return self;
};
Why is the third option the best?
Firstly, now there are methods inside the class that can be used without this prefix . this - in evil hands it is evil. In JS, it can be anything, and I highly recommend using it as little as possible.
Secondly, when the plugin is big, you decide to pass it through uglify . Then in the latter case, all references to init (except for the hash key self.init ) will be replaced by a one-letter variable, which, when the method is used frequently, decently reduces the code.
Third, private methods and variables are available to you, which you declare in the top var . It is very comfortable. Something like private methods in "adult" YaP.
From the last rule you need to squeeze one more:
All object initialization must also be kept in a separate init method
Do not do anything possible when creating an instance of the class. This is just an experience. Your plugin will grow sooner or later, and you will want to use its code from another place, not necessarily from jQuery. For example, you will have a cool parser in your code, 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 it needs.
Make a separate settings instance for each instance of the class
In the first example, we made one global options variable. And in scope jQuery some objects could get. Feel what it threatens? JS works so that if one of the elements then changes these options, then 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('
');
};
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 a hash {}. This is not true - there will always be a user to whom such settings will not work, and it will be unnecessary to call the plug-in with its settings each time. Of course, he can get into the plugin code and fix the settings on his own, but then he will lose the plugin update. Therefore, the default settings should be visible globally, and they 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 this every time the plugin is initialized.
Do not always return this
In all the examples above, the initialization code came down to the fact that we immediately returned this.each . In fact, almost all jQuery methods (plugins) return this .
Therefore, things like this are possible:
$('.tooltips')
.css('position', 'absolute')
.show()
.append('1');
It is very comfortable. But for example, the val method , if there are no parameters, returns the value of the element. So with us, we must 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 opt2 parameter . We will need it precisely for cases when some methods are called.
For example, for I / O 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, are you not using? JS forgives too much, and a huge amount of errors grows from here. Using this directive will save you at least from global variables.
(function ($) {
'use strict';
//...
} (jQuery));
Someone will say that you can declare 'use strict' at the very beginning of the file, but I do not recommend doing this. When the project grows, and other plugins will use it. And the creators of those plugins did not use the 'use script'. When grunt / gulp / npm collects all the packages into a single build file, you will get an unpleasant surprise.
I highly recommend JSLint for code validation. I have been using notepad ++ for development for many years , and it has no error highlighting. It may not be relevant for the IDE, but JSLint allows me to write better code.
Use your internal CSS reset
At the beginning of your CSS file, zero out all CSS for the entire plugin. Those. we need some main class, under which everyone else will be. When using Less, this will give extra 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 minor classes, write with a prefix. I very often saw how the auxiliary classes disable, active, hover , completely break the plugin's appearance.
Use modern project build methods
The comments correctly prompted that no modern JS project can live without updates, automatic builders, and dependency tracking.
In the code, we naively believed that jQuery was already connected to the project. But what if this is not so. Everything will break.
;(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS для Browserify
module.exports = factory;
} else {
// Используя глобальные переменные браузера
factory(jQuery);
}
}(function ($) {
'use script';
// код плагина
}));
Such a script by default in the browser will work just like before, but when using Browserify and similar systems, it automatically tightens all the necessary dependencies. And here it can be not only jQuery. Any other library.
Register your project in bower and npm
Serious guys will not use your plugin unless it has the ability to upgrade. Download the plugin archive with github is already yesterday. Now, just register on npmjs.com
Then, at the root of your project, execute
npm init
And follow the instructions. At the root of the project appears package.json
After which the command
npm publish ./
From the same place, will publish the plugin in npm.
And end users will be able to install it for 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 scripts to the 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 the tag of the current version, uploads it all to github , and then also updates the version to npmjs .
It takes the version from package.json , and it needs to be updated before each call. But you can automate this, just add to the beginning, before git add :
npm version patch
I work on windows, so the package.json variables are used like this - % npm_package_version% , in other OS you need to use $ npm_package_version .
For bower, the situation is almost the same. Only nowhere to register.
If you have not already 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 service.
After that, you can register the package:
bower register jodit https://github.com/xdan/jodit.git
That's all for now, write your tips in the comments. Thanks for attention!