A new approach to "global areas" (better than Page Defaults)

Permalink 5 users found helpful
***For the most-recent download, click here:http://www.concrete5.org/community/forums/usage/a-new-approach-to-a... ***

I often run into a problem where I (or a client) is setting up a site and making changes to a common area that exists across many pages, such as the sidebar. The three existing solutions to this problem in concrete5 seem to be:
1) Build it in to the page type template
2) Use the global scrapbook
3) Use Page Defaults

Number 1 is out if you want the client to be able to change this content. Number 2 only applies to changing content of a block that's already on every page, not adding new content (or rearranging) to every page. Number 3 (page defaults), *should* be the best solution to this, but while it's great for setting up the defaults for NEW pages, it doesn't work so well when used as the central location for global changes across existing pages (via the "Setup On Child Pages" command), for several reasons:

* Changing the positions of blocks does not get applied to existing pages.
* You cannot set the "design" of blocks from Edit Mode like on a normal page.
* You cannot "add layouts" like on a normal page.
* Accessing the "Page Defaults" is kind of confusing for non-technical site owners, because it's out of the way (I thought the whole point of C5 was to avoid going to the dashboard?!), it opens up in a new window which confuses the hell out of newbies, it has an "Add Page" button in the toolbar which makes no sense whatsoever, AND you have to be the super-user admin to even access it in the first place, which makes it completely unusable in a situation where you have a non-admin managing site content.

As I thought about this problem, I realized that what is needed is some way for the site developer to designate certain AREAS in the page type templates as "global areas" for those situations when you know that its content will be the same across all pages of that type. Then when the site owner is building out the site, they simply make changes to this global area from the place it exists in the site (so that it operates the same way as the rest of C5's in-context editing), and those changes are automatically applied across all pages having that "global area" in their template. I have found that this is how most of my clients think about editing their site anyway -- they see a sidebar, want to make a change to it, and expect that it will be changed everywhere because they know that a sidebar is common to the whole site. In other words, I encounter this situation: "I changed the sidebar but it didn't change anywhere else on the site -- do I really have to change it on EVERY page??" MUCH more often than I encounter this one: "I wanted to change a box on the sidebar only on the 'About Us' page, but not on every other page in the site". Yes, the second situation does occur, but the first situation is MUCH more common in my experience. (And even with the "global sidebar", you can work around the second situation just by changing the page type for that one page).

Anyway, I wrote some code that can be used to achieve this. Just download the attached file, unzip it, and drop the "global_area.php" file into your site's "models" directory (NOT "concrete/models"). Now, in your theme's page type templates, instead of doing this:
<?php
$a = new Area('Sidebar');
$a->display($c);
?>

...do this:
<?php
Loader::model('global_area');
$a = new GlobalArea('Sidebar');
$a->display($c);
?>

Now your home page serves as the "template" for the sidebar -- any changes made to the sidebar on the home page will automatically appear on ALL pages in the site that have the same page type (or any other page types that you put this code in) -- INCLUDING block positioning, block designs, and layouts!

If you'd rather use a different page for the "template" (for example, maybe you want to create a special hidden page that serves as the new "page default" for the sidebar), simply pass in a page path as the second argument after the area name, like this:
<?php
Loader::model('global_area');
$a = new GlobalArea('Sidebar', 'sidebar-template');
$a->display($c);
?>

Now you can create a new page, exclude it from nav and search, give it the page alias "sidebar-template", and tell your clients to go to that page to make any sidebar changes.

While this is a big improvement over the existing options (for certain situations), it isn't perfect...

PROBLEMS:
1) While it displays block and area "designs" and layout cell spacing, it does NOT output area designs that are INSIDE layouts. This was simply too complicated for me to hack around. If anyone has any ideas on how to achieve this, that would be great (the problem is that styles are outputted through the collection object, not the area object -- not sure why this is, but it makes it very difficult to access certain things when you're not on the page/collection that the area was originally added to).

2) It doesn't work with blocks that rely on the "current page's location" -- for example, if you use the breadcrumb custom template on the autonav block, it will show the breadcrumb to the global area's "template" page, not the page actually being viewed. Similarly, if you had an autonav or page list block that was set to show "child pages", it would show the child pages of the global area's "template" page, not child pages of the page actually being viewed.

3) If you enter "Edit Mode" from a page that isn't the "area template", then the area cannot be edited. This is partially a good thing, because it keeps the page versioning nice and clean (for example, if the sidebar really lives on the home page, but you changed it from the About Us page, would that change be reflected in the home page's version or the About Us page's version?). But it potentially makes things more confusing for non-technical site maintainers.

EDIT: 4) As Shotster points out below, if you have custom block/area/layout designs, they will be outputted in the <body> html, not in the <head> -- this gets applied and rendered just fine in most browsers, but it is *not* valid HTML (both XHTML and HTML 4.01) -- so if you care about having valid markup, this will not work for you.

----

If you think this might be useful to you, please try it out and let me know what you think. Thanks!

-Jordan Lev

EDIT: There is an updated file which solves the problem of block header items (js and css added by the "addHeaderItem()" function) not getting outputted -- see my followup comment dated "Aug 07, 2010 at 8:35 PM" for the new attachment.
EDIT 2: There is another updated file which solves another problem (custom style rules not getting outputted because they're added to the middle of the header items array, not at the end) -- see my followup comment dated "Aug 10, 2010 at 3:09 AM" for the new attachment.
EDIT 3: Another updated file which solves problems with getTotalBlocksInArea()

jordanlev
 
okhayat replied on at Permalink Reply
okhayat
This is really a great idea. I'll try it out and post my feedback ASAP.
Mnkras replied on at Permalink Reply
Mnkras
cool, ill try this out tomorrow ;)

nice job
andrew replied on at Permalink Reply
andrew
I like this idea, too, Jordan. I like the API too (as it uses its own new model to accomplish the goal, rather than making the existing Area() API more complicated.)

I have struggled with this, particular with the header nav. For example, when setting up pages on a site and wanting to swap out the header auto-nav for superfish, it gets incredibly complicated and irritating when using an add-on like discussion forums or eCommerce, because eCommerce has lots of pages that you can't even really edit directly (because they only appear at certain points in the checkout process.) Furthermore, since they're single pages they aren't even accessible through page defaults.

We will give this some thought. It seems like lately we've been adding lots of complexity around the sharing of content across different parts of the site (with the scrapbooks, which are more recent than page defaults, etc...) without totally cracking the problem in the perfect way. I think this would get closer, but I know we're also loathe to add things that increase complexity without real benefit. I do see real benefit here though, so we will give some definite thought and consideration.

Question: in your solution, if you opened a theme's header file and made the Header Nav area a GlobalArea() - would this work as I explained it? Since that header include is used everywhere in the theme, would it effectively share the header nav across every page in the whole site? So I could swap out the auto-nav for superfish and then all subsequent new pages created by add-ons, etc... would use my global area?
andrew replied on at Permalink Reply
andrew
Ok, I think I hit my first snag. I integrated your code and it's all working fine functionally, except for custom header items.

Basically, when a page is viewed, all blocks are retrieved in concrete/libraries/view.php, and their auto header items are output into the header. For superfish this means superfish.js, some styles, etc... Since there's no block record against sub-pages (since the block record is bound to the home page) these aren't being output here:

if ($view instanceof Page) {
   $blocks = $view->getBlocks();
   foreach($blocks as $b1) {
      $btc = $b1->getInstance();
      // now we inject any custom template CSS and JavaScript into the header
      if('Controller' != get_class($btc)){
         $btc->outputAutoHeaderItems();
      }
      $btc->runTask('on_page_view', array($view));
   }
   // now, we output all the custom style records for the design tab in blocks/areas on the page
   $c = $this->getCollectionObject();
   $view->outputCustomStyleHeaderItems();             
}


Thoughts?
jordanlev replied on at Permalink Reply
jordanlev
Hmm... I didn't think about blocks' addHeaderItems -- this is definitely a big problem. It's the same issue I had to work around to get the custom block designs to work. See the "output_custom_styles()" function in GlobalArea class -- I add an item to the header (indirectly, via the call to "$c->outputCustomStyleHeaderItems()"), then manually extract that item from the array that's returned by "View::getInstance()->getHeaderItems()".

So maybe this hack could be extended to the blocks' "addHeaderItems"? Not sure how exactly these would be identified, though -- it's easy for the custom designs because you know they are the last item in the array (because you just added them in the prior line of code), but is there a way to identify the block that an item was added from, and furthermore is there a way to identify the area that each block is in (so you can determine if it's the area you're currently working on) -- if so, then the header items can just be outputted manually by this code -- they would appear inline in the html body (as opposed to in the <head>), but that usually doesn't matter.

The alternative of course is to change the Concrete5 core code so it somehow holds off on outputting header items until after all blocks have been outputted. This sounds like a pretty significant overhaul, though. Maybe there could be a "second pass" that outputs all header items in the Loader::element('footer_required') call at the bottom of the page? -- this second pass would need to identify all of the items in the HeaderItems array that were NOT outputted in the <head>.

Just brainstorming here... not sure there's actually a clean and easy solution -- I mean, if you can't think of one I'm not sure anyone else will either :)

-Jordan
jordanlev replied on at Permalink Reply
jordanlev
Ok. I think I have a solution to this. It's super hacky, but should work.

If I duplicate that functionality you pointed out in the concrete/libraries/view.php file, I should be able to get a count of the HeaderItems array, then loop through all the blocks in the current "global area" and add their HeaderItems, then compare the new count of the HeaderItems array, then pop that number of items off the HeaderItems array and output them manually, that should do the trick.
Again, it will mean that those items are inline in the body, not in the <head>, but hopefully this doesn't matter (99% of the time, anyway).

I'll try this out and let you know if it works or not.

-Jordan
jordanlev replied on at Permalink Reply
jordanlev
Okay, here is an updated version that outputs javascript and css added by the area's blocks (via "addHeaderItem()").

Note that it outputs these inline in <body> of the html (next to the block's view html), not in the <head> of the page -- but this shouldn't be a problem most of the time.

-Jordan

UPDATE: Well, it turns out this doesn't always work. The method I'm using to determine which new header items were added assumed that header items were always added to the end of the array, but this is not the case. There appear to be 3 groups of header items -- CORE, VIEW, and CONTROLLER, and if you add a "VIEW" item (such as a custom style), it gets inserted into the array before a "CONTROLLER" item (such as a javascript file). Back to the drawing board...

UPDATE 2: I think I've addressed this problem. It's such a hack, but it works. See my comment below dated "Aug 10, 2010 at 3:09 AM" for the new attachment.
Shotster replied on at Permalink Reply
Shotster
Hi Jordan,

> Note that it outputs these inline in <body> of the html
> (next to the block's view html), not in the <head> of the
> page -- but this shouldn't be a problem most of the time.

Except that, unlike JS, CSS is not allowed anywhere but the doc head (despite what you might see elsewhere in C5). The mark-up will not pass W3C validation. I've brought this up before (more than once) because various parts of the C5 core do the same thing. I personally would not use a solution that results in invalid mark-up. (I've overridden a number of core blocks to use view.css instead of injecting the CSS inline into the body of the document.)

That said, I wish to thank you for suggesting a new direction/approach to this problem. C5 could certainly benefit from some UI and usability enhancements, and you've tackled one of them.

Thanks for your efforts,

-Steve
jordanlev replied on at Permalink Reply
jordanlev
Well I'll be...
I had no idea this wasn't valid!

If it's not obvious, I never check my code against a validator (only how it renders across browsers). And I don't want to get into a debate here about right vs. pragmatic -- suffice to say that this will be a problem for some people and not for others.

Unfortunately there is no way around this without changing the core code. Somehow the system needs to hold off on outputting the header items until all page type areas have been loaded. Or... maybe there's somewhere I can put additional code that checks all this before the header items are outputted by the system? Hmm...

UPDATE: No dice -- I have no clue how to find out what blocks / areas are on a page before the actual "Area" object is instantiated in the page type template. I also tried messing around with altering the output buffer to see if I could inject the style declarations up into the <head>, but that didn't work either (seems that the output buffer is empty by the time the Area object is instantiated).

Andrew, any ideas on how to approach this?
agedman replied on at Permalink Reply
agedman
@Jordan: I've been doing some testing with your block and I agree with you on the need for something like this. What I'm about to post may be totally off the wall -- my understanding of C5 is still pretty weak -- but I was wondering whether it might be possible to modify conrete/libraries/view.php so that in addition to outputting AutoHeaderItems and CustomStyleHeaderItems for the blocks on the current page, it could output those items for any blocks in GlobalAreas as well.

But the problem with that is that a View or Page object has no knowledge of the Areas it contains -- right? It just knows about its blocks and where to find the template file so it can include it. But what if C5 could maintain current information in the database about the GlobalAreas (and other Areas) for each page type? Then the core code in View->render() could simply check for any GlobalAreas in the page type of the page being rendered, get the master page for them (e.g. Home or template_path) and output their AutoHeaderItems and CustomStyleHeaderItems at the regular place in the rendering process.

It seems to me that we just need a tool that can search the template files (for each theme / page type) for the keywords 'Area' and 'GlobalArea', grabbing their associated arHandles and (in the case of GlobalAreas) optional template_paths and storing this info in a table called something like 'TemplateAreas'. The search would have to recursively check any included files (i.e. elements) to make sure it found all the areas. I'm picturing the table with columns for ctID, arHandle, arIsGlobal, arTemplatePath. We would only need to run this tool when a new theme was added or template files were modified (i.e. Areas or GlobalAreas added or removed) so I don't see why it would have to be a drag on performance.

I also think this could be a helpful diagnostic tool. For example, suppose I add some blocks to an area with arHandle 'Sidebar', then rename this area to 'wibble' in my template. When the page renders, the blocks all disappear because they still reference arHandle 'Sidebar'. It might be nice if C5 had the ability to detect this sort of problem and give an error message like this: "You have blocks on this page which reference an area 'Sidebar' that does not exist in the page's template"
jordanlev replied on at Permalink Reply
jordanlev
I was trying to avoid any changes to core code.
And reading files sounds like a complex and brittle solution, because now you have to build a parser or something -- and what if they forgot a closing semicolon or ?> tag, which may or may not be valid php but might trip up the area-reading-thing. Also, when building out themes, it is necessary to be able to change the areas around while you're coding, so having to "re-scan" the templates each time might be cumbersome.

But on the other hand, there's probably no way to do this programmatically, because while blocks are added via the admin UI (so the system has a chance to know what was added), areas are only added in the template code and are checked at render time. I dunno, maybe the system could check the area handle every time a block is added or removed (or every time a layout is added or removed, or every time an area "design" is added or removed)? Then it would have its list of areas and could do something with that.

But of course this too sounds like a very complicated change, and maybe not worth implementing for just 1 feature like this (I dunno... depends on how useful this is and how much people care about valid HTML).

-Jordan
jordanlev replied on at Permalink Reply
jordanlev
Attaching a new version that addresses the problem with custom style rules getting inserted in the middle of the header items array (see "UPDATE" at the bottom of my comment dated "August 07, 2010 at 8:35 PM").

This is such a hack it's not even funny (but it totally works as long as you don't have layout area styles and don't care about invalid HTML and don't have any blocks that rely on the current page's location such as breadcrumb navigation)!
sccomm replied on at Permalink Reply
I am using this latest version, and I am running into an annoying issue. getTotalBlocksInArea does not seem to be working properly on other pages that are not the home page. This is what I have...

<?php $a1 = new Area('Upper-Leading-Content');
            if ($a1->getTotalBlocksInArea($c) > 0 || $c->isEditMode()) { ?>
               <div class="centered">
                  <div class="extra-block">
                  <?=$a1->display($c);?>
                  </div>
               </div>
         <?php } ?>
         <?php Loader::model('global_area');$a2 = new GlobalArea('Global-Leading-Content');
            if ($a2->getTotalBlocksInArea($c) > 0 || $c->isEditMode()) { ?>
               <div class="centered">
                  <div class="extra-block">
                  <?=$a2->display($c);?>
                  </div>
               </div>


It just will not show up on the other pages. Help!
jordanlev replied on at Permalink Reply 1 Attachment
jordanlev
Ah, yes I see -- you're passing in the "$c" for the page being displayed, not the template page that the global area actually exists on, so it's giving you the count from the wrong page.

I'm attaching an updated version of the file that should fix this issue (I tested it out and it seemed to work, but let me know if it works for you or not).

Thanks.

-Jordan
maartenfb replied on at Permalink Reply
maartenfb
This is a VERY interesting thread.
I'm still new to Concrete5 (operating on user level) but having a global area was one of the first things I was looking for.
Hope this idea will find it's way into the core.

offTopic: What a brilliant piece of software Concrete5 is compared to Drupal and Joomla.
Astonishing how people just accept such messy interfaces.
TheRealSean replied on at Permalink Reply
TheRealSean
I'm about to have a play with this, is this the most current version? and do you think it will have any issues with 5.4.2?
jordanlev replied on at Permalink Reply
jordanlev
It's been a while since I've played with this -- nowadays I use the free "Global Area" addon for this purpose instead. It is a bit more robust than my code, and seems to handle various situations a little better. It's also a little more flexible in terms of how it can be added to your site -- what I do is designate one page on the site to be the "source" of the global area content. Then I add the global area block to the Page Defaults, and point it to that "source" page. Now every new page will pick up content from the "source" page, but if for some reason you want to override that one page you can just delete the global area block from a particular page.

I think actually this code and the global areas will work better in 5.4.2 because I got Andrew to change how the autonav block determines "current page" to use the current page that the user is viewing, not the page that the block was added to -- so this fixes one of the two big outstanding problems with this approach. The other problem is with forms, which can be solved using the free "Ajax Form" addon. (If you have custom forms well then that is still going to be a problem you will want to address somehow).

One last thing, I think I saw that Andrew was working on trying to get some kind of Global Area functionality like this into 5.5. But I don't have any official info on this, I just vaguely recall seeing some git commit messages mentioning this. No idea how far along it is or if it will ever see the light of day or anything else -- but you might want to ask him about it.

-Jordan
TheRealSean replied on at Permalink Reply
TheRealSean
I did take a look for that global area first but the market places search is sketchy at best, I found it a little while after, though.

Thanks for the tips Ill take a look at the dev version too might be worth having a poke around in there. To see how the core team plan on implementing something like this?.

Regards
Sean
robic replied on at Permalink Reply
robic
hey , i've tried this on my local concrete5 system but i have problem with using page types and global_area.
when i used those together im getting wrong page content when navigating by top nav menu ( system defaul block) . im not have any idea why this just happened . any idea or solutions

thanks
jordanlev replied on at Permalink Reply
jordanlev
Unfortunately this global area thing doesn't work in every situation (that's why I gave up working on it -- it would just never be able to work flawlessly).
The biggest problem it has is that whatever blocks are in the area are considered to be on the "template" page, not any other page. So if you have an auto-nav block that is set to show "pages under this page", it's going to always show pages under the template page, NOT pages under the one you're currently viewing.

Can you explain in more detail what the exact problem is? What are all of the settings you have for the auto-nav block? And which page is it broken on? What does it show on the broken page?

Thanks.

-Jordan
robic replied on at Permalink Reply
robic
yeah sure.

my auto navigation block had default setting with costume template.

I've examined same situation in my testing server ( actually dedicated server ) and its works properly.

in my local installation of concrete5 i have an extra content than testing server installation. that's a contact form.

but even in local installation when i was in editing mode and logged in , every thing was fine , but when i logged out this problem happened just when navigating between pages.

i think i lost one thing , that problem just happens randomly with random issues. some times wrong content shown and some other times contents right but the nav menu's current page don't changes.
when i click contact menu , page content for contact displays but nav menu just still in "order" menu i clicked previously.

and last thing , my local concrete5 installed on apache / zend server / and windows xp .
jordanlev replied on at Permalink Reply
jordanlev
I never saw this comment when originally posted... but for the sake of posterity: unfortunately you can't use the autonav block with this thing -- it will show pages relative to the template, not the page it's actually displaying on.

This only really works for straightforward content blocks like "Content", "Image", "HTML".
zest replied on at Permalink Reply
zest
Hey guys.. would love to give this a try to assist with resolving a problem I need to fix, but where do i see the download link? I fear I'm just having a boy's look and its right in front of me, but cant spot it! lol Your help would be greatly appreciated :) Thanks for your great work jordanlev :)
jordanlev replied on at Permalink Reply
jordanlev
http://www.concrete5.org/community/forums/usage/a-new-approach-to-andquotglobal-areasandquot-better-than-page-de/#74799

What's the problem you're trying to resolve? I hit a wall and realized it's not possible to make this work completely. I do have it running on a couple of sites but the site owners have been warned that it is only good for plain old content (like the content and image blocks) -- not for autonav, forms, surveys etc.

But if there's some other issue, let me know and I can try to help you figure it out.

-Jordan
zest replied on at Permalink Reply
zest
Thanks for the lightening fast reply :) Ok I'm using the Core Commerce addon and what i want to do is create a product category list in the left sidebar (using the Easy Accordian addon). In order to do this i need to create a layout, which is fine, but i want that layout automatically carried to every other page of the same type. Of course you cant add layouts to the page default so I'm hoping this will be able to solve this for me? Or is that outside the functionality provided by your work here? I'm hoping not! :) Thanks again :)
jordanlev replied on at Permalink Reply
jordanlev
Well, can't hurt to try it :)

The product list may not work when the user tries to add an item to their cart (not sure though, depends on how they've implemented that functionality). And the accordion may or may not work correctly (again, depending on how they've built it).

Let me know how it goes, though.
zest replied on at Permalink Reply
zest
Ok, after some initial excitement at seeing the changes flow out across the site.. i added my layout and accordian block... sadly it ignores this and only shows the content block above it in other pages of the same type.. ahh well! so close! :)
kirkroberts replied on at Permalink Reply
kirkroberts
I've been thinking about how to have re-usable sidebars for use in different sections of a site. The idea I had was just to create a block that displays an entire scrapbook. So the user can add/remove/edit/rearrange blocks in that scrapbook and those changes will be automatically reflected on any page that shows it.

Not as slick as the solution you were proposing, but along the same lines and may be helpful to others.

I'm trying to get a package together to submit as a free add-on in the marketplace. Just need to iron out one more detail with potential recursion (where someone might put a scrapbook block into the same scrapbook it shows... and thus tear the fabric of space-time).
jordanlev replied on at Permalink Reply
jordanlev
Sounds like a great solution, Kirk -- can't wait to see it!

I'm almost finished with a useful new addon myself. It's a custom content block generator, so when you're building sites for clients and you have specific chunks of content on a certain page (like a heading with an image and text wrapped around it underneath), you can just tell them to add that block and all the right fields are there, and you can customize the output to use the proper divs and h1/h2/etc. tags where needed -- lets you avoid a lot of hassle with TinyMCE styles.

My 2 biggest problems with Concrete5 are the TinyMCE styles and the lack of default areas -- between the two of these solutions though, I think we'll be drastically improving the usability of the system for designers/developers who need to build sites for other people!

Cheers,
Jordan
kirkroberts replied on at Permalink Reply
kirkroberts
I've submitted the Scrapbook Display - Basic block to the marketplace. Of course I immediately thought of and implemented a few stability improvements but don't know how to update my submission :-)

Image cropping was a HUGE one for me. The custom block creator is going to make my life easier (honestly, you might consider a one-time fee for that one... although that might open up more support requests :-). You've created a bunch of utilities that make so much darn sense (eg hide child pages from site map, force single sub-level).

TinyMCE wrangling is another HUGE one. I'm hoping to get together a how-to on how to add image captioning into the Content block.