Passing data from block controller to page template?

Permalink 5 users found helpful
I have a block with a form on it, and I'd like to display validation errors and messages elsewhere on the page (other than within the block's area). Is there a way to get the overall page view and call set() on it? Or is there another way to achieve what I'm trying to do (like a global "set message" or "flash" (as it's referred to by Rails)?

Thanks!

-Jordan

jordanlev
 
jordanlev replied on at Permalink Reply
jordanlev
In case anyone is looking for this in the future: it seems that there is no way to call "set" on the overall page controller from within a block's action method (when responding to a form) because the block's action method is called before the rest of the page is built.

And there doesn't seem to be any built-in "global messaging" system (although I think there should be -- then people writing blocks could send error messages somewhere that they know will get outputted regardless of the template -- but I digress).

The best way I found to achieve this was to use a global variable. In your block action method where you would normally call this:
$this->set('validationErrors', $errors);

call this instead:
$GLOBALS['validationErrors'] = $errors;

and then on the page template, do this:
<?php if (isset($GLOBALS['validationErrors'])) {
    echo 'The following problems were found:<ul>';
    foreach($GLOBALS['validationErrors'] as $error) {
        echo '<li>'.$error.'</li>'
    }
    echo '</ul>';
} ?>


(note that in this example I used a simple array of errors to make the explanation clearer, but in practice you should use the validation error helper)
TheRealSean replied on at Permalink Reply
TheRealSean
is there a better way to go about this now?,

I thought I would be able to use $this->set() now but it still does not seem to work within a block contoller?

Maybe I need to load in some helper/element??

or is the $GLOBALS method still the best way to do this.
jordanlev replied on at Permalink Reply
jordanlev
This is the only way I know of. Calling $this->set() from a block controller will only set the variable in that block's view (or add/edit, depending on which method you called it from). This is for good reason, too -- otherwise different blocks would overwrite each others' data all the time.

You can use an alement and put the $GLOBALS['whatever'] thing in there, then load that element in your templates (I do this for my Email List Signup addon), but I don't think there's a way to avoid using $GLOBALS. Is there some reason you don't want to use that? I imagine that even if there were a built-in c5 method of doing this, it would need to use the globals under the hood anyway.
TheRealSean replied on at Permalink Reply
TheRealSean
That's the route I have taken, I was looking for alternative way as I know know the old method to call $c was to use global call on it before the static class method was settled on.

I was just wandering if something similar had happened with set.

I'm attempting to call it from a template so the $this->set of course does not work

I'm not against the method though. Just wanted to see if a different method had been made +1 year
JohntheFish replied on at Permalink Reply
JohntheFish
My idea for messages to display (but not data needed within other php) would be to set the data in the block controller, write it to a hidden element in the view, then use jQuery to move the element where I wanted on the page and show it.

Th jQuery could be in the block, to push it elsewhere, or in another block or on the template, to pull all such info from many blocks into a single place.

This is completely untested, and speculative, but similar enough to other jQuery tricks that I am confident I could make it work.
jordanlev replied on at Permalink Reply
jordanlev
The potential problem with this issue of course is it requires javascript to be enabled on the browser. Which may or may not matter for any particular project, but obviously needs to be considered in case you want the site to work even without JS.
beebs93 replied on at Permalink Reply
beebs93
Hmm, what if in the block view.php:
<?php
if(is_array($arrErrors) && count($arrErrors) > 0){
   $this->controller->setErrors($arrErrors);
}else{
   // display as normal
}
?>


Then, in the block controller.php:
<?php
protected $errors = array();
public function setErrors($arr){
   $this->errors = is_array($arr) ? $arr : array();
}
public function getErrors(){
   return is_array($this->errors) && count($this->errors) > 0) $this->errors ? FALSE;
}
?>


To the template!
<?php
// NOTE: This would be while not in edit mode otherwise you couldn't edit the area's blocks
$area = new Area('Main content');
$arrBlocks = $area->getAreaBlocksArray($c);
foreach($arrBlocks as $b){
   if($b->getBlockTypeHandle() == 'my_block_handle'){   
      ob_start();
      $b->display('view', $args = array());
      $htmContent = ob_get_clean();
      $bc = $b->getController();
      if($arrErrors = $bc->getErrors()){
         // Iterate through the errors
      }else{
         // No errors so good to go
         echo $htmContent;


I've never tried it, but first thing that came to my head. Granted, a ton of work to avoid using $GLOBALS, but I'm curious if this would work.
Shotster replied on at Permalink Reply
Shotster
I was intrigued by this problem, so I did some tinkering and came up with the following...

If you hook into the on_before_render event, you can pass variables from the action method of a form's controller through to the page view. So, using the standard form block as an example...

In the action_submit_form() method of the form controller around line 250...

Events::extend(
   'on_before_render',
   'ValidationMessages',
   'setValidationMessages',
   'models/validation_messages.php',
   array( $errors )
);

Of course, you have to implement the handler as described here...


http://www.concrete5.org/documentation/developers/system/events...

(By the way, the docs are wrong. What's passed to the event handler in the 1st arg is a View object - not a Page object.)

So, in my validation_messages.php...

class ValidationMessages {
   public function setValidationMessages( $v, $msgs ) {
      $v->controller->set('validationMessages',$msgs);
   }
}

Then in the page's view template (in my test, right_sidebar.php of the default theme)...

extract($this->controller->getSets());

Then, access the variables anywhere later in the template file...

if (isset($validationMessages)) {
   foreach ($validationMessages as $validationMessage) {
      echo '<p>'.$validationMessage.'</p>';
   }
}

This is surely more resource intensive than using a global variable, but it does work and seems to offer more power and flexibility.


-Steve
Shotster replied on at Permalink Reply
Shotster
Just a couple quick follow-up remarks...

Checking to see if there are any errors before calling Events::extend() would avoid the overhead of invoking the handler if there's no need.

Secondly, I find the "extend" method name a bit misleading. What you're actually doing is declaring an event handler, so something like Events::handle() would make more sense to me.

-Steve
Shotster replied on at Permalink Reply
Shotster
> I find the "extend" method name a bit misleading. What you're actually doing is
> declaring an event handler, so something like Events::handle() would make more
> sense to me.

Upon closer inspection of the code, I have to modify my statement above. It seems the extend() method both registers an event AND specifies a handler, so "extend" is probably a better name after all. I was thinking there was a separate method for registering an event, but that's not the case.

-Steve
jordanlev replied on at Permalink Reply
jordanlev
@beebs93 and @shotster -- you guys both came up with very clever solutions! This is a fun topic and a good mental exercise, but in case someone stumbles on this thread in the future I want to state for the record that I would still use the $GLOBALS approach for a real site, because it is so much simpler.

If you're scared of globals because they're "evil", I would suggest that they're not intrinsically bad, but rather they are often abused and that's what's bad (because it makes the code harder to understand). But in this case, the globals solution makes the code easiest to understand, so it is preferable in my opinion.

-Jordan
beebs93 replied on at Permalink Reply
beebs93
Agreed - KISS still applies.

...fun to think about, though :D
Shotster replied on at Permalink Reply
Shotster
Yeah, no doubt. Simplicity rules. Having just delved into events myself, I was wanting to test my understanding more than anything else.

That said, if one needed to do some sophisticated processing, massaging of data, or other raz-a-ma-taz that didn't make sense to perform in a block's controller, using an event handler to "intercept" and work with the data might make sense.

I think I'll be relying on events for certain things now that I understand them a bit better.

-Steve
jordanlev replied on at Permalink Reply
jordanlev
Oh yeah, absolutely. I often use both techniques that your code demonstrates (looping through an area's blocks to call block controller methods, and using events), so it's super useful to go through this stuff and experiment with it. These are my favorite kinds of forum threads :)
TheRealSean replied on at Permalink Reply
TheRealSean
+1 on that its threads like these that make this forum a joy to read.

Many good ideas, and a different view helps you look at things from a different perspective.

I have certainly learnt a few things from this thread too.

I don't play with the events stuff as much as I think I should but might give it a go for my next project.
JohntheFish replied on at Permalink Reply
JohntheFish
Sticking on the server this time, and thinking along the lines of cleaner globals, how about a pigeon-hole class - a bit like a named queue, but designed to be read all at once. An object encapsulating some shared data (I am not as familiar with php as I am with other languages, so I may be mashing up some terminology).

Write errors to the 'error' pigeon-hole from any controller anywhere. Then any page or block view can check the 'error' pigeon-hole for messages in it. Use the same class for other pigeon-holes, like 'debug' messages during development.

Could there be a case for a pigeon-hole helper?