# Views v2 This document is a work-in-progress meant to provide both an overview of the new Views architecture and an implementation guide for anyone working on a View implementation. ## The Repository-View-Context pattern Without trying to create too complicated relations with better known patterns, this second version of The Events Calendar Views adopts an architecture aimed at minimizing the amount of code required to implement new Views and reduce the code duplication that existed in the previous View implementation. Much of that duplication existed as a direct consequence of each View requiring to be rendered in, mainly, two different contexts: 1. PHP initial state request; e.g. a request triggered by entering `http://example.tribe/events` in the address bar 2. AJAX request triggered by Javascript code from the View front-end; e.g. a request triggered by a user applying a keyword to a View ### The Context The two context provide pretty much the same information (who's looking at the view, what events should we show, what settings should we apply) in a way which is equivalent enough to have similar code but different enough torequire its duplication. The biggest difference between the two request types is that the PHP initial state will have to filter the main query to return the correct events, while the AJAX request has more of a leeway starting with, pretty much, a clean slate in query terms. In short the Repository-View-Context pattern tries to "hide" the context and request differences behind the API provided by the `tribe_context()` object; as an example: ```php $events_per_page = tribe_context()->get( 'events_per_page', 10 ); ``` The code above will, in a cascading style, top to bottom: * look for a `posts_per_page` value explicitly set in the current request by means of a query variable * look for a `posts_per_page` value in the `tribe_events_calendar_options` * look for a `posts_per_page` WordPress option * finally fallback on using the default value of `10` we provided as second argument And this is just a simple example! The Context abstraction is hiding way more complex "locations" (as in "a location one reads a value from") than this. ### The View The view will look into the context, understand what's supposed to show and use that information to show the corect events. ## Implementing a View Code is worth a thousand words, so below is the code of a really simple (and pretty ugly) 3-days View. The view will show events in a range of 3 days and is meant more as an example in the possibilities of the View architecture than an example in style. ### The View class The first step in implementing a new View is creating the class responsible for managing it. Below the code of the `Three_Day_List_View` class with heavily commented code: ```php add( $two_days ); // Again: let's take the end of day cutoff into acount. $end_date_object = Dates::build_date_object( tribe_end_of_day( $temp_date->format( 'Y-m-d H:i:s' ) ) ); return [ $start_date_object, $end_date_object ]; } /** * Overrides the base View method to set up template variables the way this view will need them. * * This method should contain all the logic required to provide the front-end templates with information. * This is the point of contact between the site back-end and the site front-end; its data is filterable. * Want a logic-less front-end template? Do it here. * We're not filtering the variables here: the main View class will do that for us. * * @return array An associative array containing any value that will be available to the front-end template * at any level. The values we set here will be "global" and available to any template component/part * by using the (extracted) variable by the same name. */ protected function setup_template_vars() { /** * The base View will fill in some common template variables for us like `events`, `today`, `now` * and more. */ $default_template_vars = parent::setup_template_vars(); // If the start date was explicitly set (e.g. from the bar), then use it, else default to today. $start_date = $this->context->get( 'event_date', 'today' ); list( $start_date_object, $end_date_object ) = $this->build_date_objects_from( $start_date ); $template_vars = wp_parse_args( [ 'start_date' => $start_date_object->format( 'Y-m-d' ), 'end_date' => $end_date_object->format( 'Y-m-d' ), 'keyword' => $this->context->get( 'keyword', false ), 'events' => $this->repository->all(), 'events_count' => $this->repository->count(), ], $default_template_vars ); return $template_vars; } /** * Sets up the repository, and/or the repository arguments, that should be used to fetch the events for the View. * * We do not need to filter the arguments here as the main View class will do that for us. * * @param Context|null $context The context of the View request. To the View this is the World. Everything the view * needs to know about its render context... lives in the context. * * @return array The */ protected function setup_repository_args( Context $context = null ) { if ( null === $context ) { // If we're not explicitly provided a context, then let's use the global one. $context = tribe_context(); } /* * The main View class will populate the "usual suspects", repository arguments we need to consider in each View * like keywords, page, posts-per-page. */ $repository_args = parent::setup_repository_args( $context ); $start_date = $context->get( 'event_date', 'today' ); list( $start_date_object, $end_date_object ) = $this->build_date_objects_from( $start_date ); // Any event overlapping the period should be there. $repository_args['date_overlaps'] = [ $start_date_object, $end_date_object ]; // Finally set an ordering criteria. $repository_args['orderby'] = 'event_date'; $repository_args['order'] = 'ASC'; return $repository_args; } } ``` Not **all** Views will be this simple and you might need to override more methods from the base View to make things work as you want. ### Creating the View templates Once the View class is in place it's time to give it a front-end by means of templates. Looking at the View code above the `Three_Day_List_View::get_template_slug` method will return `three_days`. Depending on where you're developing the View the code will look for the following files: * `wp-content/plugins/the-events-calendar/src/views/v2/three_days.php` * `wp-content/plugins/events-pro/src/views/v2/three_days.php` * `wp-content/themes//tribe/events/views/v2/three_days.php` And more locations you'll be able to control with the `tribe_template_path_list` filter. In this example we've created the view files in The Events Calendar plugin, here is the file structure: ``` src/views/v2 ├── three_days │   ├── content │   │   └── event.php │   ├── content.php │   └── title.php └── three_days.php ``` There's really a ton of other files in there (feel free to explore) but, right now, we care only about our Views' files. We've split the front-end rendering code into partials to keep the code tidy and be able to draw a nice ASCII tree. ### Passing information to the View template Ideally (which means you should do anything in your power to make it so, and I believe in you) **frontend templates should be logic-less**. This means that any calculation, interpolation, and "look-around" should be done **in the View class** and provided to the templates ready-to-run. Before diving into the template take a moment to go back to the View class and look at the `setup_template_vars` method: **anything** templates need should come from that method. > Do you want to quicky scaffold a View and put demo content in it? Look no further than the `View::setup_template_vars` method; filter the `tribe_events_views_v2_view__template_vars` and add/modify any data you need. #### Global and local template data When dealing with templates you will come across this distinction, willing and knowing or not, so it's worth taking some time to nail the basics: * any variable set from the `setup_template_vars` method will be **global** and available to any template in the hierarchy, no matter how nested it is in the file structure. * any variable not set there will be **local** and available only to the template receiving it and its children. Confused? Good; time to look at examples. The first template the Views code will load is the `three_days.php` one. ```php template( 'three_days/title' ); ?> template( 'three_days/content' ); ?>

Showing events from to

``` In this template we're using some variables: `$events_count`, `$start_date` and `$end_date`; where do those come from? They are **global** template variables set by the View `setup_template_vars` method. There is no special method, function or operation you need to do to get them. Let's move to the next template, `three_days/content.php`: ```php ``` Hold on a second... `have_posts`? `the_post`? Yes, we're hacking (and then restoring, we're nice people) **the loop** to make the use of the default, known and comforting WordPress loop functions possible. What's in that loop? The events for the View. Behind the scenes we're using the repository, set up in the `setup_repository_args` method, to fetch the events and "inject" them in the loop. For each event, until we have some, we load the `three_days/content/event.php` partial and we pass it an `event` variable. That variable is a `WP_Post` object decorated using the `tribe_get_event` function. That function is like `get_post` on steroids for Events and will provide a wealth of information. Note we're using `get_the_ID`: we're **in the loop**. Finally let's look at the `three_days/content/event.php` template: ```php

ID ) ) ?>

From dates->start->format( 'Y-m-d H:i' ) ) ?> to dates->end->format( 'Y-m-d H:i' ) ) ?>

``` In this template we're using a combination of all we've seen so far: * the `$event` variable is a **local** template variable provided to us from the `three_days/content.php` template * that `$event` is not just a `WP_Post` instance but is *decorated* with a number of additional properties like `dates` that contains the event start and end dates (both UTC and not UTC) objects to allow for ablative method calls * we can call `the_content()` function because we're **in the loop**. ### Adding the View to the registered Views The final step is making sure the View controller/manager/dictator picks up our new View. It's as simple as using a filter (if you're not editin the plugin code directly, which you should do only if you maintain it): ```php add_filter( 'tribe_events_views', function( array $views ){ $views['three_days'] = Three_Day_List_View::class; return $views; } ); ``` That's it, happy coding. P.S. That's not **really** it. There's more stuff to find out about in the new Views architecture, but this is a start.