Collapse long code block

libeco - July 11, 2009 - 09:41
Project:GeSHi Filter for syntax highlighting
Version:6.x-1.x-dev
Component:User interface
Category:feature request
Priority:normal
Assigned:Unassigned
Status:needs review
Description

Hi there,

Thanks for this module. I have a request though. Would it be possible to make long code blocks automatically collapse to a smaller area? I think this would be possible with jQuery through a theme, but wouldn't it be nice to have as a feature?

I would ask to look at the PHPBB-module which has this functionality, but it seems like the demo site is not working at this moment.

Thanks in advance!

#1

libeco - July 11, 2009 - 12:28

I have started something, I'm not very experienced with jQuery, so I don't know if there's a better way. This is still pure jQuery, not implemented into Drupal.

The jQuery:

$(document).ready(function() {
  var $container = $('div.geshifilter');
  var $containerHeight = $container.outerHeight();
  var $expandContainer = '<div class="collapse-link"><a class="expand" href="#">Expand</a></div>';
  var $collapseContainer = '<div class="collapse-link"><a class="collapse" href="#">Collapse</a></div>';
 
  if ($container.outerHeight() > 180) {
    $container.addClass('collapsed');
    var $containerHeightNew = $container.outerHeight();
    $container.after($expandContainer);
  }
 
  var $expandLink = $('a.expand');
  $expandLink.live('click', function() {
    $container.removeClass('collapsed').height($containerHeight);
    $(this).parent().remove();
    $container.after($collapseContainer);
    return false;
  });
 
  var $collapseLink = $('a.collapse');
  $collapseLink.live('click', function() {
    $container.addClass('collapsed').height($containerHeightNew);
    $(this).parent().remove();
    $container.after($expandContainer);
    return false;
  });

});

And some css:

.collapsed {
  height: 200px;
  overflow: auto;
}
.collapse-link {
  text-align: right;
}

#2

soxofaan - July 11, 2009 - 16:28
Version:6.x-1.2» 6.x-1.x-dev

interesting,
I'll try it out when I have more time.

looking at the code: does it also work when there are several geshifilter snippets on the same page?

maybe you should also check out the height(val) function (http://docs.jquery.com/CSS/height),
and maybe we can avoid the CSS part and do everything in jquery?

#3

libeco - July 11, 2009 - 16:43

I haven't tried it on the real page yet. I'll see if I can implement it into Drupal, I haven't really thought about the behaviour of multiple GeSHi snippets. The reason I used CSS is the ability for the user to easily change the height of the box, but perhaps this could also be an admin setting?

The height function is already in the code. As I said I'm not an experienced jQuery developer, don't know if I'm using the most efficient code, but it's a start and I'm willing to learn from corrections. :-)

#4

libeco - July 11, 2009 - 17:46

Here's a rewrite of the previous code which will work for different GeSHi snippets, however, it still needs some work for snippets with different heights.

$(document).ready(function() {
  var $container = $('div.geshifilter');
  var $containerHeight = $container.outerHeight();
  var $expandContainer = '<div class="collapse-link"><a class="expand" href="#">Expand</a></div>';
  var $collapseContainer = '<div class="collapse-link"><a class="collapse" href="#">Collapse</a></div>';
 
  if ($container.outerHeight() > 180) {
    $container.addClass('collapsed');
    var $containerHeightNew = $container.outerHeight();
    $container.after($expandContainer);
  }

  $('a.expand').live('click', function() {
    var $linkDiv = $(this).parent();
    $linkDiv.prev().removeClass('collapsed').height($containerHeight);
    $linkDiv.prev().after($collapseContainer);
    $linkDiv.remove();
    return false;
  });
 
  $('a.collapse').live('click', function() {
    var $linkDiv = $(this).parent();
    $linkDiv.prev().addClass('collapsed').height($containerHeightNew);
    $linkDiv.prev().after($expandContainer);
    $linkDiv.remove();
    return false;
  });

});

#5

libeco - July 11, 2009 - 18:20

And I think this is the finished code which works well with multiple GeSHi snippets:

$(document).ready(function() {
  var $container = $('div.geshifilter');
  var $containerHeight = new Array();
  $container.each(function() {
    $containerHeight.push($(this).outerHeight());
  });
  var $expandContainer = '<div class="collapse-link"><a class="expand" href="#">Expand</a></div>';
  var $collapseContainer = '<div class="collapse-link"><a class="collapse" href="#">Collapse</a></div>';
 
  if ($container.outerHeight() > 180) {
    $container.addClass('collapsed');
    var $containerHeightNew = $container.outerHeight();
    $container.after($expandContainer);
  }

  $('a.expand').live('click', function() {
    var $linkDiv = $(this).parent();
    var $currentItem = $container.index($linkDiv.prev());

    $linkDiv.prev().removeClass('collapsed').height($containerHeight[$currentItem]);
    $linkDiv.prev().after($collapseContainer);
    $linkDiv.remove();
    return false;
  });
 
  $('a.collapse').live('click', function() {
    var $linkDiv = $(this).parent();
    var $currentItem = $container.index($linkDiv.prev());

    $linkDiv.prev().addClass('collapsed').height($containerHeightNew);
    $linkDiv.prev().after($expandContainer);
    $linkDiv.remove();
    return false;
  });

});

#6

soxofaan - July 11, 2009 - 19:02
Status:active» needs work

I tried your code, but it doesn't work because of the 'live' function,
apparently that's a jQuery 1.3 (http://docs.jquery.com/Events/live)
I think Drupal 6 ships only a 1.2.something version by default

#7

libeco - July 11, 2009 - 19:20

You're right, Ok I've just tried this and it seems to work:

Step 1
Download jQuery Update module and enable it. Make sure you use the 6.x.2.x (currently still dev).

Step 2
Add

<?php
drupal_add_js
(drupal_get_path('module', 'geshifilter') .'/geshifilter.js');
?>
to function geshifilter_init() in geshifilter.module.

Step 3
Create a new geshifilter.js file and add this code:

Drupal.behaviors.collapseGeSHi = function() {
  var $container = $('div.geshifilter');
  var $containerHeight = new Array();
  var $containerHeightNew = new Array();
  var $expandContainer = '<div class="collapse-link"><a class="expandGeSHi" href="#">Expand</a></div>';
  var $collapseContainer = '<div class="collapse-link"><a class="collapseGeSHi" href="#">Collapse</a></div>';
 
  $container.each(function() {
    $containerHeight.push($(this).outerHeight());
    if ($(this).outerHeight() > 180) {
      $(this).addClass('collapsedGeSHi');
      $containerHeightNew.push($(this).outerHeight());
      $(this).after($expandContainer);
    } else {
      $containerHeightNew.push($(this).outerHeight());
    }
  });
 
  $('a.expandGeSHi').live('click', function() {
    var $linkDiv = $(this).parent();
    var $currentItem = $container.index($linkDiv.prev());

    $linkDiv.prev().removeClass('collapsedGeSHi').height($containerHeight[$currentItem]);
    $linkDiv.prev().after($collapseContainer);
    $linkDiv.remove();
    return false;
  });
 
  $('a.collapseGeSHi').live('click', function() {
    var $linkDiv = $(this).parent();
    var $currentItem = $container.index($linkDiv.prev());

    $linkDiv.prev().addClass('collapsedGeSHi').height($containerHeightNew[$currentItem]);
    $linkDiv.prev().after($expandContainer);
    $linkDiv.remove();
    return false;
  });

};

Step 4
Add these lines to geshifilter.css:

div.collapsedGeSHi {
  height: 200px;
  overflow: auto;
}
.collapse-link {
  text-align: right;
}

Maybe you need to clear your cache, but now it should work! :-)

#8

soxofaan - July 11, 2009 - 21:29

I don't think depending on jQuery Update is a good idea.
Isn't it possible to implement it with with jQuery 1.2?

#9

libeco - July 11, 2009 - 21:52

The live function is available only since 1.3. This function makes it possible to create an event listener form an element before the element exists. I have no idea how to do this without this function.

I found an example of doing it the 1.2 way, but I will have to look into it to see if I can convert the code to work that way.

Why would depending on a newer, better version of jQuery not be a good idea? jQuery 1.3 is said to be much faster and it's functions like .live that make it very easy to use. Or is it just the idea of having to install a module for such little added functionality?

Thanks!

#10

soxofaan - July 11, 2009 - 22:30

I have nothing against 1.3 :)
it is indeed about having to install an extra module for a rather small feature. GeSHi filter has no dependencies outside of Drupal core and I want to keep it that way.

I've been experimenting myself a bit and came up with the following (based on http://andylangton.co.uk/articles/javascript/jquery-show-hide-multiple-e...):

$(document).ready(function() {
  $('div.geshifilter').append('<div class="geshifiltertools"><a href="#" class="geshifilter-tools-toggle">show more/less</a></div>');
  $('div.geshifiltertools').prev().hide();
  $('a.geshifilter-tools-toggle').click(function(){
    $(this).parent().prev().toggle('slow');
    return false;
  });
});

it's still very rough, (shows all or nothing of a snippet, instead of partial collapsing), but I don't need the live function something alike.

#11

libeco - July 12, 2009 - 10:41

Here is my version without the .live function:

Step 1
Add

<?php
drupal_add_js
(drupal_get_path('module', 'geshifilter') .'/geshifilter.js');
?>
to function geshifilter_init() in geshifilter.module.

Step 2
Create a new geshifilter.js file and add this code:

Drupal.behaviors.collapseGeSHi = function() {
  var $container = $('div.geshifilter');
  var $containerHeight = new Array();
  var $containerHeightNew = new Array();
  var $expandContainer = '<div class="collapse-link"><a class="expandGeSHi" href="#">Expand</a></div>';
  var $collapseContainer = '<div class="collapse-link"><a class="collapseGeSHi" href="#">Collapse</a></div>';
  var $expandLink = '<a class="expandGeSHi" href="#">Expand</a>';
  var $collapseLink = '<a class="collapseGeSHi" href="#">Collapse</a>';
 
  $container.each(function() {
    $containerHeight.push($(this).outerHeight());
    if ($(this).outerHeight() > 180) {
      $(this).addClass('collapsedGeSHi');
      $containerHeightNew.push($(this).outerHeight());
      $(this).after($expandContainer);
    } else {
      $containerHeightNew.push($(this).outerHeight());
    }
  });
 
  // Expanding the box
  $('div.collapse-link').bind('click', function(e) {
    var $linkDiv = $(this);
    var $currentItem = $container.index($linkDiv.prev());
    var $clicked = $(e.target);

    if($clicked.is('a.expandGeSHi')) {
      $linkDiv.prev().removeClass('collapsedGeSHi').height($containerHeight[$currentItem]);
      $linkDiv.append($collapseLink);
      $clicked.remove();
      return false;
    }
  });
 
  // Collapsing the box
  $('div.collapse-link').bind('click', function(e) {
    var $linkDiv = $(this);
    var $currentItem = $container.index($linkDiv.prev());
    var $clicked = $(e.target);

    if($clicked.is('a.collapseGeSHi')) {
      $linkDiv.prev().addClass('collapsedGeSHi').height($containerHeightNew[$currentItem]);
      $linkDiv.append($expandLink);
      $clicked.remove();
      return false;
    }
  });

};

Step 3
Add these lines to geshifilter.css:

div.collapsedGeSHi {
  height: 200px;
  overflow: auto;
}
.collapse-link {
  text-align: right;
}

Maybe you need to clear your cache, but now it should work! :-)

#12

soxofaan - July 12, 2009 - 11:21

#11 doesn't seem to work for me: code is not collapsed and collapse link does not change much

I'm also trying some stuff out.
I found this interesting inspiration: http://sim.plified.com/2008/09/15/sliding-content-from-a-partial-height-... (also try out the demo).
A big advantage of this approach is that the collapsing/expanding is animated, which is much more user friendly than instantaneous hiding/showing.

#13

soxofaan - July 12, 2009 - 12:31

this seems to work well for me:

var geshifilter_collapse_height = 100;

$(document).ready(function() {
  $('div.geshifilter').each(function() {
    // Get code container and its height.
    var code_container = $(this).children(':first');
    var code_container_original_height = code_container.height();

    // Only do the collapsing where it makes sense.
    if (code_container_original_height > geshifilter_collapse_height) {
      // Add more/less link.
      $(this).append('<div class="geshifilter-tools"><a href="#" class="geshifilter-tools-toggle">Read more</a></div>');
      // Store the original heights of the geshifilter divs.
      code_container.attr('original_height', code_container_original_height);
      // Collapse by default.
      code_container.addClass('geshifilter-collapsed').css("height", geshifilter_collapse_height);
    }
  });

  // Add handler to read more/collapse link.
  $("a.geshifilter-tools-toggle").click(function() {
    // Get code container.
    var code_container = $(this).parent().prev();

    if (code_container.hasClass('geshifilter-collapsed')) {
      // Show more (use original height).
      var original_height = code_container.attr('original_height') + 'px';
      code_container.removeClass('geshifilter-collapsed').animate({height: original_height}, 'slow');
      $(this).text('Show less');
    }
    else {
      // Collapse.
      code_container.addClass('geshifilter-collapsed').animate({height: geshifilter_collapse_height}, 'slow');
      $(this).text('Read more');
    }
    // Return false so that the link is no followed.
    return false;
  });

});

still some (styling) issues to do

#14

libeco - July 12, 2009 - 12:39

I wonder why #11 is not working for you, here it works perfectly.

#13 works well for me, but what I read from the book Drupal 6 JavaScript and jQuery it is better to let Drupal handle the jQuery with
Drupal.behaviors.collapseGeSHi = function() {
Or ofcourse some other name you choose.

#15

soxofaan - July 12, 2009 - 13:53
Status:needs work» needs review

ok, here is a real patch (and a screenshot).

About the behaviors: from what I understand of it, behaviors is only about attaching event handlers to objects. Here we are also adding objects on their own (the toggle link inside a div), so I think Drupal.behaviors is not needed here. (The click event handler added to the toggle link could be done with Drupal.behaviors, but it's not necessary as far as I know, and it would make the code more difficult to understand.)

still to do/figure out:
* make it an opt-in feature through the admin interface?
* make configurable inline like [code collapsible=true]foo bar code [/code]?
* collapsing should also be optional for geshinodes and geshifields

AttachmentSize
516356_codecollapsing_01.patch 3.41 KB
codecollapsing.png 94.22 KB

#16

libeco - July 12, 2009 - 14:20

Does this make any sense for the admin setting? Ofcourse a checkbox should be added to the admin settings for this to work.

<?php
if (variable_get('adminsettingname', '') == true) {
 
drupal_add_js(drupal_get_path('module', 'geshifilter') .'/geshifilter.js');
}
?>

#17

libeco - July 12, 2009 - 15:16

I've applied the patch with TortoiseMerge and it did not create the .js file, don't know if it's a limitation of TortoiseMerge, patch-files or this one in particular.

#18

soxofaan - July 13, 2009 - 08:17

adding new files should be no problem with patch files, so I'm afraid it's a limitation of ToroiseMerge

anyway here is an updated patch and individual js file

AttachmentSize
516356_codecollapsing_02.patch 3.69 KB
geshifilter.js.txt 1.86 KB

#19

libeco - July 13, 2009 - 10:38

Wouldn't it be better to change 'Show more code' to 'Show all code'?

#20

voipfc - September 13, 2009 - 21:23

subscribing

 
 

Drupal is a registered trademark of Dries Buytaert.