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

Issue Summary

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!

Comments

#1

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

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

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

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

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

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

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

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

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

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

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

#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

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

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

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

AttachmentSizeStatusTest resultOperations
516356_codecollapsing_01.patch3.41 KBIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch 516356_codecollapsing_01.patch.View details | Re-test
codecollapsing.png94.22 KBIgnoredNoneNone

#16

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

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

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

AttachmentSizeStatusTest resultOperations
516356_codecollapsing_02.patch3.69 KBTest request sentFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch 516356_codecollapsing_02.patch.View details
geshifilter.js.txt1.86 KBIgnoredNoneNone

#19

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

#20

subscribing

#21

Worked a bit more on the patch

now collapsible code blocks are optional (off by default) and can be triggered with something like

[code language="java" collapsible="true"]
bla bla
[/code]
AttachmentSizeStatusTest resultOperations
516356_codecollapsing_03.patch8.33 KBTest request sentPASSED: [[SimpleTest]]: [MySQL] 0 pass(es).View details

#22

I am just wondering if this patch can make it into the mainline version.
I'd love not to login via SSH to patch it, on my few sites that use it.

I like it so much that I blogged about it http://devblog.brahmancreations.com/content/geshi-show-more-code.

I wonder if only the 3 of us in this thread that know about it.

Thanks

Frank

#23

If there is more user feedback on this patch, it's certainly supposed to go in the main line.
(Some simpletest coverage of the feature would also be nice.)
Have you already tried it on a development/localhost site or something?

#24

Great feature! I use successfully the second patch to minimize changes.

#25

#21: 516356_codecollapsing_03.patch queued for re-testing.

#26

Title:Collapse long code block» Collapse long code block - errors applying patch.

Does the patch depend on which version of Geshi is used?

Does it change the Geshi libraries own files, or only those of the Drupal module?

What directory should the patch be applied from?

#27

Title:Collapse long code block - errors applying patch.» Collapse long code block

at voipfc:

It should be applied against the CVS HEAD version (or the latest 6.x-1.x-dev nighly build tar ball) of the geshifilter module (applying it from the geshifilter directory should work).

It only changes the geshifilter drupal module files, it does not touch the geshi library.

#28

#18: 516356_codecollapsing_02.patch queued for re-testing.

#29

I've just recently started using this module and I think it great. Unfortunately the version I downloaded doesn't seem to have this feature yet. I'm not a developer and I don't know how to apply the patch. Can someone attach a .zip that I can download or make a new build so I can use this?

nobody click here