Knockout, JQMobile, and generating a collapsible-set doesn't quite seem to work right
Asked Answered
N

2

5

I've checked out a number of samples, but none are quite the same as what I'm trying to do.

What I've got works, mostly, but it doesn't quite work right.

Here's a fiddle to illustrate the issue.

http://jsfiddle.net/5yA6G/4/

Notice that the top set works fine, but it's statically defined.

The bottom set (Tom, steve, bob) "work" basically, but the header element ends up both in the collapsible header AND in the portion of the collapsible that gets hidden.

Seems like I must be doing something wrong, but I haven't been able to figure out what.

Any ideas?

Nosography answered 3/7, 2012 at 15:7 Comment(0)
E
4

I actually found a much easier way to do this:

  1. Set up your foreach binding as you normally would for me it looked like this

    <div data-bind="foreach: promotions">
    
        <h3 data-bind="text: Title"></h3>
            <p>Creator:<span data-bind="text: Creator"></span></p> 
            <p>Effective Date:<span data-bind="text: EffectiveDate"></span></p>
            <span data-bind="text: Description"></span>
            <a data-bind="text: ButtonText, attr: {href: ButtonLink}"></a>
    

  2. Wrap that in a div with class="collapsible like so

    <div data-role="collapsible-set" data-bind="foreach: promotions">
    
    <div class="collapsible">
        <h3 data-bind="text: Title"></h3>
            <p>Creator:<span data-bind="text: Creator"></span></p> 
            <p>Effective Date:<span data-bind="text: EffectiveDate"></span></p>
            <span data-bind="text: Description"></span>
            <a data-bind="text: ButtonText, attr: {href: ButtonLink}"></a>
    

  3. Apply the collapsible widget via jquery mobile after you do your binding like so:

    $(document).ready(function () {
        ko.mapping.fromJS(data, dataMappingOptions, PromotionViewModel.promotions);
        ko.applyBindings(PromotionViewModel);
        $('.collapsible').collapsible();
    });
    
  4. For a collapsible set the same idea can be applied just set the class="collapsible-set" on your foreach div. Hope this helps

Extensive answered 11/12, 2012 at 17:21 Comment(0)
N
6

Just for reference and for anyone else running into this problem, it turns out to be at least somewhat obvious in hindsight.

Knockout's built in "anonymous" templating works great in many cases, but with JQMobile, it can be a tad quirky.

That's because JQMobile will adjust the content of the anonymous template when the page loads, just as it does with all the other content.

Then, later, when you use knockout's ApplyBindings function, knockout will add the applicable elements, just as it should. As many posts and answers have hinted at, you then MUST call collapsible() on the newly created elements, via something like this.

$("div[data-role='collapsible']").collapsible({refresh: true});

No problem there. HOWEVER, if JQM has already applied formatting, then the anonymous template has already been "rendered" by JQM, so rendering it again by calling collapsible causing all sorts of funky results, including doubled heading, nested collapsibles, etc.

The solution for me was to use Knockout's "Named Template" feature, and just put the template to render the collapsible elements into a tag, like this:

<script type="text/html" id="alarm-template">
    <div data-role="collapsible" data-collapsed="true" data-collapsed-icon="arrow-d" data-expanded-icon="arrow-u" data-enhance="false">
        <h3 data-bind="text:name"></h3>
        <p>The content here</p>
        <p data-bind="text: name"></p>
    </div>
</script>               

Doing this prevents JQM from "rendering" the template elements when the page loads, so they'll be rendered properly when they're actually generated.

EDIT: The above works fine for collapsibles NOT in a collapsible-set, but, if they're in a set, I've found the styling of the elements (particularly, the corner rounding to indicate belonging to a set) doesn't work right.

From what I can tell, there are 2 problems:

The first is that just triggering "Create" doesn't actually refresh the styling of all the collapsibles in the set. to do that you have to do...

$("div[data-role='collapsible-set']").collapsibleset('refresh');

But, there's a worse problem. JQM "marks" the last item in the set as the "last item". That fact then gets used to determine how to style the last item as it's being expanded/collapsed.

Since Knockout doesn't actually rebuild the entire set (for speed), each time you call the refresh method, JQM dutifully marks the last item as "last", but never removes the marks on previous items. As a result, if you start from an empty list, EVERY item ends up being marked "last" and the styling fails because of this.

I've detailed the fix for that at github in an issue report.

https://github.com/jquery/jquery-mobile/issues/4645

Nosography answered 4/7, 2012 at 1:37 Comment(0)
E
4

I actually found a much easier way to do this:

  1. Set up your foreach binding as you normally would for me it looked like this

    <div data-bind="foreach: promotions">
    
        <h3 data-bind="text: Title"></h3>
            <p>Creator:<span data-bind="text: Creator"></span></p> 
            <p>Effective Date:<span data-bind="text: EffectiveDate"></span></p>
            <span data-bind="text: Description"></span>
            <a data-bind="text: ButtonText, attr: {href: ButtonLink}"></a>
    

  2. Wrap that in a div with class="collapsible like so

    <div data-role="collapsible-set" data-bind="foreach: promotions">
    
    <div class="collapsible">
        <h3 data-bind="text: Title"></h3>
            <p>Creator:<span data-bind="text: Creator"></span></p> 
            <p>Effective Date:<span data-bind="text: EffectiveDate"></span></p>
            <span data-bind="text: Description"></span>
            <a data-bind="text: ButtonText, attr: {href: ButtonLink}"></a>
    

  3. Apply the collapsible widget via jquery mobile after you do your binding like so:

    $(document).ready(function () {
        ko.mapping.fromJS(data, dataMappingOptions, PromotionViewModel.promotions);
        ko.applyBindings(PromotionViewModel);
        $('.collapsible').collapsible();
    });
    
  4. For a collapsible set the same idea can be applied just set the class="collapsible-set" on your foreach div. Hope this helps

Extensive answered 11/12, 2012 at 17:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.