Showing with 100 additions and 50 deletions.
  1. +16 −0 demos/widgets/collapsibles/index.php
  2. +59 −45 js/widgets/collapsible.js
  3. +13 −5 tests/integration/collapsible/collapsible_core.js
  4. +12 −0 tests/integration/collapsible/index.html
@@ -211,6 +211,22 @@
</div>
</div><!--/demo-html -->

<h2>Pre-rendered markup</h2>
<p>You can supply pre-rendered markup for any collapsible to save startup time. The example below illustrates the markup you have to provide for a pre-rendered collapsible.</p>
<div data-demo-html="true">
<div data-role="collapsible" data-enhanced="true" class="ui-collapsible ui-collapsible-inset ui-corner-all ui-collapsible-collapsed">
<h4 class="ui-collapsible-heading ui-collapsible-heading-collapsed">
<a href="#" class="ui-collapsible-heading-toggle ui-btn ui-btn-icon-left ui-icon-plus">
Heading
<div class="ui-collapsible-heading-status"> click to expand contents</div>
</a>
</h4>
<div class="ui-collapsible-content ui-collapsible-content-collapsed" aria-hidden="true">
<p>I'm the collapsible content. By default I'm closed, but you can click the header to open me.</p>
</div>
</div>
</div>

</div><!-- /content -->

<div data-role="footer" class="jqm-footer">
@@ -17,6 +17,7 @@ var rInitialLetter = /([A-Z])/g;

$.widget( "mobile.collapsible", {
options: {
enhanced: false,
expandCueText: null,
collapseCueText: null,
collapsed: true,
@@ -32,68 +33,77 @@ $.widget( "mobile.collapsible", {
},

_create: function() {
var anchor, placeholder,
$el = this.element.addClass( "ui-collapsible" ),
opts = this.options,
heading = $el.children( opts.heading ).first(),
replacementHeading = heading,
content = $el.wrapInner( "<div class='ui-collapsible-content'></div>" ).children( ".ui-collapsible-content" ),
accordion = $el.closest( ":jqmData(role='collapsible-set')" + ( $.mobile.collapsibleset ? ", :mobile-collapsibleset" : "" ) ).addClass( "ui-collapsible-set" );

// Replace collapsibleHeading if it's a legend
if ( heading.is( "legend" ) ) {
replacementHeading = $( "<div role='heading'>"+ heading.html() +"</div>" );
placeholder = $( "<div><!-- placeholder for legend --></div>" ).insertBefore( heading );
heading.remove();
}

anchor = replacementHeading
.detach()
//modify markup & attributes
.addClass( "ui-collapsible-heading" )
.append( "<span class='ui-collapsible-heading-status'></span>" )
.wrapInner( "<a href='#' class='ui-collapsible-heading-toggle'></a>" )
.find( "a" )
.first()
.addClass( "ui-btn" );

//drop heading in before content
replacementHeading.insertBefore( content );
var elem = this.element,
ui = {
accordion: elem
.closest( ":jqmData(role='collapsible-set')" +
( $.mobile.collapsibleset ? ", :mobile-collapsibleset" : "" ) )
.addClass( "ui-collapsible-set" )
},
opts = this.options;

$.extend( this, {
_accordion: accordion,
_accordionWidget: null,
_anchorClasses: "",
_elClasses: "",
_contentTheme: "",
_ui: {
placeholder: placeholder,
originalHeading: heading,
anchor: anchor,
content: content,
heading: replacementHeading
}
_ui: ui
});

if ( opts.enhanced ) {
ui.heading = $( ".ui-collapsible-heading", this.element[ 0 ] );
ui.content = ui.heading.next();
ui.anchor = $( "a", ui.heading[ 0 ] ).first();
} else {
this._enhance( elem, ui );
this._setOptions( opts );
}

//events
this._on({
"expand": "_handleExpandCollapse",
"collapse": "_handleExpandCollapse"
});

this._on( heading, {
"tap": function(/* event */) {
heading.find( "a" ).first().addClass( $.mobile.activeBtnClass );
this._on( ui.heading, {
"tap": function() {
ui.heading.find( "a" ).first().addClass( $.mobile.activeBtnClass );
},

"click": function( event ) {
this._handleExpandCollapse( !heading.hasClass( "ui-collapsible-heading-collapsed" ) );
this._handleExpandCollapse( !ui.heading.hasClass( "ui-collapsible-heading-collapsed" ) );
event.preventDefault();
event.stopPropagation();
}
});
},

_enhance: function( elem, ui ) {
elem.addClass( "ui-collapsible" );
ui.originalHeading = elem.children( this.options.heading ).first(),
ui.content = elem.wrapInner( "<div class='ui-collapsible-content'></div>" ).children( ".ui-collapsible-content" ),
ui.heading = ui.originalHeading;

// Replace collapsibleHeading if it's a legend
if ( ui.heading.is( "legend" ) ) {
ui.heading = $( "<div role='heading'>"+ ui.heading.html() +"</div>" );
ui.placeholder = $( "<div><!-- placeholder for legend --></div>" ).insertBefore( ui.originalHeading );
ui.originalHeading.remove();
}

this._setOptions( opts );
ui.anchor = ui.heading
.detach()
//modify markup & attributes
.addClass( "ui-collapsible-heading" )
.append( "<span class='ui-collapsible-heading-status'></span>" )
.wrapInner( "<a href='#' class='ui-collapsible-heading-toggle'></a>" )
.find( "a" )
.first()
.addClass( "ui-btn" );

//drop heading in before content
ui.heading.insertBefore( ui.content );

return ui;
},

_handleExpandCollapse: function( isCollapse ) {
@@ -131,11 +141,11 @@ $.widget( "mobile.collapsible", {
// parent accordion, then from the defaults.
_optionValue: function( options, name ) {
var ret,
accordion = this._accordion,
accordionWidget = this._accordionWidget;
accordion = this._ui.accordion,
accordionWidget = this._ui.accordionWidget;

if ( accordion.length && !accordionWidget ) {
this._accordionWidget = accordionWidget = accordion.data( "mobile-collapsibleset" );
this._ui.accordionWidget = accordionWidget = accordion.data( "mobile-collapsibleset" );
}

ret =
@@ -166,6 +176,10 @@ $.widget( "mobile.collapsible", {
_destroy: function() {
var ui = this._ui;

if ( this.options.enhanced ) {
return;
}

if ( ui.placeholder ) {
ui.originalHeading.insertBefore( ui.placeholder );
ui.placeholder.remove();
@@ -200,7 +214,7 @@ $.widget( "mobile.collapsible", {
}

// Set corners for individual collapsibles to false when in a collapsible-set
if ( this._accordion.length > 0 ) {
if ( this._ui.accordion.length > 0 ) {
opts.corners = false;
}

@@ -4,6 +4,14 @@

// TODO split out into seperate test files
(function( $ ){
function testExpandCollapse( selector ) {
ok($( selector ).hasClass( "ui-collapsible-collapsed" ), selector + " should be collapsed");
$( selector + " >:header a" ).first().click();
ok(!$( selector ).hasClass( "ui-collapsible-collapsed" ), selector + " should be expanded after click");
$( selector + " >:header a" ).first().click();
ok($( selector ).hasClass( "ui-collapsible-collapsed" ), selector + " should be collapsed");
}

module( "Collapsible section", {});

test( "The page should be enhanced correctly", function(){
@@ -15,11 +23,11 @@
});

test( "Expand/Collapse", function(){
ok($( "#collapsed-collapsible" ).hasClass( "ui-collapsible-collapsed" ), "First collapsible should be collapsed");
$( "#collapsed-collapsible" ).click();
ok(!$( "collapsed-collapsible" ).hasClass( "ui-collapsible-collapsed" ), "First collapsible should be expanded after click");
$( "#collapsed-collapsible" ).click();
ok($( "#collapsed-collapsible" ).hasClass( "ui-collapsible-collapsed" ), "First collapsible should be collapsed");
testExpandCollapse( "#collapsed-collapsible" );
});

test( "Expand/Collapse pre-rendered", function(){
testExpandCollapse( "#pre-rendered-collapsible" );
});

module( "Collapsible set", {});
@@ -55,6 +55,18 @@ <h3>Section B</h3>

</div>

<div id="pre-rendered-collapsible" data-nstest-role="collapsible" data-nstest-enhanced="true" class="ui-collapsible ui-collapsible-inset ui-corner-all ui-collapsible-collapsed">
<h4 class="ui-collapsible-heading ui-collapsible-heading-collapsed">
<a href="#" class="ui-collapsible-heading-toggle ui-btn ui-btn-icon-left ui-icon-plus">
Heading
<div class="ui-collapsible-heading-status"> click to expand contents</div>
</a>
</h4>
<div class="ui-collapsible-content ui-collapsible-content-collapsed" aria-hidden="true">
<p>I'm the collapsible content. By default I'm closed, but you can click the header to open me.</p>
</div>
</div>

<div id="basic-collapsible-set" data-nstest-role="collapsible-set">
<div data-nstest-role="collapsible">
<h3>Section A</h3>