Need advice/help/input on how to build this

Permalink
See the attached image for the design I am trying to build. It's a "Press Room" page, displaying a tabular list of a bunch of different "Press" items. There will be three different types of items that could be added to the list:

(1) A video - most likely on YouTube
(2) An offsite link (to a news item on another website)
(3) A PDF - a Press Release document created by the client

I'm thinking I would use a Page_List block on the Press Room page... and use it to pull in the relevant content from it's child pages. But what's the best way to deal with the fact that the "children" could be different? For instance: for a video, I want to display the camera icon on the listing, and it would need to grab the URL of the YouTube video in order link to it. But a PDF press release would be stored in the File Manager and I'd want to link there. While all three different types of "children" would need to have "Title", "Source" and "Date" attributes to fill out the tabular display.

So how would YOU do this?

(1) Page List pulling in child pages that have different page types?

(2) Page List pulling in a single type of child page that has different block types put into them?

(3) Skip the Page List and just create 3 block types that get added to the Press Room page?

(4) Some brilliant, über-simple idea I haven't even thought of (I LOVE those, LOL!)

Thanks!
- John

1 Attachment

arrestingdevelopment
 
jordanlev replied on at Permalink Reply
jordanlev
I've done this before. Not sure if it's the best way, but my approach was to create a single_page for the top-level list, then create one page type for the sub-items and then 3 different custom blocks (one for each type of content). To add a new item, users would add a new page of that special page type and then choose one of the 3 custom block types and add that to the page. Each of the 3 custom blocktypes also had a public controller function that could be called to retrieve its content programmatically (which is demonstrated in the code below).
The single_page used the PageList API to retrieve all pages of that special page type, then for each of those pages I pulled out the first block who's blocktype handle started with a special prefix I used for the 3 custom blocktypes -- like this:
Loader::model('page_list');
$pl = new PageList();
$pl->sortBy('cvDatePublic', 'desc'); //$pl->sortByPublicDate() is only ascending, and there's no sortByPublicDateDescending() function [NOTE: THERE MAY BE A sortByPublicDateDescending() FUNCTION AS OF 5.5?]
$pl->filterByCollectionTypeHandle('press_room_item'); //Your custom page type handle
$pages = $pl->get();
$nh = Loader::helper('navigation');
$items = array();
$btHandlePrefix = 'my_custom_blocktype_handle_prefix_'; //All 3 of your custom block types must have handles starting with the same string
foreach ($pages as $page) {
   $blocks = $page->getBlocks('Main');
   $content = '';
   foreach ($blocks as $block) {
      if (substr($block->btHandle, 0, strlen($btHandlePrefix)) == $btHandlePrefix) {
         $bi = $block->getInstance();
         $content = $bi->getSpecialContent(); //This is a special function you must create in each of your custom blocktypes


The advantages to this approach is that you can automatically tie in to search results (although you might not want people to ever see the individual item pages, in which case this point is moot). Also in my case I wanted to categorize the content so I created a custom attribute that could be assigned to each page that would put it into the appropriate category (I didn't include that code in my sample above because it would over-complicate things). Also also it provides a unified interface for the end-user -- they already know how to add pages and set page titles and choose custom attributes. The downside is that it's a bit on the complicated side in terms of code. Might be easier to just have user add blocks to the one "press room" page (although you'll need to figure out how to reverse-sort it and handle pagination).

I don't think there's an uber-simple way to do this (although if someone thinks of one I'd love to hear it as well).
arrestingdevelopment replied on at Permalink Reply
arrestingdevelopment
Jordan,

Wow! I'm just about to hit the sack, but got your post. Haven't absorbed all of what you've provided... on first pass it sounds really good!

But I've got a couple of quick questions I wanted to fire off before my brain shuts down. ;)

(1) Was there a specific reason you opted to use a single page for the page_list functionality? Was it some other factor of the project? Or was it necessary in order to implement the listing functionality? I'm thinking I could get away with a custom page_list template (or at worst a whole new block based on page_list) that I just put on a "normal" page.

(2) I'm wondering if I could get away with a simpler mechanism than the custom controller function for content retrieval (that sounds like it's WAY over my head... I'm still wading around in the shallow end of the C5/PHP pool, LOL!). If I used a custom page type for creating the individual items, could I use the view.php code to layout the elements on the page so that I could just pull the entire "main" area into my Page_list block as is?

(3) Maybe I should use custom attributes instead? Use a page_list/page_list-like block on the listing page. Create a custom page type for the list items... and add custom attributes. One could be a select attribute to identify the type of list item. Then have others for "File Selection" if it's a PDF press release or "Link" if it's a Youtube video or off-site page. The page type wouldn't have any editable areas... maybe just text that shows up in edit mode to tell the editor to use the "Properties/Custom Attributes" section to enter/edit the data?

(4) Worst case, I'm thinking the three custom blocks could be placed onto a "standard" page... and use some jQuery to provide the "pagination" or "more" functionality so that only 10 of them were displayed at a time? Although I'm wondering if that would make the page too heavy to load (it would have to load all of it, then the JS would paginate) and/or too heavy for C5 (is there any practical limit to the number of blocks you want added to an area on a page?).

Or maybe my sleep-deprived brain is just floundering! :D

Thanks, as always, for your input!

- John (off to bed now)
jordanlev replied on at Permalink Best Answer Reply
jordanlev
1) I think I made it a single_page because of that categorization feature I talked about -- I needed a category filter list in the sidebar, and needed that to tie into the list itself. I don't like the idea of two different blocks interacting with each other on the same page because if one gets deleted then everything breaks -- a single_page prevents the user from changing any of the custom functionality on that page which was desirable for that situation.

2) Now that you mention it, a custom template for the page_list block is probably the simpler way to go for your situation. You can probably simplify the code even further -- if you assume that the user is going to add the appropriate custom block to each page, and those blocks display the content appropriately (i.e. the display is the same on the page itself and in the top-level page_list), you can just add this inside your page_list custom template foreach loop:
<?php
$a = new Area('Main');
$a->disableControls();
$a->display($page); //or $cobj if using Concrete5.4.2.2 or lower
?>


3) Custom attributes could be a solution, but this means that someone who visits the actual page with the specific content in it won't see anything. This might be okay though -- maybe those pages exist solely for the use of data entry and should not be visible to normal viewers of the site (make sure you set the "exclude from search" and "exclude from nav" etc. attributes on that page type's defaults [if using 5.5+]).

4) At first using javascript for pagination will be fine, but after hundreds of items are added it will be a bad idea.
arrestingdevelopment replied on at Permalink Reply
arrestingdevelopment
Thanks, Jordan.

1) That makes sense. Since I won't be needing any interaction between blocks on the listing page, my setup won't be as fragile... so I think a standard page will work for me. Great to hear about the criteria that led you to that solution, though, because it helps to expand my understanding of not just the HOW, but the WHY (and even learning the fact that it CAN be done)!

2) I'm liking the simplicity of custom blocks that properly format the content right on the child pages... and then just using the page_list to pull that content in is much simpler (and closer to my skill level, LOL! Thanks for the snippets, btw... they're a HUGE help)!

3) I could go that route... the child pages really don't need to be displayed. Now, anyway. But a good night's sleep has me thinking that this could violate the KISS principle and might confuse the end-user when editing. Not to mention the fact that there MIGHT be a situation where I WANT to show the child page... so having them actually display something that looks good is probably a better idea.

4) Yeah. That's what I was thinking, too. It was really a crutch... figuring I'm better at jQuery than I am at PHP, so it would be easier. But, in the long run, it wouldn't be sustainable.

Thanks again for all your input!!! It's a huge help, and greatly appreciated.

- John
jordanlev replied on at Permalink Reply
jordanlev
Glad I could help. Let me know what you eventually go with and how it turns out.

Your instincts are good -- keeping things simple is the best guiding principle (in my opinion).
arrestingdevelopment replied on at Permalink Reply
arrestingdevelopment
Thanks! Will, do. I'm planning on tackling this over the weekend, so I'll report back with any progress (or hair-pulling, LOL!).

I just wish that my "instincts" when it comes to C5 were better. I tend to find myself "bucking the trend" when it comes to planning the structure for content and content entry. I know that C5 is based around the concept of the page... but oftentimes I find that fact to be counter-intuitive for my customers. As a former systems analyst, I have a tendency to "think like a client"... and the idea that a user has to go to a different page in order to edit what they are seeing on this page contradicts the KISS principle. For THEM.

So I tend to gravitate toward block-based solutions. Since they lend themselves to the full in-context editing benefits. I just wish, sometimes, that all of the page-oriented "goodness" could be translated down to the block level (like all the page_list "grab-a-bunch-of-system-objects-and-sort-or-filter-them-how-you-choose-then-grab-content-out-of-the-resulting-subset" mojo. That would be SWEET on the block level!).

Like there are times when it just feels like it would be more natural to have a block type that allowed a user to add/edit/maintain multiple instances of itself... from within the block editing interface. Like this Press Room page. It would feel more "natural" to me to put the listing page in Edit mode... then click to edit the listing "block"... and have a dialog window appear showing all the rows of data along with entry fields for adding more rows and/or editing/deleting existing ones. Even if the underlying structure was actually pages. It would just feel more WYSIWYG, in-context, KISS principle.

Sometimes I get the feeling that the way things get done in tools like C5 (or WordPress or Joomla or Drupal or...) is very programmer-centric. And, unfortunately, what makes sense to a programmer's mind doesn't always gel with the way non-technical humans (i.e. the rest of the world) think. LOL! For instance:
"You want to edit the content you're seeing here on this page?  Sure!
That's easy!  Go to the sitemap, then expand the page that shows the content
you want to edit.  Then find the sub-page that represents that content
and select it.  Put it in Edit mode.  Choose 'Properties' and go to
the 'Custom Attributes' area and look for a field labeled 'XXXX' and
enter your information there. It's EASY!"

LOL!

But...I'm sure if I was a better programmer, I could figure out a way to do exactly what I want. Heck... C5 is certainly flexible enough!!! :D

Sorry for the diatribe. It's my bedtime again. ;)
jordanlev replied on at Permalink Reply
jordanlev
I understand your point about *some* data entry situations being counter-intuitive with the page-based model. My opinion is that more often than not the page-based model is the most sensical, but yes sometimes there are things (like your Press Room) that would make more sense another way.

Something you might want to look into is setting up a Composer interface for these custom page types. Then the user just goes to the "Composer: Write" dashboard page to enter a new one, and it should appear in the proper place. The only tricky thing will be switching between the three custom blocks -- I think when you set up default blocks for composer they're there no matter what. May need to code things so that if the block exists but doesn't have anything entered into it, it displays nothing (so really every page has all 3 custom blocks on them, but only 1 of them is displaying anything). Or you could make 1 custom block that has fields for all 3 options (but assumes user will only use 1 of the 3 sets of fields).

I think something like this would fit your situation well. The only problem (other than figuring out the block thing I mention above) is users have to go to that dashboard page to use it -- but once they're there it's just one or two steps for them.
arrestingdevelopment replied on at Permalink Reply
arrestingdevelopment
Hmmm... Composer could be a good option.

Whether I end up using composer or not, I guess I could try going about it a couple of different ways:

(1) Single page type ("Press Room Item") with 3 different custom blocks ("PR Video", "PR PDF" and "PR Link"). With advanced permissions, I guess I could limit them to only putting one of the three desired blocks onto the page... and lock it down to only a single block allowed for the Main area. Have the custom page type display the info as it should appear, then have a page_list block with a custom template on another page gathering them all up for display.

(2) Three separate page types ("PR Video", "PR PDF", and "PR Link"), each locked down to have only a single block of the correct type hardcoded into the Main area.

(3) A single page type that has all three blocks hard-coded onto the page, along with a 3-option radio-button form field for the user to choose "Video", "PDF" or "Link"... then show/hide the appropriate section(s) using JS. I'm guessing that the only challenge here would be in enforcing required fields... how can you require entry of data on fields for blocks that are now hidden? And what happens when they don't complete all of the expected fields for the block?

I'm thinking that I'll try starting off as simple as possible: page_list block with custom template and 3 custom blocks. I'll see how the "train-the-logic-into-them" approach works... and tighten things down IF that's not working (advanced permissions to limit block choices, custom page type, etc). But the exercise in "what-if" scenarios is invigorating! ;D

Thanks!!!

- John
mkly replied on at Permalink Reply
mkly
You can have a block edit a page. add.php and edit.php are just forms. And you can do whatever you want in the block controllers save() method.
arrestingdevelopment replied on at Permalink Reply
arrestingdevelopment
@mkly,

NOW you're talkin' outta-the-box! ;D Really creative idea!!! I never even stopped to think about the fact that all of these elements are really just standard HTML forms... and that whatever we can dream of doing is (mostly) possible!

Now... if only my coding skills were as advanced as your creative-thinking skills! LOL!

As I just posted in response to Jordan, I'm thinking I'll try and stick with the simplest approach for this current project... but discussing the "what-if" scenarios on topics like this is such a huge help in having a better grasp of the total range of possibilities with C5! It's really one of the key factors in my burgeoning (dare I say?) LOVE of the software! Simplicity on the front-end for site-owners... PHENOMENAL COSMIC POWERS FOR DEVELOPERS! Mwah-hah-hah! ;)

Thanks!
arrestingdevelopment replied on at Permalink Reply
arrestingdevelopment
OK... so I'm a little late, but I'm finally getting around to coding this, and I've run into a little snag.

Here's the setup:

I created a custom page type. On that page type I added a custom attribute to select the type of Press Item this is (Video, Document, or Offsite link) so I can use that to display the proper icon. I will use the page Name and the Public Date in my display.

I have created a custom block for a Press Room Video, where I am capturing the link to the video and the text of the "Publication/Source" of the video. I have edited the block's view.php so that it is displaying all of the correct content, wrapped in the necessary divs, etc.

<?php 
defined('C5_EXECUTE') or die("Access Denied.");
$title = $page->getCollectionName();
$date = $page->getCollectionDatePublic('n/j/Y');
?>
<div class="title">
   <a href="<?php  echo htmlentities($field_1_textbox_text, ENT_QUOTES, APP_CHARSET); ?>" target="_blank" title="<?php echo $title ?>"><?php echo $title; ?></a>
</div><!-- End title -->
<div class="source">
   <?php  echo (!empty($field_2_textbox_text)) ? htmlentities($field_2_textbox_text, ENT_QUOTES, APP_CHARSET) : 'Un-named Source'; ?>
   <br />
   <?php echo t($date); ?>
</div>


Then I created a custom template for the Page List block (based on Jordan's cleaned up Page List template) that prepares a bunch of variables (this is up at the top of the foreach loop:

// Prepare data for each page being listed...
$prType = $page->getAttribute('press_item_type');
$title = $th->entities($page->getCollectionName());
$date = date('F j, Y', strtotime($page->getCollectionDatePublic()));
$class= "";
$class = ($count % 2 >0) ? "odd" : "";
$class.=" " . strtolower($prType);


Then at the end of the foreach loop, it grabs the content of the 'Main' area of the child page:

<div class="pressItem<?php echo " " . $class; ?>">
   <?php 
      $a = new Area('Main');
      $a->disableControls();
      $a->display($page);
   ?>
</div><!-- End pressItem -->


All is working fine. EXCEPT... when the block gets displayed on the page with the page_list, the $title it is pulling is that of the page_list's page... not that of the block's parent page.

So... question: how do I change the reference to be sure that, no matter WHERE the block is being displayed, it always shows ITS parent's page's title (a lot of possessives there!)?

Thanks!

- John
jordanlev replied on at Permalink Reply
jordanlev
That is very strange. Not sure why that would be happening. Can you post the entire psge_list custom template? (In the snippets of code you pasted I can't see where it would be messing up the title, but perhaps there is something elsewhere in the template that is messing that up?)
arrestingdevelopment replied on at Permalink Reply 3 Attachments
arrestingdevelopment
Jordan,

Here's the entire code for the custom page_list template (attached separately, too):

<?php 
/************************************************************
 * DESIGNERS: SCROLL DOWN! (IGNORE ALL THIS STUFF AT THE TOP)
 ************************************************************/
defined('C5_EXECUTE') or die("Access Denied.");
$pages = $cArray;
$th = Loader::helper('text');
$count=1;
//$ih = Loader::helper('image'); //<--uncomment this if generating thumbnails below
//$nh is already set for us by the controller
$showRss = false;
if (!$previewMode && $controller->rss) {
   $showRss = true;
   $rssUrl = $controller->getRssUrl($b);
   $rssTitle = $th->entities($controller->rssTitle);


Also attached are two screen-grabs... one of the display of the custom block on the custom page type (not glamorous, but it's never viewed)... then the other showing that same block being pulled into the page_list and displaying the page-title of the page_list page (Press Room).

Thanks for your help (and all of the awesome code from the modified blocks like Page_List)!!!

- John
jordanlev replied on at Permalink Reply
jordanlev
It doesn't look like you're actually outputting the $title variable anywhere.
arrestingdevelopment replied on at Permalink Reply
arrestingdevelopment
Jordan,

As always... thanks for your help and time!

You're right. In the page_list block I'm not. I was trying to do all of the layout/structuring of content in the custom block that is placed on the "Press Room Item" custom page type. Then just pull that whole thing into the Page_List by grabbing the "Main" area to display.

Since I am building a link by wrapping a URL (entered in a field in the custom "Press Room Video" block) around the "Name" of the "Press Room Item" page the block is being placed on... I didn't have access to each of the individual components to be able to build it piece-by-piece in the page_list block.

Does that make sense?

So the place where the "Name" of the page is supposed to be grabbed is in the code for view.php for the custom block:
<?php 
defined('C5_EXECUTE') or die("Access Denied.");
$title = $page->getCollectionName();
$date = $page->getCollectionDatePublic('n/j/Y');
?>
<div class="title">
   <a href="<?php  echo htmlentities($field_1_textbox_text, ENT_QUOTES, APP_CHARSET); ?>" target="_blank" title="<?php echo $title ?>"><?php echo $title; ?></a>
</div><!-- End title -->
<div class="source">
   <?php  echo (!empty($field_2_textbox_text)) ? htmlentities($field_2_textbox_text, ENT_QUOTES, APP_CHARSET) : 'Un-named Source'; ?>
   <br />
   <?php echo t($date); ?>
</div>


But then, when this block/code is being pulled into the page_list... I think the "$title = $page->getCollectionName();" is re-running within the context of the page_list page... so it is displaying THAT page's Name, not the page the block is actually embedded on.

So... with a good night's rest... I'm thinking it's because I shouldn't be putting that code into the block's view.php (since that is getting re-run in the wrong context) and should, instead, put it into the block's controller? That way it won't be re-run in the context of the Page_List page?

I feel like there's a Concrete5 "Ah-HAH!" moment coming again! :D

Thoughts?

- John
jordanlev replied on at Permalink Reply
jordanlev
Shouldn't make a difference whether you put the code in the block's view or controller.
What I'm curious about is in your block view code you have $title = $page->getCollectionName(), but where is that $page variable coming from? I'd guess you are setting that in the block controller? And if it's showing the name of the page that the page_list is on instead of the name of the page that the custom block is on, I'd also guess you are setting that to be equal to Page::getCurrentPage(). If that's the case, then you need to understand that Page::getCurrentPage() always gets the current page that the user is viewing, not the page that a block has been placed on. 9 times out of 10 they're the same thing so it's not obvious that there's a difference, but every now and then (like in this case), it is different.

So your options are to either figure out some different code that gets the page that the block has been placed on (I'm not sure what this is off the top of my head but I'm sure there's something). Or don't output the page name in the block view but instead do it in the page_list custom template. For the page that has the custom block on it, you could output the page name in the page_type template (not in the block).

Hope that makes sense.
arrestingdevelopment replied on at Permalink Reply
arrestingdevelopment
Well... (get ready to pull hair! LOL!)... I don't know where that $page variable is coming from.

<pause... to let the screaming/hysterical laughing die down>

I thought it must be being passed from the Block controller... although I didn't explicitly code anything. The only code in the Block controller is what got placed there by your (again, totally awesome) Designer Content Add-On. Here's the controller.php from the custom block:
<?php  
defined('C5_EXECUTE') or die("Access Denied.");
class PressRoomVideoBlockController extends BlockController {
   protected $btName = 'Press Room Video';
   protected $btDescription = 'Block to add the necessary details for a Press Room Video link.';
   protected $btTable = 'btDCPressRoomVideo';
   protected $btInterfaceWidth = "700";
   protected $btInterfaceHeight = "450";
   protected $btCacheBlockRecord = true;
   protected $btCacheBlockOutput = true;
   protected $btCacheBlockOutputOnPost = true;
   protected $btCacheBlockOutputForRegisteredUsers = true;
   protected $btCacheBlockOutputLifetime = 300;
   public function getSearchableContent() {
      $content = array();


Don't ya just love helping N00Bs?!?! ;)

I'll try and see if I can find any code that gets the page the block has been placed on. But I don't think I can output the page name anywhere else but in the block's view.php because of the fact that I need to weave content from the block together with attributes from the block's parent page. The block is super-stupid simple... gathering two pieces of information: the URL to the video and a text attribute for the Publication/Source. The URL is then used as the "href" for an anchor tag wrapped around the parent page name to create the "title" of the block when displayed. So... in the page_list view.php, I don't have access to the individual fields of data entered in the block so that I can do that. Do I?

I was originally going to use custom attributes on the "Press Room Item" page type to capture all of this... and then just retrieve each attribute in the page_list block template and assemble it as needed. But I thought it wasn't particularly end-user friendly to have ALL of the necessary content added in custom attributes... plus it requires them knowing WHICH attributes to add on to the page (since 5.4.2 won't allow setting default properties)... and since there are three different types of "Press Room Items" that could be created, that have slightly different properties (and thus, need slightly different assembly for display), I thought I'd use three different custom blocks (Press Room Video being just the first of the three) so I could have their view.php code lay things out correctly. Then all the page_list template was supposed to have to do was retrieve all the children, loop through them and wrap them in the necessary container divs that identify odd/even, etc.

Glad I got a good night's sleep... looks like I'll be up to my eyeballs in code today. LOL!

- John
arrestingdevelopment replied on at Permalink Reply
arrestingdevelopment
UPDATE:

Jordan... thanks for the pointer about there being another way to access the parent page of a block. FOUND IT! It's "getBlockCollectionObject()"... which the documentation explains as:

"If the current block object specifies a page, returns that Page object. If not, returns the original page object where this block exists as an original."

So... in my block's view.php file, I changed:

$title = $page->getCollectionName();
$date = $page->getCollectionDatePublic('n/j/Y');


to:

$title = $controller->getBlockCollectionObject()->getCollectionName();
$date = $controller->getBlockCollectionObject()-getCollectionDatePublic('n/j/Y');


And VOILA! It worked!

Makes perfect sense, too. Using "$page" doesn't have the correct context. Sure, while viewing the block on the page it was created, it gets information for that page. But when a Page_List is pulling that content into a different page, $page takes on the new page's context and gets the wrong info!

Off to create some more custom blocks and start populating my page list!

Thanks SO MUCH for all the help!

- John
arrestingdevelopment replied on at Permalink Reply
arrestingdevelopment
UPDATED UPDATE:

I re-did the code for this because the way it was working was kinda slow...

Check out this posting on the other thread I had on this topic:

http://www.concrete5.org/index.php?cID=276025#284555...

Thanks!

- John