Javascript implementations of WordPress features in PoP

Recently there has been a discussion among the WordPress development community on what javascript framework should be chosen to be added to WordPress in the near future, with React and Vue as the most likely candidates. In that same blog post, a comment was highlighting that the discussion is not focusing on how good each framework is at implementing those functionalities which sit at the very core of WordPress, such as: hooking, localization and dynamic activation/deactivation of modules.

Concepts alike to these functionalities have been implemented for the PoP javascript engine. In this blog post we will detail how our solutions have been designed and implemented.

Dynamic activation/deactivation of modules

An object called JSLibraryManager allows to register javascript function through objects. This object has function register, where JS objects can register themselves for each function they are implementing, and function execute which triggers the execution of the specified function in all objects which registered themselves as implementing it.

(function($){
JSLibraryManager = {

  // All the registered JS libraries
  libraries: [],

  // All the methods that each library can handle. Each method can be served by different libraries
  // This way each library can also serve common methods, like 'destroy', 'getState', 'clearState', etc
  methods: {},

  register : function(library, methods, highPriority, override) {

    var t = this;

    t.libraries.push(library);
    $.each(methods, function(index, method) {

      // override: allows for any library to override others
      if (!t.methods[method] || override) {
        t.methods[method] = [];
      }

      if (highPriority) {
        t.methods[method].unshift(library);
      }
      else {
        t.methods[method].push(library);
      }
    })
  },

  execute : function(method, args) {

    var t = this;

    var ret = {};
    var libraries = t.methods[method];
    if (libraries) {
      $.each(libraries, function(index, library) {

        ret['l'+index] = library[method](args);
      });
    }

    return ret;
  }
};
})(jQuery);

A javascript object then registers all its public functions under JSLibraryManager:

(function($){
popBootstrap = {

  tooltip : function(args) {
    
    var t = this;
    var pageSection = args.pageSection, targets = args.targets;

    jQuery(document).ready( function($) {
      
      targets.each(function() {

        var tooltip = $(this);
        var options = {
          placement : tooltip.data('tooltip-placement') || "top",
          title: tooltip.attr('title'),
        };

        tooltip.tooltip(options);
      });    
    });    
  },

  popover : function(args) {

    var t = this;
    var pageSection = args.pageSection, block = args.block, targets = args.targets;
    jQuery(document).ready( function($) {
      
      var options = {
        placement: 'auto',
        delay: {
          hide: 100
        },
      };    
      targets.each(function() {

        var popover = $(this);
        var popoverTarget = $(popover.data('popover-target')).first();
        var content = popoverTarget.html();
        var popoverOptions = $.extend({}, options, {content: content});
        popover.popover(popoverOptions)
      });  
    });  
  },  
};
})(jQuery);

//-------------------------------------------------
// Register all functions
//-------------------------------------------------
JSLibraryManager.register(popBootstrap, ['tooltip', 'popover']);

Finally, in the PHP object we specify what javascript functions must be executed on the newly created elements on the DOM, for each module:

class PoP_Template_Processor_PopoverLayoutsBase extends GD_Template_ProcessorBase {

  ...

  function get_block_jsmethod($template_id, $atts) {

    $ret = parent::get_block_jsmethod($template_id, $atts);  
    $this->add_jsmethod($ret, 'popover');    
    return $ret;
  }
}

The PoP engine in the frontend will execute the specified javascript functions for each module, by calling the method execute from  JSLibraryManager, which will invoke all registered functions under that name. If no javascript object has registered as implementing it, then nothing will happen.

When activating a WordPress plugin, it will load its .js files containing javascript objects registering themselves; when deactivating, the .js files will never be loaded, and so its javascript objects will never be registered. Hence, activating/deactivating plugins will indeed do the same at the frontend side.

Hooking

Adding actions and filters to javascript is a continuation of the strategy defined above. We can define any hook as a function, to be invoked under some name, under which can register those javascript objects implementing the hook.

Let’s see an example. Say that we need to modify some value, but we don’t know or care about who will modify it, we can simply call function execute from JSLibraryManager, passing the name of the hook and an object containing all values to modify alongside other needed values. Because javascript passes objects by reference, the modified properties of the object can be read again after the completion of the execution.

function fetchURL(url, target) {
  
  ...

  // Allow plug-ins to modify the url
  var args = {
    url: url,
    target: target
  };
  JSLibraryManager.execute('hookFetchURL', args);
  url = args.url;

  fetch(url);
}

Finally, we register the functions implementing the hooks, by registering an object and its functions under JSLibraryManager.

(function($){
HookObject = {

  hookFetchURL : function(args) {
  
    var t = this;
    args.url = t.add_query_arg(args.url, 'timestamp', Date.now());
  },

  add_query_arg : function(key, value, url) {

    url += (url.split('?')[1] ? '&':'?') + key + '=' + value;
    return url;
  }
};
})(jQuery);

//-------------------------------------------------
// Register all functions
//-------------------------------------------------
JSLibraryManager.register(HookObject, ['hookFetchURL']);

Localization

PoP does not attempt to provide localization features in the front-end. Instead, all values are already localized from the back-end, and sent as configuration values in the JSON code provided by the API.

In other words, we do not create a javascript template with this logic:

Your event is on {{localize "13/08/2017"}}

Instead, our Handlebars javascript template is like this:

{{titles.event-date}}

And then we generate the values on the back-end as it is usually done with WordPress, filling it in the configuration of a module, done in its corresponding PHP object:

class PoP_Template_Processor_EventLayoutsBase extends GD_Template_ProcessorBase {

  function get_template_configuration($template_id, $atts) {
  
    $ret = parent::get_template_configuration($template_id, $atts);

    $ret['titles'] = array(
      'event-date' => sprintf(
        __('Your event is on %s'),
        date_i18n(get_option('date_format'), strtotime('13-08-2017'))
      )
    );

    return $ret;
  }
  ...
}

Finally, the PoP engine in the front-end will merge the configuration with the javascript template, and produce the desired output.

Welcome to the PoP framework!
Break the information monopoly

the PoP framework is open source software which aims to decentralize the content flow and break the information monopoly from large internet corporations. Read more.


Sign up to our newsletter: