Jump to content
larrybotha

An almost MVC approach to using templates

Recommended Posts

haha, none taken - we've built quite a specific set of tools, so we don't expect it to be to everyone's liking.

Our generator may be a good starting point for a more generic one.

Share this post


Link to post
Share on other sites
Hi Larry,
 
Thanks for putting this together and sharing!
 
One question:
 
I've added the following files:
- a root page 'tasks',
- a template 'tasks',
- a controller 'tasks_controller'
 
and the url /tasks/ is responding fine, but I was assuming that the url /tasks/action/update/ could be controlled with the following function in the controller:
function page_action(){
  if($this->input->urlSegment1=='update'
    echo 'update function';
  } 
}

Or is it still necessary to add the page /tasks/action/ and so on?

Share this post


Link to post
Share on other sites

Update:

I've found my own solution, but please do tell if this is not the best approach ;)

In core/controller.php after:

$func = 'page_'.f8\Strings::snake_case($this->page->name);
if (!method_exists($this, $func)) {
$func = 'index';
}

add:

$func = 'page_'.f8\Strings::snake_case($this->input->urlSegment1);
if (!method_exists($this, $func)) {
$func = 'index';
}

somewhere on the 86th line so the controller looks at the first 'function'-segment ;)

 

Share this post


Link to post
Share on other sites

Hi Nico,

UrlSegments are something we haven't yet really considered for the framework - purely because we haven't had to use them yet!

You were on the right track, but the name of the function wasn't outputting correctly with your modification - call() allows you to define a function based on a particular path. You'd have to loop through all urlSegments, until you get to an empty one, and then append a string of all the segments to the name of the page in call().

In your controller you would then have:

// tasks_controller.php

...

// handle requests to tasks/action/update
function page_tasks_action_update() {
  return $this->render('some-template', array( 'some_var' => 'some_value' ));
}

...

I've made an update to core/controller.php: https://github.com/fixate/pw-mvc-boilerplate/blob/master/core/controller.php#L80-L97 to handle urlSegments :)

  • Like 1

Share this post


Link to post
Share on other sites

Thanks for the update :) Another thing: I'd like the ability to render a template from the partials-folder (or newly created dialogs-folder) from a controller. This is useful in some cases, eg loading stuff with ajax. Is this possible at the moment or should I edit some things in controller.php and view.php? thx again!

Share this post


Link to post
Share on other sites

Another thing; what to do when I've to retrieve a ajax dialog with some HTML from a view-template. At this point, the api_controller.php is called but this file doesn't allow $this->render like controller.php does.

Share this post


Link to post
Share on other sites

You can render templates from wherever you like - by default render() will look inside views/, but you can set a relative path from there:

// MyController.php

class MyController extends ApplicationController {
  ...

  function index() {
    ...
  
    // use views/dialogs/my_template.html.php
    return $this->render('dialogs/my_template', array(...));
  }
}

will look inside views/dialogs/

Did I understand your question correctly?

----------------------------------------------

WRT api_controller.php, my knowledge of ajax is pretty slim (I didn't write any of the API stuff, so I may be wrong here), but what appears to happen is when a request comes in from your JS, the boilerplate will look at controllers/api/my_controller.php instead of controllers/my_controller.php.

Inside controllers/api/my_controller.php you will extend ApiController, instead of Controller. The call() method in the ApiController class, in contrast to the Controller class', looks for a method that matches an HTTP verb you would need to define in your controller (PUT, PATCH, DELETE, and UPDATE don't seem to be supported - perhaps because ProcessWire only supports GET and POST), and handles it accordingly.

The ApiController class has its own partial() method to generate markup inside controllers. The return value of your method is then handed back to your JS.

So, in a nutshell, you should be able to create a new controller as follows:

<?php
/**
 * MyController - API Controller
 */

class MyController extends ApiController {
  ...
  
  // GET verb specified in AJAX request
  function get() {
    ...

    $html = '';

    // use views/partials/dialogs/my_dialog.html.php
    $html .= $this->partial("dialogs/my_dialog", array(...));

    // return results of request to js
    return array(
      'html' => $html
    );
  }
}

Additionally, there's a function, render_js_data(), in javascript.php which will retrieve the page's path and template as a window object in a script tag for easy access in your javascript. You can simply add it to scripts.html.php:

// scripts.html.php

...

<?= $this->render_js_data() ?>
<?= $this->render_scripts() ?>

and in your js then use:

// main.js

if (this.processWire.template === 'my_template') {
  // do something
}

I hope I've made sense!

Share this post


Link to post
Share on other sites

Hey Larry,

Finally got around to checking this out. Question:

// Index will be executed by all pages using the Home template.// Except if a page-specific method is defined (see below)// This function MUST be defined in your template controller.

How does PW call a page-specific method? ie: foo_bar

Thanks!

Share this post


Link to post
Share on other sites

Hi Mackski!

The page-specific method is being called by the MVC framework, not by PW itself!

Let's say you have a page with a name of /i-really-enjoy-carpeting using a template called 'carpeting'.

Inside carpeting_controller.php you could then have the following:

# carpeting_controller.php.php

class CarpetingController extends ApplicationController {
  function page_i_really_enjoy_carpeting() {
    return $this->render();
  }

  function index() {
    ...
  }
}

What happens before a page is rendered is the call() method inside core/controller.php checks to see if there exists a method matching the snake-cased version of the current page name, prepended by 'page_'. If so, it will use that method instead of index() in the controller, allowing you to set variables and use templates for that page only.

i.e. if the current page's name is i-really-enjoy-carpeting, and a page_i_really_enjoy_carpeting() method exists in the controller, use that method to process logic for that page.

Share this post


Link to post
Share on other sites

I think this is an amazing setup. It was a bit of a steep learning curve for me, it was my first experience programming MVC.

I must say it works sweet, changing layouts is a breeze with the logic in the controllers. I love it and thinks this deserves way more attention!

Share this post


Link to post
Share on other sites

Glad to hear it's worked out nicely for you, @spoetnik! Thanks for opening that issue on Github - if you come across anything else, please open an issue or feel free to send through a pull request :)

Share this post


Link to post
Share on other sites

We've now added cache busting for all assets in our boileplate.

This means that in production, if an asset has been added to a manifest in the assets/ folder, your site will now serve an MD5'd version of the file.

i.e. style.css -> style-18925h5bsdfsdfn.css

No worries about appending query strings or force refreshing to get the latest asset, it's all done for you.

Use of the manifest is optional. If an asset is not being rev'd in the manifest, it will be served with the filename you provide.

e.g.:

<!-- if this asset is not in the manifest, it will be served as assets/img/logo.svg -->
<link rel="logo" type="image/svg" href="http://<?= $config->httpHost . $this->assets('img/logo.svg') ?>"/>

<!-- if this asset IS in the manifest, in production it will be served as assets/css/style-9n183he.css -->
<link rel="stylesheet" type="text/css" href="<?= $this->assets('css/style.css') ?>" >

For an asset to be eligible for rev'ing, it must be accessed through the assets helper in views / templates:

$this->assets('path/to/my/asset.ext')

This is used best in conjunction with some sort of task automation, for which we have a Yeoman generator which generates a project with a Gulpfile that handles all the rev'ing of assets without any additional work for you:

https://github.com/fixate/generator-fixate-pw (and more specifically, the gulpfile: https://github.com/fixate/generator-fixate-pw/blob/master/app/templates/_gulpfile.coffee)

To create the manifest, you can run the following from the command line:

$ gulp build

You're now ready to deploy with your cache busted assets.

Assets that are being rev'd by Gulp are css, js, images, and fonts (even handles rewriting paths in your css).

Happy cache busting!

  • Like 4

Share this post


Link to post
Share on other sites

Looks interesting, Larry Botha! I'm familiar with MVC from playing with some Python frameworks, and I'm getting to the point where tools like Yeoman make sense.

Also, the graphics on your site look wonderful.

Share this post


Link to post
Share on other sites

Hi Larry, thank you loads for going through the efforts of putting this together. I've been experimenting with different ideas of implementing *VC along Processwire again and again, but was never really satisfied. Glancing at your solution it looks like it might be what I was looking for. I'm definitely going to try it out for the next smaller project.

Share this post


Link to post
Share on other sites

thanks @WinnieB :)

Yeoman's great for automating the dirty work and getting right to business.

@boundaryfunctions great to hear it may be of use to you! It's very much based on Rails, and our new manifest feature is reminiscent of Sprockets / Asset Pipeline (but way faster thanks to Node and Gulp). Make sure to check out the wiki on the repo on Github - it's a little rough around the edges, but it should get you going a bit faster.

  • Like 1

Share this post


Link to post
Share on other sites

@Nico Smit our last PW project was released when 3.x was not yet stable, so we haven't yet toyed with the latest stable branch of PW. We'll be starting one or two new PW sites in the coming months, but with our current backlog I'm not sure we'll be getting there any time soon!

In my spare time I'm building a project for our company on PW which will be starting in the next couple weeks, so I'll have to ascertain how quickly I can resolve those issues. If it's not too much trouble (I think it may come down to refactoring how our classes are instantiated / namespaced) I'll update the boilerplate then, but with our deadlines I may be forced to carry on with 2.x until I do get the time.

 

@Nico Smit if you can open an issue, or even make a pull request it would be much appreciated!

  • Like 1

Share this post


Link to post
Share on other sites

@Nico Smit I took a look last night at 3.x with the MVC framework - I ran into a few issues where previously global ProcessWire functions weren't available, such as `wire` and `wireEncodeJSON` in `traits/search.php` and `controllers/javascript.php` (there'll be a couple other files, such as `traits/forms.php`, but I haven't yet scoured all the files), but the issue was remedied by adding the ProcessWire namespace to those files, and using the namespace to access the functions:

<?php
# traits/search.php
use ProcessWire as PW;

...
  
# changed from wire(...) to:
PW\wire(...);

I didn't have any issues with `Error: Class 'Environment' not found`. I did an install using our generator.

UPDATE 06/11/2016: Just released 0.5.9 which adds fixes for the global `wire` calls in the boilerplate: https://github.com/fixate/pw-mvc-boilerplate/releases/tag/0.5.9

Edited by larrybotha
added note about updating the boilerplate repo
  • Like 2

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...