There are different ways to create a layout for Drupal. Someone is making up already darkened pages, someone is trying to do with standard themes, but as a rule, at first the layout designer makes up pages by design, and the output is a set of html files - slices. Then developers integrate these files piece by piece when teming.
And in the process of integration are errors, some modifications, so the layout and scripts related to it, should be available for editing and testing.
It is about the latter method that we will mainly talk about, I will describe typical mistakes and best practices for solving them when writing JS scripts for D7. I think it will be interesting both for the Drupal typesetters, and for the developers of the modules. In the case of web designers, the basic principle that should be guided is the fact that your script will work in Drupal environment, and this imposes a number of restrictions, ideally, the script should connect to Drupal and work without any additional modifications, while working on slices outside of Drupal .
JQuery variable name
In Drupal 7, in contrast to version 6, you can’t just take it and use jQuery methods through $, because this variable is simply not declared.
Often you have to deal with the situation when the layout designer wrote the scripts that work in the layout, or you just took a ready-made script, and with the integration problems start. Most likely the problem is in using the $ variable. You don’t need to be scared - to go and to replace $ with jQuery everywhere.
Let's say there is a script:
$(function () { $('div.menu-expanded').hide(); $(....); });
To make it work when connected to Drupal, it needs to be reissued as follows:
(function ($) { $(function () { $('div.menu-expanded').hide(); $(...); }); }) (jQuery);
Thus, by simply wrapping the entire code intact, using the standard scope allocation mechanism, the jQuery object became accessible by the name $.
It is better to immediately explain to your typesetter such a technique in order to avoid problems in the future and not to climb on his scripts with corrections.
Drupal.behaviors
Drupal has a special way of handling the document ready event, Drupal.behaviors, which provides several advantages. In D6, he was already, just a little changed the way of writing.
For example, there is such a script:
$(function (){ $('a.tooltip').someTooltipPlugin(); });
Everything is simple and understandable, but what will happen if the elements of a, with a class of interest to us appear on the page asynchronously? It turns out that you need to manually call a function that re-hangs all the handlers, or duplicate this code at the return point of asynchronous content.
The behaviorist mechanism proposes the following concept.
All code that should be called when the page is ready should be enclosed in the following construction:
(function ($) { Drupal.behaviors.yourName = { attach : function(context, settings) {
In this way, we have defined our behavioral - we simply added a new property to the Drupal.behaviors object. Its advantage is that in addition to the call when loading the entire page, Drupal will call all behaviorists, for example, when receiving an ajax request response (in case it is a standard request, for example, updating a form or twisting).
All behaviorists can be called manually like this:
Drupal.attachBehaviors(document, {});
Drupal will go through all the properties of Drupal.behaviors and call the attach method.
Pay attention to the first argument (I decided not to talk about the second argument as it is in my understanding that purely front-end developers would not be helpful). Here you need to transfer the contents (DOM element or selector) to which you want to apply (“attach”) the behaviorr. This value will be available as a context argument in each behaviorator. This is necessary in order to limit the scope of the code inside the behaviorr.
If you take the code from the previous example, the handler will be added to all links on the page with each call of the behaviorr. But if we rewrite the code like this:
$('a...', context).someTooltipPlugin();
Then, when loading the page, all links with a class on the whole page will be processed, because the first call of behaviorists occurs with the document object as the context.
Then at each call of behaviorists, but only inside the newly received content.
For example, if we have a list with ajax pagination, then on going to the second page we are interested in re-processing only the content of the second page, and not the entire document.
If you write your module, you simply have to wrap up your functionality in behaviorr, which would allow third-party developers to “attach” your logic-behavior to their content. And at any time, and not only when loading the page.
Nuance for web designers. In the case when the layout is first prepared, and then it is planned to be bolted to Drupal - the code should be drawn up immediately using behaviorists.
Naturally, on the layout, behaviorists will not work without native Drupal scripts, there will be errors in accessing non-existent variables, etc.
To avoid this, I suggest connecting the drupal.js script, which lies in the Drupal core on the layout.
In this way, we will be able to write basic scripts in a form suitable for Drupal, and when applying the layout, just copy them.
All that the typesetter needs to know is that instead of the usual document ready to write behaviorists. And inside always use context when building selectors.
')
jQuery.once
If behaviorists were needed to additionally add handlers to new content, now consider the reverse problem. Some handlers can be assigned to the same elements twice, but this is not always necessary.
The easiest way is to add a class when first processing, and to include the absence condition of this class in the selector.
$('a.tooltip:not(.processed)', context).addClass('processed').someTooltipPlugin();
This construction inside the behaviorr ensures that when the code is re-executed, the handler is added once.
The same mechanism is embedded in the jQuery.once plugin, which is included in the D7 core and is available by default on any page.
Using the plugin, the construction above can be replaced like this:
$(context).once('myIdentifier', function () {
Where myIdentifier is any unique value that will be used as the same processed class.
Can be replaced by a shorter design:
$(context).once(function () {
In this case, the plugin itself will form a unique class.
Base url
Quite a common mistake - forget to consider the base url. Not once met her in proven Contrib modules.
Recognizing it is easy - a slash at the beginning:
var url = '/ ajax / my-module / some-action';
Usually they develop on the same environment, and just hardcodes the slash, and when the base URL changes - some scripts stop working ... Most often the error occurs when forming the path for ajax requests and when specifying the path for the a and img tag attributes.
The correct one is to use a special variable, which is declared by the kernel:
var url = Drupal.settings.basePath + 'ajax/my-module/some-action';
Ways for ajax requests
A small recommendation (in our company, this is just a standard) - when forming the ajax request path, start them with the word ajax - this will make it possible to easily separate all ajax requests from the usual ones, if necessary. For example, exclude them from the cache at once, search in logs, etc. Then the module name with a hyphen will immediately tell where to look for the handler of this request. Hyphens are needed for the readability of URLs, even if no one sees it, it’s better to stick to one format, because you won’t build alias nodes with underscores?
An example of the "correct" path according to this logic would be - ajax / my-module / some-action.
Line output
There are a number of functions that should be used when outputting text to JS.
I will simply list the main ones, without deepening - all of them are analogs of the PHP core functions:
They are all announced in drupal.js, so they won't work without it.