PoP is an open source framework for building progressive web apps, decentralized websites and social networks.

PoP combines WordPress and Handlebars into an MVC architecture:

Show me how it works

Development features

The PoP engine automatically provides the website of its own API, just by adding output=json to any page’s URL.

Key benefits:

Documentation-less API: the API needs no documentation, because simply removing parameter “output=json” from its URL renders the website, displaying the information for GET requests and forms with their required fields for POST operations.

Simplicity: the API URL can be calculated directly from the website URL.

SEO friendly: the API URL keeps WordPress’ permalink structure and, hence, the slug’s high ranking.

API-ready: the API can also be consumed by third-party systems, mobile phone apps, etc, without further work needed by the developer.

Because they comprehend the same JSON structure, PoP websites can natively interact with each other, fetching and processing data from each other in real time, for both GET and POST requests.

The calendar below is fetching and displaying events from many different websites. Clicking on an external link inside the calendar opens the page in this same website.

Applications:

Decentralization: the application’s data sources can be split into several websites/domains

Aggregation: content from a variety of PoP websites can be aggregated and displayed together

Component microservices: components/libraries can be made available to developers as SaaS






		

		

Most content in the website, by nature “dynamic” or mutable, is made “static” or immutable by serving it through a Content Delivery Network (CDN), thus making the best of both worlds: content is dynamically generated through WordPress, but it can be cached as coming from a static site generator.

This feature has the following advantages:

Cheaper hosting: websites with high traffic can reduce their hosting costs, since content is mainly served from the CDN instead of the server.

Faster: CDN data-centers are located near the user, thus greatly reducing latency and boosting the overall website speed.

WordPress is still there: content is still created dynamically, through WordPress. The static version of the website is generated on runtime.






		

		

Through the implementation of service workers, PoP provides offline first capabilities, with the following benefits:

  • the website functions offline
  • performance increases by reducing network requests
  • pages load immediately (from the second access onwards)

Service workers are not yet implemented in all modern browsers, however both Chrome and Firefox (the two most popular browsers, with a combined market share of more than 70%) do support it.

PoP components were designed to be separated and recombined effectively, in several dimensions:

All code is organized into modules. A module is either an atomic/reusable functionality, or itself a composition of other modules. In its .php file, each module defines:

  • its submodules
  • its configuration (classes and styles, text strings, links, etc)
  • data fields to retrieve from the queried database object
  • what javascript functions to execute after appending to the DOM
class PoP_PostLayoutProcessor {

  function get_name() {
    
    return 'post-layout';
  }

  function get_submodules() {
    
    return array(
      'post-details'
    );
  }

  function get_template_configuration() {  
    
    return array(
      'classes' => array(
        'div' => 'alert alert-info'
      ),
      'strings' => array(
        'title' => __('Title:'),
        'url' => __('URL:')
      ),
      'module-ids' => array(
        'layout-details' => 'post-details'
      )
    );
  }    

  function get_data_fields() {
    
    return array(
      'id', 
      'title', 
      'url', 
      'content'
    );
  }

  function get_block_jsmethods() {
    
    return array(
      'img-content' => 'imgResponsive'
    );
  }
}

// Initialize
new PoP_PostLayoutProcessor();

PoP gets the configuration and the data-fields from the modules, queries the database, and sends the results to the client as a JSON code.

In the front-end, PoP creates a context containing the module’s configuration and the queried object data (under itemObject), and executes the corresponding Handlebars javascript template to generate the HTML code:

<div class="{{classes.div}}" id="{{generateId itemObject.id}}">
  <p>{{strings.title}} <strong>{{itemObject.title}}</strong><</p>
  <p>{{strings.url}} {{itemObject.url}}<</p>
  <div id="{{generateId itemObject.id group='img-content'}}">
    {{{itemObject.content}}}
  </div>

  {{#withModule . module-ids.layout-details}}
    {{enterModule ../.}}
  {{/withModule}}
</div>

(Please notice that the current module ‘post-layout’ is executing submodule ‘post-details’, producing its HTML to be nested inside.)

After appended to the DOM, javascript methods are executed on the corresponding elements, passed by PoP under args.targets:

(function($){
popPostUtils = {

  imgResponsive : function(args) {

    var targets = args.targets;
    targets.find('img').addClass('img-responsive');
  }
};
})(jQuery);

// Initialize
popJSLibraryManager.register(popPostUtils, ['imgResponsive']);

In summary, each module is composed of:

  • A PHP object defining the module’s configuration
  • A Handlebars .tmpl template, with the view of the module
  • Javascript functions to execute on the elements added to the DOM
  • Styles defined in .css files

There are different types of modules, namely: topLevel, pageSection, block, blockGroup and module. They have the following hierarchy:

  • 1 topLevel contains N pageSections
  • 1 pageSection contains N blocks/blockGroups
  • 1 blockGroup contains N blocks/blockGroups
  • 1 block contains N modules
  • 1 module contains N modules (ad infinitum)
It is the single, top-level module loaded by the application. It is similar to WordPress’ hierarchy template (Single, Home, Archive, Author, etc).

toplevel

It is a physical/functional section on the website, either visible or hidden (eg: modals, offcanvas, etc).

pagesections

It is an independent component, which keeps a state; it can be added to different pageSections and customized for each.

blocks

It is a special type of block, which collects/wraps other blocks and allows to synchronize state among them.

blockgroups

It is an atomic/reusable functionality, or a composition of modules; modules are the building blocks of a PoP application.

modules


PoP operates in a strictly top-down manner: parent modules know what inner modules they contain, down the line to the last module, and can change those modules’ properties; however, inner modules do not know who is including them, and do not need to care either.

When initializing properties, parent modules take precedence over inner modules: when a module initializes itself, if any one property has already been set by a parent module, then that value will be kept. This way, PoP allows for dependency-injection, where each module can set its default attributes, yet allow for properties to be injected externally in advance (possibly coming from a configuration file).

class GD_EM_Template_Processor_ScrollMapBlocks extends GD_Template_Processor_BlocksBase {

  function init_atts($template_id, &$atts) {

    // $template_id = current module
    $this->add_att($template_id, $atts, 'direction', 'horizontal');

    // 'map-layout' = submodule
    $this->add_att('map-layout', $atts, 'direction', 'horizontal');
    
    return parent::init_atts($template_id, $atts);
  }
}

The decentralization features of PoP allow to create an application architecture in which different components are being served from different domains:

site-wireframe

To implement it, simply set the external domain source on the block. This configuration value can be defined directly in the block, or it can be injected externally, in which case the block is unaware where it is fetching its data from.

class GD_Template_Processor_SectionBlocks extends GD_Template_Processor_BlocksBase {

  function get_dataloadsource_domain($template_id, $atts) {

    switch ($template_id) {
      
      case GD_TEMPLATE_BLOCK_AUTHORHIGHLIGHTS_SCROLL_LIST:
        
        return 'https://demo.getpop.org';
    }

    return parent::get_dataloadsource_domain($template_id, $atts);
  }
}
class GetPoP_Template_Processor_CustomBlockGroups extends GD_Template_Processor_ListBlockGroupsBase {

  function init_atts_blockgroup_block($blockgroup, $blockgroup_block, &$blockgroup_block_atts, $blockgroup_atts) {

    if ($blockgroup == GD_TEMPLATE_BLOCKGROUP_AUTHORHIGHLIGHTS_SCROLL && $blockgroup_block == GD_TEMPLATE_BLOCK_AUTHORHIGHLIGHTS_SCROLL_LIST) {

      $this->add_att($blockgroup_block, $blockgroup_block_atts, 'dataloadsource-domain', 'https://demo.getpop.org');
    }

    return parent::init_atts_blockgroup_block($blockgroup, $blockgroup_block, $blockgroup_block_atts, $blockgroup_atts);
  }
}





		

		

PoP provides optimal rendering capabilities, allowing to render the HTML in the backend when loading the page first time, yet rendering all code in the frontend through javascript from the 2nd page-load onwards.

It is isomorphic: the code used to render the HTML in both front and back-end is the same: precompiled Handlebars javascript templates, rendered in the front-end using the Handlebars runtime library, and in the back-end using LightnCandy.

Advantages:

SEO: all search engines can perfectly parse the site’s HTML.

Integration with technologies and systems: server-side HTML rendering is needed for implementing new technologies such as AMP and microformats, and for broad system interoperability.

Speed: the first page is loaded much faster, since it doesn’t need to wait to load javascript templates to render it. Starting on the 2nd page onwards, javascript takes over, rendering faster and making the website dynamic.

Re-usability: developers implement the code only once and use it everywhere: front and back-end, emails, etc.

PoP queries the database and recreates the data structure in the JSON code sent to the front-end, keeping the relationships among all different objects (eg: if two different posts share the same author, the author’s data is output only once, and the posts reference the author through the id).

The produced JSON object has key ‘database’, containing the data for all objects queried in the request; key ‘dataset’, with the ids of the result objects for each block; and key ‘settings’=>‘db-keys’, with the top-most object domain (“posts”, “users”, “locations”, etc) for each block.

json-structure

Viewing the response’s JSON code in Chrome’s Console. Pay attention how under key ‘database’ the object data is relational, and objects from different domains have also been fetched: the post’s author (with id 851) and the post’s location (with id 23494); and under key ‘dataset’ it indicates the result object ids for each block (posts with ids 23787, 23784, 23644 for the main block)

PoP integrates with WordPress mainly in the back-end, executing WordPress functions to fetch data. Implementing a PoP website requires the implementation of these objects to communicate with the WordPress API to fetch the data. PoP already ships with implementations for the most basic object types (posts, users, tags, comments) and some custom post types (events, locations).

Basic example

Below is a description of setting up some required components: a “block”, a “dataloader”, some module defining “data-fields”, and a “fieldProcessor”.


A “block” is a special module in PoP which defines the configuration for fetching data from WordPress, such as what “dataloader” will be invoked, how to filter the query, the data’s source URL, among others:

class GD_Template_Processor_SectionBlocks extends GD_Template_Processor_BlocksBase {

  function get_dataloader() {

    return GD_DATALOADER_POSTLIST;
  }

  function get_dataload_source() {

    global $author;
    return get_author_posts_url($author);
  }

  function get_dataload_query_args() {

    global $author;
    return array(
      'author' => $author,
      'cat' => 55,
    );
  }
}

// Initialization
new GD_Template_Processor_SectionBlocks();

A “dataloader” executes WordPress functions to fetch data from the database, allowing to set the args in the query to filter the results.

define ('GD_DATALOADER_POSTLIST', 'post-list');

class GD_DataLoader_PostList extends GD_DataLoader_PostListBase {

  function get_name() {
    
    return GD_DATALOADER_POSTLIST;
  }
  
  function get_query($vars = array()) {
  
    $query = array();
    if ($cat = $vars['cat']) $query['cat'] = $cat;
    if ($author = $vars['author']) $query['author'] = $author;

    return $query;
  }
  
  function execute_query($query) {

    return get_posts($query);
  }
}

// Initialization
new GD_DataLoader_PostList();

What data will be retrieved from the queried object is defined in all modules, through specifying what “data-fields” they need.

class GD_Template_Processor_PostLayouts {

  function get_data_fields() {

    return array('title', 'url', 'comments-count');
  }
}

// Initialization
new GD_Template_Processor_PostLayouts();

The “data-fields” are processed and resolved through an object called a “fieldProcessor”.

define ('GD_DATALOAD_FIELDPROCESSOR_POSTS', 'posts');

class GD_DataLoad_FieldProcessor_Posts extends GD_DataLoad_FieldProcessor {

  function get_name() {
  
    return GD_DATALOAD_FIELDPROCESSOR_POSTS;
  }
  
  function get_value($resultitem, $field) {
  
    $post = $resultitem;

    switch ($field) {

      case 'title' :
        return get_the_title($post->ID);

      case 'url' :
        return get_permalink($post->ID);
    
      case 'post-type' :
        return $post->post_type;

      case 'comments-count' :
        return get_comments_number($post->ID);

      case 'cats' :
        return wp_get_post_categories($post->ID, array('fields' => 'ids'));

      case 'tags' :
        return wp_get_post_tags($post->ID, array('fields' => 'ids'));
    }

    return null;
  }
}

// Initialize
new GD_DataLoad_FieldProcessor_Posts();

In summary, adding the required objects to implement a WordPress website using PoP is a straightforward process, and the code already ships with plenty of implementations.

Open all
In addition to caching the HTML response, the website caches the settings for all similar posts. This way, upon requesting two posts belonging to the same post type (such as events), the second post will access the cached settings and need only retrieve the object data from the database.
All pages have been stripped of all user information related to them (eg: has the user recommended it?). All such user information is loaded on a subsequent, immediate request.

As a consequence, requesting the page produces the same response for all users, allowing most pages in the website to be cached, independently if the user is logged in or not; only those pages showing user-specific information, such as My Content, are non-cacheable.
Selected content, such as comments added to a post, can be loaded only after the page has loaded, allowing for a faster initial page load.
The software pre-loads specified pages, running in the background, so they are already loaded by the time the user clicks on the corresponding link. Eg: when clicking on the following links they will open immediately, since they were preloaded: Add Post, Contact us. Once these pages have loaded, if available, their code is stored in the browser using Local Storage.
Clicks can be intercepted in the front-end, and choose to do something instead of sending the request to the server. Eg: adding a post can open in a new page, or be intercepted and open immediately in a floating window.
When the user clicks on a link, PoP intercepts the request to submit it using AJAX. PoP can add extra parameters to the URL, which are not in the original link (and so hidden to the user), such as: language, theme, timestamp, etc.

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: