1 / 44

High Performance JavaScript using Drupal's JavaScript API

Learn how to optimize JavaScript in Drupal 7, 8, and beyond with efficient techniques by Acquia Certified Developer JayFriend Ly. Explore cacheable methods, library usage, and more.

valentino
Download Presentation

High Performance JavaScript using Drupal's JavaScript API

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. High Performance JavaScript using Drupal's JavaScript API Best practices

  2. JayFriend Ly • Acquia certified Drupal Grandmaster • Acquia Certified Developer • Acquia Certified Developer – Front end specialist • Acquia Certified Developer – Back end specialist • Living in Japan since 2000 • Working with Drupal since 2007

  3. JayFriend Ly • Owner of Jaypanin Yokohama • English http://www.jaypan.com • Japanese http://www.jaypan.jp • Jaypan specializes in • high-performance AJAX/JavaScript heavy applications • Custom module/theme development • App Development • Drupal integration with outside technologies • JavaScript libraries • Linux programs • Multiple Drupal instances

  4. Overview • This presentation is primarily on JavaScript in Drupal 7 • The goal is to have the minimum memory footprint on each page load. • This is achieved through: • Adding JavaScript in a cacheable manner • Adding JavaScript in a manner that it can be removed further down the page build process, and is only attached when required • Storing jQuery objects in variables • Only Attaching event handlers to elements a single time • Traversingtheminimalnumberofelementspossiblewhenattachinghandlers • Removing event handlers from elements when elements are removed from the DOM (page)

  5. Adding JavaScript To Pages • Drupal 6 • Module and theme .infofiles • drupal_add_js()

  6. Adding JavaScript To Pages • Drupal 7 • Moduleandtheme.info files • drupal_add_js() • #attachedpropertyofrenderarrays • Libraries • Defined in hook_library() • Called using drupal_add_library(), or in #attached property of render arrays

  7. Adding JavaScript To Pages • Drupal 8 • Asset libraries: *.libraries.yml files • #attachedproperty ofrenderarrays • drupal_add_js() no longer exists

  8. Adding JavaScript in a cacheable manner • Adding JS to module and theme .info files • scripts[] = some_script.js • Pros • Easy to add • Cached • Passes through the JavaScript API

  9. Adding JavaScript in a cacheable manner • Adding JS to module and theme .info files • scripts[] = some_script.js • Cons • Added to every page – creates unnecessary overhead if not needed on every page • Cannot configure position in DOM • Can only be removed further down the line by using drupal_js_alter()

  10. Adding JavaScript in a cacheable manner • Adding JS using #attached property of render arrays • $element[‘#attached’][‘js’][] = array • ( • ‘type’ => ‘file’, • ‘data’ => ‘/path/to/some_script.js’, • );

  11. Adding JavaScript in a cacheable manner • Adding JS using #attached property of render arrays • Pros • Configuration can be finely tuned • Scripts are aggregated when caching is enabled • Scripts can be easily removed further down the page build process if unneeded • Scripts can be replaced further down the page build process if different functionality is needed.

  12. Adding JavaScript in a cacheable manner • Adding JS using #attached property of render arrays • Cons • Harder to learn

  13. Configuration options • Can be used for both drupal_add_js() and #attached • Scope • Header – added to the <head> of the page • Footer – added before the closing </body> tag

  14. Configuration options • Can be used for both drupal_add_js() and #attached • Group • Three groups–addedinthefollowingorder • JS_LIBRARY - Core JS scripts like jQuery, drupal.js, ajax.js • JS_DEFAULT – Default level • JS_THEME – Scripts added in the theme .info file • Weight – Scripts are added from lightest to heaviest

  15. Configuration options • Can be used for both drupal_add_js() and #attached • Scriptsareaddedasfollows: • Brokenintotwosections by scope (headerandfooter) • Ordered by group (JS_LIBRARY etc) within each scope • Ordered by weight within the group (lower weights come ahead of heavier weights)

  16. Configuration options • Can be used for both drupal_add_js() and #attached • Additional configuration options: • every_page • Defaults to FALSE • All files with this set to TRUE are aggregated together and included on every page • JS added through .info files have this set to TRUE • Preprocess • Defaults to TRUE • If aggregation is turned on, file will be aggregated if TRUE • Ideally should never be FALSE

  17. Configuration options-drupal_add_js() • drupal_add_js( • // The path • drupal_get_path(‘module’,‘my_module’).‘/js/some_script.js’, • // Configuration options • array( • ‘scope’ => ‘footer’, • ‘group’ => JS_LIBRARY • ‘weight’ => 1, • ‘every_page’ => TRUE, • ) • );

  18. Configuration options - #attached • $element[‘some_element’] = array ( • ‘#markup’ => ‘<a href=“node/123/delete”>’ . T(‘Delete’) . ‘</a>’, • ‘#attached’ => array ( • ‘js’ => array( • array( • ‘type’ => ‘file’, • ‘data’ => drupal_get_path(‘module’,‘my_module’).‘/js/delete_link.js’, • ‘scope’=>‘footer’, • ‘group’=>JS_LIBRARY, • ‘weight’=>1, • ‘every_page’=>TRUE, • ), • ), • ), • );

  19. Configuration options • SeeAPIpagefordrupal_add_js()formoreinformation: • https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_add_js/7.x

  20. AddingJavaScriptinaneffectivemanner • #attached is preferred over drupal_add_js() • #attached scripts are cached as part of the render array • Example: hook_block_view() • Scripts added using drupal_add_js() will not be outputted to the browser when block caching is enabled. • Scripts added as #attached are included when block caching is enabled

  21. AddingJavaScriptinaneffectivemanner • When using #attached, the element should be added to the render element to which it applies. • $element[‘wrapper’] = array( • ‘body’ => array( // body elements go here ), • ‘read_more_link’ => array ( • ‘#markup’ => <a href=“/node/123” class=“read_more”>Read More</a>’, • ‘#attached’ => array( • ‘js’ => array ( • array( • ‘type’ => ‘file’, • ‘data’ => drupal_get_path(‘module’, ‘my_module’) . ‘/js/read_more_link.js’, • ), • ), • ), • ), • );

  22. AddingJavaScriptinaneffectivemanner • If #access is set to FALSE on an element that has a script attached to it, the script will not be loaded. This means that the script is only loaded if the element to which it belongs is part of the page • function mymodule_element_view_alter(&$build) { • $build[‘wrapper’][‘read_more_link’][‘#access’] = FALSE; • } • The script read_more_link.js(see previous slide) will not be attached to the page anymore, since the element it is attached to has access set to FALSE.

  23. Writing effective Scripts • Scripts should be wrapped in an anonymous function. • (function() { • // Code goes here • }()); • Anonymous functions make scripts more bulletproof, as failures inside the function will(usually) not affect other scripts.

  24. Writing effective Scripts • Global variables to be used inside anonymous functions should be passed into the anonymous function. • (function(Drupal){ • // Use the Drupal object here • }(Drupal));

  25. Writing effective Scripts • Global variables can be aliased inside the anonymous function. For example, jQuery can be aliased as $ inside the function. • (function($){ • // This is ok as $ exists inside the anonymous function • $(“.some_element”); • }(jQuery)); • // This is ok as jQuery exists globally • jQuery(“.some_element”); • // This will cause an error as $ does not exist globally • $(“.some_element”);

  26. Writing effective Scripts • If a function name is used more than once in the global script, the last declaration is used, overwriting the original declaration • function doSomething() {console.log(“this”) } • function doSomething() {console.log(“that”) } • doSomething(); • This will cause “that” to be written to the console, and the original function will not/cannot be used. • This can happen when multiple scripts on a site use the same function names in the global scope.

  27. Writing effective Scripts • Functionnamesin separate anonymousfunctions don’t have to be unique. • (function() { • function doSomething() {console.log(“this”);} • doSomething(); • }()); • (function() { • function doSomething() {console.log(“that”);} • doSomething(); • }()); • The function name doSomething() isusedtwice,buteach exists inaseparateanonymousfunction,sothereisnoproblem and no error.

  28. Writing effective Scripts • Variables defined inside anonymous functions (actually all functions) are specific to that function • Variables do not exist outside that functions in which they are defined • (function(Drupal) { • var message = Drupal.t(“hello”); • alert(message); // alerts “hello/こんにちは” • }(Drupal)); • // message does not exist outside the anonymous function • alert(message); // Error

  29. Writing effective Scripts • Each time a jQuery object is created, the entire page (or the context – more to come later) is searched for the given selector. This adds overhead if an element is used regularly in a script. • jQuery objects used more than once in a script should be stored in script-wide variables.

  30. Writing effective Scripts • (function() { • // Declare our variable. This will be available to any • //function in this script • var container; • // The function to initialize this script • function init() { • // Save the jQuery object into the container variable • container = $(“#node_container”); • } • // Call the initialization • script init(); •   // the container variable now contains $(“#node_container”), for use • // anywhere in the script • }());

  31. Drupal.behaviors • Behaviors has two methods • attach() • detach()

  32. Drupal.behaviors - Attach • The Drupal equivalent of window.onloador $(document).ready(). • Executed on page load • Also executed when new elements are added to the DOM (function(Drupal){ Drupal.behaviors.uniqueKey={ attach:function(){ alert(“attached”); } }; }(Drupal));

  33. Context • Context will be some or all of the document • On initial page load, it will be the entire document • On subsequent calls, supposed to contain only the new elements inserted into the DOM • Pass this object to the jQuery constructor to ensure only the relevant part of the DOM is scanned for the given selector

  34. Context • (function($,Drupal) { • Drupal.behaviors.uniqueKey={ • attach:function(context) { • // Only .some_class found inside context will be selected • $(“.some_class”, context).appendTo(“body”); • } • } • }(jQuery,Drupal));

  35. AttachingBehaviors • Behaviors are attached using Drupal.attachBehaviors(). This should be called on new elements that are inserted into the DOM • Calls to Drupal.attachBehaviors() loop through each instance of Drupal.behaviors and call the attach() function (if one exists).

  36. AttachingBehaviors • $.ajax({ • url:”/path/to/ajax/callback”, • success:function(data) { • // Convert newly returned elements into (a) JavaScript object(s) • varnewElements = $(“<div/>”).html(data.newElements).contents(); • // Attach behaviors to the new elements. newElements will become the • // context passed to Drupal.behaviors.uniqueKey.attach() • Drupal.attachBehaviors(newElements) • // Insert the new elements into the DOM • $(“body”).append(newElements); • } • });

  37. AttachingBehaviors • Caveat- When creating a new jQuery object using contextas follows: • $(“.some_selector”, context) • jQuery searches for the selector within the children of context. This means that if the class is the main object in the context, it will not be found. For example if this is context: • <div class=“node”> • <p>Node contents</p> • </div> • Then the following will return an empty jQuery object, since .node is the top level element in context: • $(“.node”, context);

  38. AttachingBehaviors • This can be solved by changing this: • Drupal.attachBehaviors(newElements); • To this: • Drupal.attachBehaviors(newElements.parent()); • By passing the parent of the new elements, every level of the newly inserted elements will be searched. However, this can also significantly increase the number of elements in the context depending on what is contained within the parent, so this should only be used if absolutely necessary as it adds additional overhead.

  39. Drupal.behaviors - detach • Executed when removing elements from the DOM • Can be called by any script using Drupal.detachBehaviors() • Usually used to remove event handlers from elements before they are removed from the DOM (function($,Drupal){ Drupal.behaviors.uniqueKey={ detach:function(context){ $(“.some_class”, context).unbind(“click”); } }; }(jQuery, Drupal));

  40. Detaching behaviors • Behaviors are detached using Drupal.detachBehaviors(). This should be called on any elements right before they are removed from the DOM. • $(“.node_delete_link”).click(function() { • //TraversetheDOMupwardsuntilthenodewrapperisfound • $(this).parents(“.node:first”).slideUp(300, function() { • // $(this) is $(“.node”) • Drupal.detachBehaviors($(this)); • // Remove $(“.node”) from the DOM • $(this).remove(); • }); • });

  41. $.once() • Code called inside $.once() will only be executed a single time • Generally used to prevent event handlers from being attached multiple times to an element • $.once() acts like $.each() in that it will loop through each element in the jQuery object • Inside the callback function, $(this) refers to the current element. • $(“.some_element”).once(“some-unique-key”, function() { • // $(this) refers to the current iteration of $(“.some_element) • console.log($(this)); • });

  42. once • $.once has changed with D8 (jQuery 2), and needs to be called along with $.each(). It also handles tracking of which elements have had $.once applied differently than in D7. • D7: • $('.mybehavior', context).once('mybehavior’ • D8: • $(context).find('[data-mybehavior]').once('mybehavior').each(function() { • See https://www.drupal.org/node/2457769 for more information.

  43. Tying it all together • (function($, Drupal, window) { • var initialized, container; • function loadNewNodes() { • $.ajax( • { • url:"/path/to/server/script/that/loads/new/nodes", • success:function(data) { • varnewNodes = $("<div/>").html(data.nodes); • Drupal.attachBehaviors(newNodes); • container.prepend(newNodes.children()); • window.setTimeout(loadNewNodes, 5000); • } • }); • } • function nodeHideLinkAttach(context) { • $(".node_hide_link", context).once("node-hide-link-attach", function() { • $(this).click(function(e) { • e.preventDefault(); • $(this).parents(".node:first").slideUp(300, function() { • Drupal.detachBehaviors($(this)); • $(this).remove(); • }); • }); • }); • } • function init() { • if(!initialized) { • initialized = true; • container = $("#some_container_div"); • window.setTimeout(loadNewNodes, 5000); • } • } • Drupal.behaviors.uniqueKey = { • attach:function(context) { • init(); • nodeHideLinkAttach(context); • }, • detach:function(context) { • $(".node_hide_link", context).unbind("click"); • } • }; • }(jQuery, Drupal, window));

  44. FURTHER READING • Tutorial on this subject: http://www.jaypan.com/tutorial/high-performance-javascript-using-drupal-7s-javascript-api • Thank you.

More Related