Find the last block

Permalink 1 user found helpful
I need to set a class of "last" to the div containing the last Block in my Area.

Is there something built into C5 that tells me the current count of the blocks being setup? Or how can I figure out when the last block is being added and add this class into the Div I have setup in the block?

joemc
 
jordanlev replied on at Permalink Reply
jordanlev
This is not built in to C5. If you're a developer willing to spend some time on it, I think there's a few approaches you could try:

1) Hack the concrete/models/area.php file, in the "foreach ($blocksToDisplay as $b) {" loop in the display() function -- get a count of that $blocksToDisplay array before the loop starts, increment a variable every time through the loop, and at the end, check if it's at the max, and output the enclosing div with its class (and remember to set the closing </div> after the block has been outputted). Note that this method is not recommended unless you really need this functionality because hacking the core code can lead to problems, and means you have to re-apply it if you ever update the system. Also, this method has the disadvantage of applying to ALL areas outputted on your site (although perhaps you could figure out how to determine which area is being outputted and decide whether or not to output the div based on that somehow?)

2) Make a custom template for the block that somehow calls the surrounding area and gets the total block count, and its position in the area, and sees if it's the last one or not. Output the div accordingly. This only works if you're talking about a single type of block though, as you can't do this for every single block in the system (well, you could I guess, but seems like a lot of work).

3) In your page type template, use $a->setBlockWrapperStart('<div class="myBlock">') and $a->setBlockWrapperEnd('</div>') (before $a->display()) and then use jquery to check for the last element inside the divs having the "myBlock" class, and rewrite the dom on that last block to give its div the "last" class. As long as you are okay having this not work when javascript is unavailable, this is definitely your best bet.
joemc replied on at Permalink Reply
joemc
Thanks for the help and suggestions! I agree that your 3rd choice would work the best for me...

I had actually already tried that, but my jquery script wasn't returning any of the dynamically created blocks that where created by C5.

I'm guessing it's probably an issue with the position of my javascript include lines... is there a good resource for C5 on best practices for including jquery files and css files in a theme?
Mnkras replied on at Permalink Reply
Mnkras
well, you can already get an array of all blocks in an area, then you can do a foreach and apply the class, look at the autonav block as it applies a </li></ul> to the end
jordanlev replied on at Permalink Reply
jordanlev
Mnkras, that's kind of like my option #2 above -- but I think he's talking about applying this as part of his theme, so it works across all blocks (regardless of type), in which case it would be impractical to create custom templates for all of them.
synlag replied on at Permalink Reply
synlag
several ways to solve this i think, one could be to create a 'last' template for blocks we assume to appear sometimes at the bottom of an area

$a = new Area('Area Name');
$c = Page::getCurrentPage();
$areaObjects = $a->getAreaBlocksArray($c);
$lastAreaBlock = array_shift(array_reverse($areaObjects));
$lastAreaBlock->setCustomTemplate('last');
$a->display($c);


another might be to do it with the design stuff for blocks.
joemc replied on at Permalink Best Answer Reply
joemc
Ok, this worked perfectly with the addition of one line of code... I needed to reset all of the other blocks back to the original view before setting the last block to the 'last' view. Here's my code:
$a = new Area('Tour Dates');
$c = Page::getCurrentPage();
$areaObjects = $a->getAreaBlocksArray($c);
$lastAreaBlock = array_shift(array_reverse($areaObjects));
foreach($areaObjects as $bl) { $bl->setCustomTemplate('view.php'); }
$lastAreaBlock->setCustomTemplate('templates/last.php');
$a->display($c);
joemc replied on at Permalink Reply
joemc
Ok, I've figured out the solution (at least good enough for this project) using the advice from jordanlev (opt. 2 above) and Mnkras.

Here is what I put in my template (I have added notes for anyone who wants to try and duplicate for their own needs).

<?php
   // Create a counter variable
   $count = 1;
   // Create the area
   $a = new Area('Tour Dates');
   // Create an array of the blocks
   $blocks = $a->getAreaBlocksArray($c);
   // Store the size of the array
   $numBlocks = sizeof($blocks);
   // Loop through the blocks
   foreach($blocks as $ni) {
      // If this is the last block, add a div with a class of "last"
      if ($count==$numBlocks) {
         echo "<div id=\"dateBlock\" class=\"last\">";
      // Else, add a div
jordanlev replied on at Permalink Reply
jordanlev
Be aware that if the area has any custom designs or if it has a layout, those won't get displayed because you aren't calling $a->display($c).

But if you don't care about those things for this situation, then this is a great solution.
joemc replied on at Permalink Reply
joemc
Ahh, good point... I guess my solution doesn't work for me.
jordanlev replied on at Permalink Reply
jordanlev
Joe,
Did you put your code inside the $(document).ready(function() { ... }); construct? That's where you want to put things that will run after all of the page elements are loaded, and it doesn't matter where in your code you put this -- it will magically just work.

If you want to paste the javascript you used I'd be happy to take a look at it.
joemc replied on at Permalink Reply
joemc
Jordan - I'm using my own .js files so I've been adding them to the header like this:
<script type="text/javascript" src="<?=$this->getThemePath()?>/js/bannerRotator.js"></script>


and then adding:
<?php Loader::element('header_required'); ?>


Below it...

I'm very new to concrete5, so I'm kind of going through a quick crash course, trying to figure out the best way to do things.

But, is there a better way to include my own external .js files, or is it best to add my scripts into an already created concrete5 script? And if so, where is that script to add to?

Most of my jquery has broke since trying to implement it into concrete5, so I do need to figure out the proper way to implement it with concrete5.

Thanks for the help!
jordanlev replied on at Permalink Reply
jordanlev
Joe,
What's inside your own .js files? If it's just code that's not wrapped in the jquery $(document).ready(function() { ... }); thing, then you're going to have problems with it running at the wrong time regardless of how it's included.

As for how to include the files, yes, the way you're doing it is the best way. One problem you may be having is if you're including the jquery library yourself -- Concrete5 already includes that automatically on every page so if you include it also, it might cause problems.
joemc replied on at Permalink Reply
joemc
Jordan - Yes, I am using $(document).ready(function() { ... }); - with my code located inside.

I removed my jquery library include, but when I run my site I get this error in my console:

Uncaught ReferenceError: $ is not defined
jordanlev replied on at Permalink Reply
jordanlev
Hmm... make sure your own javascript file includes (e.g. <script type="text/javascript" src="<?=$this->getThemePath()?>/js/bannerRotator.js"></script>) are AFTER the concrete5 ones (<?php Loader::element('header_required'); ?>)
joemc replied on at Permalink Reply
joemc
Ahh... Thanks! In trying to figure all this out I've been reading conflicting advice on these things. I read somewhere to put the includes before the "header_required".

Everything seems to work now with my includes coming after the "Loader::element('header_required');" and not including my own jquery.js file.

Thanks again for the help! On to my next issue and forum topic...
thirdender replied on at Permalink Reply
Here's how I implemented option #2 for a project I'm working on. I had rows of a custom block type (created with Designer Content), and needed HR tags after all but the last in an area. I was working from pre-sliced HTML... if it had been my slice, I would probably have styled using :first-child (and still might re-work it, but wanted to try this first).

This code is run in the view.php every time a block of this type is displayed. I'm assuming the Area and Block objects are cached and not pulled from the database every time, but not sure.

<?php
    // Check to see if we're the last block in the area... output <hr /> if false
    if (is_object($obj->a)) {
        $lastBlock = end($obj->a->getAreaBlocksArray($page));
        if ($obj->bID != $lastBlock->bID) {
            echo '<hr />';
        }
    }