Love Fuel?    Donate

FuelPHP Forums

Ask your question about FuelPHP in the appropriate forum, or help others by answering their questions.
Error when nesting views
  • Hi guys, I'm following method 2 in the docs here. Unfortunately, I'm getting an error that $header, $menu, $content, and $footer aren't set. Here's my code:
       $views = array();   
       $views['header'] = View::forge('header')->render();
       $views['menu'] = View::forge('menu')->render();
       $views['content'] = View::forge('content')->render();
       $views['footer'] = View::forge('footer')->render();
       
       return View::forge('wrapper', $views)->render();
    

    Here's my wrapper view:
       <?php var_dump($header); ?>
       <?php echo $header; ?>
       <?php echo $menu; ?>
       <?php if(!empty($featured)): ?>
       <div id="feature-box">
        <?php echo render('featured'); ?>
       </div>
       <?php endif; ?>
       <?php echo $content; ?>
       <?php echo $footer; ?>
    

    If I var_dump($views); inside of my controller, they are indeed set... any ideas?
  • No, as said, it all works here. If it wouldn't, nobody would be able to pass any variable to a view. Phones would ring of the hook here if that would be the case! You don't have an error in your security output filter (in your config.php), like a non-existent method or function? In that case it will assume it contains a regex, which most likely causes all variables to be empty (note: that is different from "do not exist"!).
  • Here's my security section of my config:
    'security' => array(
      'csrf_autoload'   => false,
      'csrf_token_key'  => 'fuel_csrf_token',
      'csrf_expiration'  => 0,
      'uri_filter'   => array('htmlentities'),
    
      /**
       * This input filter can be any normal PHP function as well as 'xss_clean'
       *
       * WARNING: Using xss_clean will cause a performance hit.  How much is
       * dependant on how much input data there is.
       */
      'input_filter'   => array(),
    
      /**
       * Whether to automatically encode (htmlentities) view data
       */
      'auto_filter_output' => false,
    
      /**
       * With output encoding switched on all objects passed will be converted to strings or
       * throw exceptions unless they are instances of the classes in this array.
       */
      'whitelisted_classes' => array('Fuel\\Core\\View', 'Fuel\\Core\\ViewModel', 'Closure')
     ),
    
  • No output_filter defined, which means it will use the htmlentities() method of the Security class. Can you do a \Debug::dump($__data) in your wrapper view? It should list the array of all variables passed to the view...
  • I think I've figured out my issue - Controller_Wrapper extends Controller_Template (and has since before I wanted to switch to a different style of nested views), so I have to set the variables to $this->header, etc... Previously my view had a bunch of echo render() statements in it calling each of the views individually. What I'm trying to do is allow my base wrapper controller to handle ajax requests. At the same time, I'm changing how variables are assigned throughout the system, because previous I was using set_global, etc. Basically, a browser request to /ajax/path/to/controller does a Request::forge('path/to/controller'); and encodes the response in the right format for the AJAX request. The problem is I don't want to echo the entire template for AJAX requests - I just want to return a partial view as HTML instead of having it inside of the entire template. I'd like to reuse as much code as possible here. Any ideas?
  • I use a 'view root' for that. I have in my views directory folders called 'html', 'mobile', 'ajax', etc. In these folders, I have the same view files, but with different content. In my controller constructor, I determine the output type, and based on that, use the view 'html/this/view', 'mobile/this/view' or 'ajax/this/view'. As they all receive the same variables, you can do what you want with them, including returning them as json. (actually, my ajax views don't output anything, they store data in a central location, which is converted to json output in a shutdown event. This allows me to work with a template/widget system where a page is built up from lots of views, and still return data as json, even if it came from different views)
  • One of the issues I'm faced with is the <title> of the site is actually set by the controller being called. I was calling set_global('title') previously, but since I'm doing the views differently now (by returning the view instead of setting it to $this->template->content) I'm not sure how to set the title of the page. I could use set_global still, but is there another way?
  • I'm also having another issue now. All of my controllers extend Controller_Wrapper. Controller_Wrapper extends Controller_Template. I've setup one of my controllers to just return a View object - and all I see when I load the main page of my site is the View returned by that controller. I expect to see that view wrapped in the rest of the site.
    In my Controller_Wrapper before method:
       parent::before();
       $this->template->header = View::forge('header');
       $this->template->menu = View::forge('menu');
       $this->template->content = Request::forge(Uri::string())->execute();
       $this->template->footer = View::forge('footer');
    

    In my controller's index method:
      $total_articles = Model_Article::find()->count();
      Pagination::set_config(array(
       'pagination_url' => 'admin/articles',
       'per_page' => 10,
       'total_items' => $total_articles,
       'num_links' => 5,
       'uri_segment' => 3
      ));
      $data['articles'] = Model_Article::find('all', array(
       'offset' => Pagination::$offset,
       'limit' => Pagination::$per_page
      ));
      $this->template->title = "Article";
      return View::forge('admin/articles/index', $data);
    

    What am I doing wrong here?
  • If your template is served by Controller_Template, your index should not return the partial. Returning the view will be taken care of by the Controller_Template. You should assign the "admin/articles/index" view to the appropriate section of the template. I'm a bit clueless to why that Request is there, you're fetching the same controller again?
  • Well, like I said before - I'm trying to set this up so I can do both AJAX requests and regular requests with as little code duplication as possible. Ideally, this means at most adding an AJAX check to each controller method to determine how to output the view. I'd prefer *not* having to do this, however. I do see your point about fetching the same controller twice, though ;) My best guess is that I need to stop using Controller_Template, and just handle the wrapper on my own. I *don't* want to have to assign the header, footer, etc in every single method. I'd rather do this in exactly one place across my entire app (for obvious reasons). I don't really need the set_global stuff. To outline what I'd like to do: 1) I'd like to just return partials in each controller, if at all possible. This will make it very easy to implement AJAX. I feel the best way to do this is using HMVC to request the controller when it's an AJAX request.
    2) I need to be able to pass some data from the requested controller to the wrapper controller to assign to the views (since I don't want to have to dupe the view code in a bunch of places). Thoughts?
  • The way I see it: Controller_App extends Controller_Wrapper extends Controller_Template. Wrapper defines the page template using the structure of the template controller, and in the before of the wrapper controller you define the 'static' bits of the template: everything but the template body (the content). When your Controller_App action method is called, it processes the request, creates the view object for the template body, assigns all variables to it, and finally assigns the view object to the template variable. After this all, the after() method of your controller stack is called. Normally, in case of the Controller_Template it will render the template view and return it, so Fuel can send it to the browser. So what you need to do is define your own after() method in Controller_Wrapper. In that method you check if it was an ajax call. If not, call the parent::after(), business as usual. If it was an ajax call, fetch the variables from the body view object using the get() method of the view, construct your json array, and send it out. Do not call the parent::after(), so your template will not be rendered anymore. If you can't make it generic, you can also do this in Controller_App, and have a specific after() method per controller.
  • Thanks, that set me in the right direction. My ajax controller is still performing an HMVC request (for now) and returning the rendered view directly.
  • By the way, I'm getting this error:
    OutOfBoundsException [ Error ]: View variable is not set: content
    

    Here's my code:
    <?php
    class Controller_Ajax extends Controller_Wrapper {
     public function router() {
      $this->is_ajax = true;
      $segments = Uri::segments();
      array_shift($segments); // remove ajax
      $path = implode('/', $segments);
      
      $output['html'] = Request::forge($path)->execute();
      var_dump($output);
     }
    }
    

    Controller_Wrapper:
     public function after($response) {
      // If the request is ajax, we want to return the content
      if(!$this->is_ajax) {
       parent::after($response);
      } else {
       $this->template->get('content');
      }
     }
    

    Controller_Admin_Articles:
      $view = View::forge('admin/articles/index', $data);
      if(Request::main() === Request::active()) {
       $this->template->title = "Article";
      }
      $this->template->content = $view;
    

    Any ideas?
  • Trying to get my head around what you are doing here. Where in this setup is your template? Your main request is to Controller_Ajax, which extends Controller_Wrapper, which extends Controller_Template. So this should be where your template exists. Then you do an HMVC request to Controller_Admin_Articles. Why are you accessing the template there? It doesn't exist there. That controller should also not extend your wrapper, otherwise you will have two templates. The method in Controller_Admin_Articles should return the view object, and then in Controller_Ajax, assign it to the template (so $output should be $this->template->content).
  • Unfortunately I actually need to extend Controller_Wrapper in all of those controllers. Controller_Ajax doesn't need to extend Controller_Wrapper, though, since it's making an HMVC request I can handle errors in the Ajax controllers separately. Basically, Controller_Wrapper handles all the auth. Controller_Admin does it for admin.
  • Hehe, having one last issue - I have to disable profiling when in an HMVC request, but I'm not sure what I need to do to make that happen. I've tried using \Config::set('profiling', false); but it still renders the profiler at the bottom of the screen when I hit the ajax controller... any ideas?
  • You can't enable/disable the profiler dynamically, other then adding some code to the config file itself. The value is read and used long before any user code is executed.
  • Think you may want to look at Controller Template.
    http://fuelphp.com/dev-docs/general/controllers/template.html
  • This is not an issue that can be solved with a template controller. All that will do for you is autoload the 'wrapper' view. Which version of Fuel are you using? If you're going to use render(), know that your array contains string output. So you need to use the third parameter 'false' to make sure your rendered HTML isn't escaped:
    return View::forge('wrapper', $views, false);
    

    It's better just to remove the render() method, and have Fuel render the views automatically when needed. With this change, your code works without problems where, on the latest 1.1/develop.
  • I'm on Fuel 1.1 right now :D I removed the calls to ->render() and I'm still getting the same error. Any ideas?

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

In this Discussion