Please copy/paste the html code below into your website.

Preview:

Please select and copy (Ctrl + C) the URL below.

Preview:

Please select and copy (Ctrl + C) the URL below.

Add your thought, tweet @thePoPframework



Plugins can easily integrate with PoP to add their own views. Eg: the map below is done by integrating with Events Manager.
Scroll right to load more results
The same page can be open in different sections on the page, such as sidebars, floating windows, modal windows, or your own creations.

Open “PoP Blog”...

In a floating windowIn a modal window
Similar to a browser, the website keeps all pages open, showing tabs to switch among all of them.
Content posted by the users can be published immediately, or await for admin’s approval for publishing. In case the post is published immediately, a “Flag inappropriate content” mechanism is in place for the community to moderate the content.
After posting new content, all users concurrently browsing the website (either logged in or not) will see a pop-up notifying them of the update.
View 1 new post
Multimedia: add images, image galleries, Youtube videos, tweets, and other embeds.

#Hashtags: categorize the content by adding hashtagged words in it; each hashtag has its own permalink, under which is shows all content tagged with it.

@Mentions: the user writing content can @mention any other user inside the post, and this person will receive a notification.
Inter-linking: the post can be related to any number of already existing posts, giving a proper context to the post created.

Co-authoring: the post can have any number of co-authors, which will be also given rights to edit the post.
Custom metadata: plugins can add their own metaboxes. Eg: the event post type, from Events Manager, can add fields for date/location.
Loading data
Users can subscribe to #hashtags, and receive notifications whenever new content is tagged with them.
Users can follow other users, and receive notifications whenever there is any new activity associated to them.
Members:
Users can recommend posts, extract highlights from them, and up/down-vote these highlights.
These are the general notifications. Please log in to see your personal notifications.
Members:
It allows to search posts and users, or quickly access any post, user or tag.
PoP allows the creation of highly customized filters, for all type of content (posts, users, comments, tags).
Open all

Extended caching

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.

User state detached from the response

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.

Lazy loading

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.

Background pre-loading of data

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.

Intercepting clicks

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.

Extra information added to the URL, hidden to the user

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.
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 displaying events from a different website (source calendar here). Clicking on a link inside the calendar opens the page locally, not in a new tab (check it out!).

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

Such as a list or grid of items, full view into each, or you can create your own custom views.
PoP themes have different “modes” or presentation styles, used for displaying printing or embeddable versions of the website, alternative layout designs, etc.
Users can post content directly from the front-end. In addition to creating posts and custom-type posts (eg: events), users can also submit external URL links, which will be embedded directly from their source.
Scrolling down displays a never-ending stream of fully-loaded, yet partially-minimized, content items.

Users receive real-time notifications concerning any activity related to them and their network (i.e. their followed users + those users who are members of the same communities), such as:

  • a followed user posts new content
  • a subscribed topic receives a new post
  • a recommended post gets a new comment
  • the user has a new follower
  • etc
PoP allows the creation of user roles, for granting special powers to the users, on a user-by-user basis. For instance, members may be given access to premium, behind-a-paywall sections.
The user below has been granted role “Community”, for which this user can accept members in the website.

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

Sign up to our newsletter:


What is being said about PoP? Brought to you by…

Brought to you by…

Targets

Targets

Tabbed browsing

Tabbed browsing

Content moderation

Content moderation

Content updates

Content updates

https://getpop.org/en/add-location/ Subscribe to topics

Subscribe to topics

Follow users

Follow users

Recommend posts, extract and up/down-vote highlights

Recommend posts, extract and up/down-vote highlights

Notifications

Notifications

Mark all as read Search / Quick access input

Search / Quick access input

Filters

Filters

Core features

Core features

Social network features

Social network features

Development features

Development features

Multiple views to display content

Multiple views to display content

Multiple presentation styles

Multiple presentation styles

Content features

Content features

Feed Feed Extended List List Thumbnail Details Email PoP Blog

PoP Blog

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:

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 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.

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)

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);
  }
}

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

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);
  }
}

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:

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:

leo-zakopane

Leonardo Losoviz is the creator of the PoP framework, and has been working tirelessly on it since 2013.

Read more about us and the story behind PoP.

Explore PoP websites in the wild:

Implement your website

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

Check the demoOpen in GitHub

PoP combines WordPress and Handlebars into an MVC architecture:

#collapse-howitworks.panel-group {margin-bottom: 0;}#collapse-howitworks.panel-group .panel {border-radius: 0;}#collapse-howitworks .panel {margin-bottom: 0;background-color: transparent;border: 0;border-radius: 0;-webkit-box-shadow: 0 0 0;box-shadow: 0 0 0;}#collapse-howitworks .panel-body {padding: 0;} Model View Controller

Model

 

View

 

Controller

Show me how it works

/*#carousel-howitworks {border: 1px dotted #b94f5a;background-color: #fff;background-color: rgba(255, 255, 255, 0.7);}*/.carousel-howitworks-caption {/*color: #fff;*/text-align: center;/*text-shadow: 0 1px 2px rgba(0,0,0,.6);*//*background-color: #b94f5a;*//*background-color: rgba(185, 79, 90, 0.75);*/padding: 5px;background-color: rgba(255,255,255,0.7);border-radius: 4px;}/* Somehow it adds

out of nowhere, which screw the formatting, so make these invisible */#carousel-howitworks > p,#carousel-howitworks > .carousel-inner > p,#carousel-howitworks > .carousel-inner > .item > p {display: none;} Whenever the user clicks on a link…
  1. PoP intercepts the user request in the front-end and delivers it to the webserver using AJAX 2. the PoP controller processes the request and delivers it to WordPress to get the data 3. PoP generates a response in JS