---
title: Weekly Widget 1 - TreeCombo
description: Weekly Widget 1 - TreeCombo
image: https://www.bitovi.com/hubfs/canjs-banner-background.jpg
---

- ![AI implementation](https://www.bitovi.com/hubfs/AIConsultingIcon.svg)
  
  [AI implementation](https://www.bitovi.com/services/ai-consulting)
- ![Systems engineering](https://www.bitovi.com/hubfs/icon%20-%20backend.svg)
  
  [Systems engineering](https://www.bitovi.com/services/systems-engineering-consulting)
- ![Project Management](https://www.bitovi.com/hubfs/icon%20-%20PM.svg)
  
  [Project Management](https://www.bitovi.com/services/agile-project-management-consulting)
- ![Product Design](https://www.bitovi.com/hubfs/icon%20-%20design.svg)
  
  [Product Design](https://www.bitovi.com/services/product-design-consulting)
- ![Frontend development](https://www.bitovi.com/hubfs/icon%20-%20frontend.svg)
  
  [Frontend development](https://www.bitovi.com/services/frontend-development-consulting)
- [View more
  
  →
  
  ](https://www.bitovi.com/digital-consulting-services)

We're Experts in...

- [JavaScript](https://www.bitovi.com/services/frontend/javascript-consulting)
- [AI training](https://www.bitovi.com/ai-training-for-software-engineers)
- [Angular](https://www.bitovi.com/services/frontend/angular-consulting)
- [Design systems](https://www.bitovi.com/services/axure-figma-migration)
- [React](https://www.bitovi.com/services/frontend/react-consulting)
- [Temporal](https://www.bitovi.com/services/backend/temporal-consulting)
- [React Native](https://www.bitovi.com/services/frontend/react-consulting/react-native)
- [Node.js](https://www.bitovi.com/services/backend/nodejs-consulting)

Showcase

![Yum! Brands](https://www.bitovi.com/hubfs/yum-showcase-link-1.png)

[View case study](https://www.bitovi.com/en/bitovi-yum-case-study)

More Projects

- [![Levi's](https://www.bitovi.com/hubfs/levis.svg)](https://www.bitovi.com/web-application-consulting-work/levis-ecommerce-responsive-redesign)
- [![Christie's International Real Estate](https://www.bitovi.com/hubfs/christies.svg)](https://design.bitovi.com/christies)
- [![BAFS](https://www.bitovi.com/hubfs/bafs.svg)](https://www.bitovi.com/ux-design-consulting/ux-case-studies/bafs-ppp)
- [View more
  
  →
  
  ](https://www.bitovi.com/our-software-consulting-work)

Open Source Tools

We build powerful tools and open source them to support the community.

[See what we've built →](https://www.bitovi.com/open-source)

- [![Blog](https://www.bitovi.com/hubfs/icon%20-%20blog.svg)
  
  BlogWe post about delivering products and solving problems.
  
  ](https://www.bitovi.com/blog)
- [![Partnerships](https://www.bitovi.com/hubfs/Handshake-1.svg)
  
  PartnershipsLearn about Bitovi's technology partners
  
  ](https://www.bitovi.com/partnerships)
- [![Academy](https://www.bitovi.com/hubfs/icon%20-%20academy%20(4).svg)
  
  AcademyFree courses to build delivery skills
  
  ](https://www.bitovi.com/academy)
- [![Open source tools](https://www.bitovi.com/hubfs/icon%20-%20open%20source.svg)
  
  Open source toolsUse or contribute to our community
  
  ](https://www.bitovi.com/open-source)

Let's Connect

- [![Discord](https://www.bitovi.com/hubfs/DiscordLogo.svg)
  
  Discord
  
  ](https://discord.gg/J7ejFsZnJ4)
- [![LinkedIn](https://www.bitovi.com/hubfs/LinkedinLogo.svg)
  
  LinkedIn
  
  ](https://www.linkedin.com/company/bitovi/)
- [![GitHub](https://www.bitovi.com/hubfs/GithubLogo.svg)
  
  GitHub
  
  ](https://github.com/bitovi/)

![Eggbot](https://www.bitovi.com/hubfs/build_assets/bitovi-limbo-cms-react/338/js_client_assets/assets/eggbot-LTGhdSGL.png)

Name *

Work Email *

Phone

What's your project?

Send

### Contact Us

(312) 620-0386contact@bitovi.com

 January 18, 2013

# Weekly Widget 1 - TreeCombo

 Weekly Widget 1 - TreeCombo

![Justin Meyer](https://www.bitovi.com/hubfs/justin.jpeg)

 Justin Meyer

Share:

[![Twitter](https://www.bitovi.com/hubfs/limbo-generated/_astro/twitter-white.os3xLc3C_Z2nW4or.svg) ](https://twitter.com/intent/tweet?text=) [![Reddit](https://www.bitovi.com/hubfs/limbo-generated/imgs/icons/reddit.png) ](http://reddit.com/submit?url=)

I, [@justinbmeyer](http://twitter.com/justinbmeyer), am going to post a weekly widget made with CanJS. I hope to continue this for as long as I have widgets to write. If you **want to see something**, please tweet it to [@canjs](http://twitter.com/justinbmeyer). These posts are going to be quick and dirty. Eventually, I will put these up on [CanJS's recipe page](http://canjs.us/recipes.html) with a full description.

I'm starting with the `TreeCombo` below:

JSFiddle

## What it does

The tree combo allows you to select items within and navigate through a hierarchial collection of data. Click "→" to see child items. Click the breadcrumb navigation at the top to return to parent items.

It also displays a list of the items the user has selected under "You've selected:".

## How it works

A `TreeCombo` control is created like:

```
new TreeCombo("#treeCombo", {
  items: new can.Observe.List(data),
  title: "All Content",
  selected: selected
});
```

Where:

- items - an observable list of hierarchial data
- title - the text for the "return to top level text"
- selected - an observable list of items selected by the widget

The trick is to maintain the state of the items the user has navigated through as `this.options.breadcrumb`. When a user clicks the "→" button, I add the corresponding item to `breadcrumb` with:

```
this.options.breadcrumb.push(el.closest('li').data('item'));
```

When someone clicks a breadcrumb, I remove all the items in `breadcrumb` after the clicked breadcrumb item:

```
".breadcrumb li click": function(el){
  var item = el.data('item');
  // if you clicked on a breadcrumb li with data
  if(item){
    // remove all breadcrumb items after it
    var index = this.options.breadcrumb.indexOf(item);
    this.options.breadcrumb.
      splice(index+1, 
             this.options.breadcrumb.length-index-1)
  } else {
    // clear the breadcrumb
    this.options.breadcrumb.replace([])
  }
}
```

Prior to this, using live-binding, I've setup the page to automatically update when `breadcrumb` changes. The items selectable to the user are always the last item in the breadcrumb's children. I made a compute to calculate this with:

```
var selectableItems = can.compute(function(){

  // if there's an item in the breadcrumb
  if(this.options.breadcrumb.attr('length')){

    // return the last item's children
    return this.options.breadcrumb
       .attr(""+(this.options.breadcrumb.length-1))
       .attr('children');
  } else{

    // return the top list of items
    return this.options.items;
  }
}, this);
```

Next, I render the template which list each `breadcrumb` item and `selectableItems` item:

```
<ul>
  <% breadcrumb.each(function(item){ %>
    <li <% (el)-> el.data('item', item) %>> <%= item.attr('title') %> </li>
  <%})%> 
  <% selectableItems().each(function(item){ %>
  <li class='<%= selected.indexOf(item) >= 0 ? “checked”:”” %>’ <%=(el)-> el.data(‘item’,item) %> ></p>
  <pre><code>        <input type="checkbox"
                 <%= selected.indexOf(item) >= 0 ? 
                     "checked":""%>>

          <%= item.attr('title') %>

          <%if(item.children && item.children.length){ %>
              <button class="showChildren">→</button>
          <%}%>
      </li>
  <% }) %>
</ul>
```

Selection is handled by adding and removing items in `this.options.selected`:

```
".options li click": function(el){
  // toggles an item's existance in the selected array
  var item = el.data('item'),
      index = this.options.selected.indexOf(item);
  if(index === -1 ){
    this.options.selected.push(item);
  } else {
    this.options.selected.splice(index, 1) 
  }
}
```

The live-binding template above includes:

```
<li class='<%= selected.indexOf(item) >= 0 ? "checked":"" %>'>
```

Which handles adding a "checked" class that highlights checked rows.

Finally, at the end of the script, I use another template to show what's selected:

```
$("#selected").html(can.view('selectedEJS', selected));
```

This also changes auto-magically via live-binding.

## Conclusion

Seriously ... how awesome is this! Observes and computes can make formerly tricky widgets almost stupidly easy. The hard part is determining how you want to represet state.

Give me some suggestions for next week!

[![Tag for open source](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) open source ](https://www.bitovi.com/blog/topic/open-source)[![Tag for development](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) development ](https://www.bitovi.com/blog/topic/development)

 Previous Post

![js-banner](https://www.bitovi.com/hs-fs/hubfs/js-banner.jpg?height=117&name=js-banner.jpg) [ Introducing jQuery++ ](https://www.bitovi.com/blog/introducing-jquery)

  

 Next Post

![canjs-banner-background](https://www.bitovi.com/hs-fs/hubfs/canjs-banner-background.jpg?height=117&name=canjs-banner-background.jpg) [ Weekly Widget 2 - 2-Way Mustache Helpers ](https://www.bitovi.com/blog/weekly-widget-2-2-way-mustache-helpers)

```json
{
  "@context" : "http://schema.org",
  "@type" : "Organization",
  "address" : {
    "@type" : "PostalAddress",
    "addressCountry" : "United States",
    "addressLocality" : "Libertyville",
    "addressRegion" : "IL",
    "postalCode" : "60048",
    "streetAddress" : "1134 Pine Tree Lane "
  },
  "alternateName" : "Bitovi",
  "areaServed" : {
    "@type" : "GeoCircle",
    "geoMidpoint" : {
      "@type" : "GeoCoordinates",
      "latitude" : "41.8781",
      "longitude" : "87.6298"
    },
    "geoRadius" : "5000 km"
  },
  "description" : "Bitovi is a UX, UI design and front-end JavaScript development consulting company",
  "email" : "contact@bitovi.com",
  "image" : "https://www.bitovi.com/hubfs/bitovi-logo-x2.png",
  "logo" : "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg",
  "mainEntityOfPage" : {
    "@id" : "https://www.bitovi.com/blog/weekly-widget-1-treecombo",
    "@type" : "WebPage",
    "description" : "Weekly Widget 1 - TreeCombo"
  },
  "naics" : "541511",
  "name" : "Bitovi Web App Consulting",
  "sameAs" : [ "https://www.facebook.com/BitoviLLC/", "https://twitter.com/bitovi", "https://www.linkedin.com/company/bitovi" ],
  "telephone" : "312-620-0386",
  "url" : "http://bitovi.com"
}
```

```json
{
  "@context" : "http://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Justin Meyer"
  },
  "dateModified" : "November 29, 2018, 5:53:11 PM",
  "datePublished" : "2013-01-18 20:31:03",
  "description" : "Weekly Widget 1 - TreeCombo",
  "headline" : "Weekly Widget 1 - TreeCombo",
  "image" : {
    "@type" : "ImageObject",
    "url" : "https://www.bitovi.com/hubfs/canjs-banner-background.jpg"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg"
    },
    "name" : "Bitovi"
  }
}
```