DoneJS StealJS jQuery++ FuncUnit DocumentJS
6.0.1
5.33.2 4.3.0 3.14.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
    • getting started
      • CRUD Guide
      • Setting Up CanJS
      • Technology Overview
    • topics
      • HTML
      • Routing
      • Service Layer
      • Debugging
      • Forms
      • Testing
      • Logic
      • Server-Side Rendering
    • app guides
      • Chat Guide
      • TodoMVC Guide
      • TodoMVC with StealJS
    • beginner recipes
      • Canvas Clock
      • Credit Card
      • File Navigator
      • Signup and Login
      • Video Player
    • intermediate recipes
      • CTA Bus Map
      • Multiple Modals
      • Text Editor
      • Tinder Carousel
    • advanced recipes
      • Credit Card
      • File Navigator
      • Playlist Editor
      • Search, List, Details
    • upgrade
      • Migrating to CanJS 3
      • Migrating to CanJS 4
      • Migrating to CanJS 5
      • Migrating to CanJS 6
      • Using Codemods
    • other
      • Reading the API Docs
  • API Docs
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

Multiple Modals

  • Edit on GitHub

This intermediate guide shows how to create a multiple modal form.

The final widget looks like:

See the Pen CanJS 6.0 - Multiple Modals - Final by Bitovi (@bitovi) on CodePen.

The following sections are broken down the following parts:

  • The problem - A description of what the section is trying to accomplish.

  • What you need to know - Information about CanJS that is useful for solving the problem.

  • How to verify it works - How to make sure the solution works if it’s not obvious.

  • The solution - The solution to the problem.

Setup

The problem

In this section, we will fork this CodePen that contains some starting code that we will modify to use modals instead of adding each form directly in the page.

What you need to know

The CodePen creates and several basic components:

  • <occupation-questions> - A form that asks the sorts of things the user does.
  • <diva-questions> - A form that asks for expenses for divas.
  • <programmer-questions> - A form that asks for a programmer's programming language.
  • <income-questions> - A form that asks how the user gets paid.
  • <my-app> - The main application component. It uses all of the above components to update its value.

<my-app> is mounted in the HTML tab as follows:

<my-app></my-app>

The solution

START THIS TUTORIAL BY CLONING THE FOLLOWING CODEPEN:

Click the EDIT ON CODEPEN button. The CodePen will open in a new window. In that new window, click FORK.

See the Pen CanJS 6.0 - Multiple Modals - Setup by Bitovi (@bitovi) on CodePen.

This CodePen:

  • Loads all of CanJS’s packages. Each package is available as a named export. For example can-stache-element is available as import { StacheElement } from "can".

Create a simple modal

The problem

In this section, we will:

  • Create a simple <my-modal> custom element that will put its "light DOM" within a modal window.
  • Show the <diva-questions> component when isDiva is set to true.

What you need to know

  • Use {{# if(value) }} HTML {{/ if }} to show HTML when value is true.

  • Content between custom element tags like:

    <custom-element>SOME CONTENT</custom-element>
    

    Is available to be rendered with the <content> element within the custom element’s view. The following would put SOME CONTENT within an <h1> element:

    class CustomElement extends StacheElement {
      static view = `<h1><content></content></h1>`;
    }
    customElements.define("custom-element", CustomElement);
    

How to verify it works

If you click the isDiva radio input, a modal window with the <diva-questions> form should appear.

The solution

Update the JS tab to:

import {
    stache,
    stacheConverters,
    StacheElement,
    type,
    ObservableArray
} from "//unpkg.com/can@6/ecosystem.mjs";

stache.addConverter(stacheConverters);

class OccupationQuestions extends StacheElement {
    static view = `
        <h3>Occupation</h3>
        <div class="content">
            <p>
                Are you a diva?
                <input id="diva-yes" type="radio" checked:bind="equal(this.isDiva, true)">
                <label for="diva-yes">yes</label>

                <input id="diva-no" type="radio" checked:bind="equal(this.isDiva, false)">
                <label for="diva-no">no</label>
            </p>
            <p>
            Do you program?
                <input
                    id="programmer-yes"
                    type="radio"
                    checked:bind="equal(this.isProgrammer, true)"
                >
                <label for="programmer-yes">yes</label>

                <input
                    id="programmer-no"
                    type="radio"
                    checked:bind="equal(this.isProgrammer, false)"
                >
                <label for="programmer-no">no</label>
            </p>
            <p><button on:click="this.next()">Next</button></p>
        </div>
    `;

    static props = {
        isDiva: Boolean,
        isProgrammer: Boolean
    };
}

customElements.define("occupation-questions", OccupationQuestions);

class DivaQuestions extends StacheElement {
    static view = `
        <h3>Diva Questions</h3>
        <div class="content">
            <p>Check all expenses that apply:</p>
            <p>
                <input
                    id="swagger"
                    type="checkbox"
                    checked:bind="boolean-to-inList('Swagger', this.divaExpenses)"
                >
                <label for="swagger">Swagger</label>
            </p>
            <p>
                <input
                    id="fame"
                    type="checkbox"
                    checked:bind="boolean-to-inList('Fame', this.divaExpenses)"
                >
                <label for="fame">Fame</label>
            </p>
            <p><button on:click="this.next()">Next</button></p>
        </div>
    `;

    static props = {
        divaExpenses: type.Any
    };
}

customElements.define("diva-questions", DivaQuestions);

class ProgrammerQuestions extends StacheElement {
    static view = `
        <h3>Programmer Questions</h3>
        <div class="content">
            <p>What is your favorite language?</p>
            <p>
                <select value:to="this.programmingLanguage">
                    <option>C</option>
                    <option>C++</option>
                    <option>Java</option>
                    <option>JavaScript</option>
                </select>
            </p>
            <p><button on:click="this.next()">Next</button></p>
        </div>
    `;

    static props = {
        programmingLanguage: String
    };
}

customElements.define("programmer-questions", ProgrammerQuestions);

class IncomeQuestions extends StacheElement {
    static view = `
        <h3>Income</h3>
        <div class="content">
            <p>What do you get paid in?</p>
            <p>
                <select value:bind="string-to-any(this.paymentType)">
                    <option value="undefined">Select a type</option>
                    <option>Peanuts</option>
                    <option>Bread</option>
                    <option>Tamales</option>
                    <option>Cheddar</option>
                    <option>Dough</option>
                </select>
            </p>
            <p><button on:click="this.next()">Finish</button></p>
        </div>
    `;

    static props = {
        paymentType: type.maybeConvert(String)
    };
}

customElements.define("income-questions", IncomeQuestions);

class MyModals extends StacheElement {
    static view = `
        <div class="background"></div>
        <div class="modal-container">
            <content>Supply some content</content>
        </div>
    `;
}

customElements.define("my-modals", MyModals);

class MyApp extends StacheElement {
    static view = `
        <occupation-questions 
            isDiva:bind="this.isDiva"
            isProgrammer:bind="this.isProgrammer"
        />
        <p>isDiva: {{ this.isDiva }}</p>
        <p>isProgrammer: {{ this.isProgrammer }}</p>

        {{# if(this.isDiva) }}
            <my-modals>
                <diva-questions divaExpenses:bind="this.divaExpenses" />
            </my-modals>
        {{/ if }}
        <p>diva expenses: {{ this.divaExpenses.join(', ') }}</p>

        <programmer-questions programmingLanguage:bind="this.programmingLanguage" />
        <p>programmingLanguage: {{ this.programmingLanguage }}</p>

        <income-questions paymentType:bind="this.paymentType" />
        <p>paymentType: {{ this.paymentType }}</p>
    `;

    static props = {
        // Stateful properties
        isDiva: false,

        divaExpenses: {
            get default() {
                return new ObservableArray();
            }
        },

        isProgrammer: false,
        programmingLanguage: String,
        paymentType: String

        // Derived properties
    };

    // Methods
}

customElements.define("my-app", MyApp);

Pass a component instance

The problem

In this section, we are matching the same behavior as the previous example. However, we are going to change the <my-modals> component to take a component instance to render in a modal instead of "light DOM".

What you need to know

  • Use { get default() { /* ... */ }} to create a default value for a property:

    class MyComponent extends StacheElement {
      static props = {
        dueDate: {
          get default(){
            return new Date();
          }
        }
      };
    }
    
  • Component instances can be created like:

    const component = new ProgrammerQuestions().initialize({
      programmingLanguage: "JS"
    });
    

    This is roughly equivalent to:

    <programmer-questions programmingLanguage:from="'JS'" />
    
  • Use can-value and bindings to setup a two-way binding from one component to another:

    static props = {
      get default() {
        return new ProgrammerQuestions().bindings({
          programmingLanguage: value.bind(this, "programmingLanguage")
        });
      }
    };
    

    This is roughly equivalent to:

    <programmer-questions programmingLanguage:bind="this.programmingLanguage" />
    
  • Render a component instance with {{ component }}.

The solution

Update the JS tab to:

import {
    stache,
    stacheConverters,
    StacheElement,
    type,
    ObservableArray
} from "//unpkg.com/can@6/ecosystem.mjs";

stache.addConverter(stacheConverters);

class OccupationQuestions extends StacheElement {
    static view = `
        <h3>Occupation</h3>
        <div class="content">
            <p>
                Are you a diva?
                <input id="diva-yes" type="radio" checked:bind="equal(this.isDiva, true)">
                <label for="diva-yes">yes</label>

                <input id="diva-no" type="radio" checked:bind="equal(this.isDiva, false)">
                <label for="diva-no">no</label>
            </p>
            <p>
            Do you program?
                <input
                    id="programmer-yes"
                    type="radio"
                    checked:bind="equal(this.isProgrammer, true)"
                >
                <label for="programmer-yes">yes</label>

                <input
                    id="programmer-no"
                    type="radio"
                    checked:bind="equal(this.isProgrammer, false)"
                >
                <label for="programmer-no">no</label>
            </p>
            <p><button on:click="this.next()">Next</button></p>
        </div>
    `;

    static props = {
        isDiva: Boolean,
        isProgrammer: Boolean
    };
}

customElements.define("occupation-questions", OccupationQuestions);

class DivaQuestions extends StacheElement {
    static view = `
        <h3>Diva Questions</h3>
        <div class="content">
            <p>Check all expenses that apply:</p>
            <p>
                <input
                    id="swagger"
                    type="checkbox"
                    checked:bind="boolean-to-inList('Swagger', this.divaExpenses)"
                >
                <label for="swagger">Swagger</label>
            </p>
            <p>
                <input
                    id="fame"
                    type="checkbox"
                    checked:bind="boolean-to-inList('Fame', this.divaExpenses)"
                >
                <label for="fame">Fame</label>
            </p>
            <p><button on:click="this.next()">Next</button></p>
        </div>
    `;

    static props = {
        divaExpenses: type.Any
    };
}

customElements.define("diva-questions", DivaQuestions);

class ProgrammerQuestions extends StacheElement {
    static view = `
        <h3>Programmer Questions</h3>
        <div class="content">
            <p>What is your favorite language?</p>
            <p>
                <select value:to="this.programmingLanguage">
                    <option>C</option>
                    <option>C++</option>
                    <option>Java</option>
                    <option>JavaScript</option>
                </select>
            </p>
            <p><button on:click="this.next()">Next</button></p>
        </div>
    `;

    static props = {
        programmingLanguage: String
    };
}

customElements.define("programmer-questions", ProgrammerQuestions);

class IncomeQuestions extends StacheElement {
    static view = `
        <h3>Income</h3>
        <div class="content">
            <p>What do you get paid in?</p>
            <p>
                <select value:bind="string-to-any(this.paymentType)">
                    <option value="undefined">Select a type</option>
                    <option>Peanuts</option>
                    <option>Bread</option>
                    <option>Tamales</option>
                    <option>Cheddar</option>
                    <option>Dough</option>
                </select>
            </p>
            <p><button on:click="this.next()">Finish</button></p>
        </div>
    `;

    static props = {
        paymentType: type.maybeConvert(String)
    };
}

customElements.define("income-questions", IncomeQuestions);

class MyModals extends StacheElement {
    static view = `
        <div class="background"></div>
        <div class="modal-container">
            {{ this.component }}
        </div>
    `;
}

customElements.define("my-modals", MyModals);

class MyApp extends StacheElement {
    static view = `
        <occupation-questions 
            isDiva:bind="this.isDiva"
            isProgrammer:bind="this.isProgrammer"
        />
        <p>isDiva: {{ this.isDiva }}</p>
        <p>isProgrammer: {{ this.isProgrammer }}</p>

        {{# if(this.isDiva) }}
            <my-modals component:from="this.divaQuestions"></my-modals>
        {{/ if }}
        <p>diva expenses: {{ this.divaExpenses.join(', ') }}</p>

        <programmer-questions programmingLanguage:bind="this.programmingLanguage" />
        <p>programmingLanguage: {{ this.programmingLanguage }}</p>

        <income-questions paymentType:bind="this.paymentType" />
        <p>paymentType: {{ this.paymentType }}</p>
    `;

    static props = {
        // Stateful properties
        isDiva: false,

        divaExpenses: {
            get default() {
                return new ObservableArray();
            }
        },

        isProgrammer: false,
        programmingLanguage: String,
        paymentType: String,

        divaQuestions: {
            get default() {
                return new DivaQuestions().bindings({
                    divaExpenses: value.bind(this, "divaExpenses")
                });
            }
        },
        // Derived properties
    };

    // Methods
}

customElements.define("my-app", MyApp);

Show multiple modals in the window

The problem

In this section, we will:

  • Show all form components within a modal box.
  • Show the <diva-questions> and <programmer-questions> modals only if their respective questions (isDiva and isProgrammer) checkboxes are selected.
  • Remove all the form components from being rendered in the main page content area.

We will do this by:

  • Changing <my-modals> to:
    • take an array of component instances.
    • position the component instances within <div class="modal-container"> elements 20 pixels apart.
  • Changing <my-app> to:
    • create instances for the OccupationQuestions, ProgrammerQuestions, and IncomeQuestions components.
    • create a visibleQuestions array that contains only the instances that should be presented to the user.

What you need to know

  • Use ES5 getters to transform component's stateful properties to new values. For example, the following returns true if someone is a diva and a programmer:

    static props = {
      isDiva: Boolean,
      isProgrammer: Boolean,
      get isDivaAndProgrammer() {
        return this.isDiva && this.isProgrammer;
      }
    };
    

    This can be used to derive the visibleQuestions array.

The solution

Update the JS tab to:

import {
    stache,
    stacheConverters,
    StacheElement,
    type,
    ObservableArray
} from "//unpkg.com/can@6/ecosystem.mjs";

stache.addConverter(stacheConverters);

class OccupationQuestions extends StacheElement {
    static view = `
        <h3>Occupation</h3>
        <div class="content">
            <p>
                Are you a diva?
                <input id="diva-yes" type="radio" checked:bind="equal(this.isDiva, true)">
                <label for="diva-yes">yes</label>

                <input id="diva-no" type="radio" checked:bind="equal(this.isDiva, false)">
                <label for="diva-no">no</label>
            </p>
            <p>
            Do you program?
                <input
                    id="programmer-yes"
                    type="radio"
                    checked:bind="equal(this.isProgrammer, true)"
                >
                <label for="programmer-yes">yes</label>

                <input
                    id="programmer-no"
                    type="radio"
                    checked:bind="equal(this.isProgrammer, false)"
                >
                <label for="programmer-no">no</label>
            </p>
            <p><button on:click="this.next()">Next</button></p>
        </div>
    `;

    static props = {
        isDiva: Boolean,
        isProgrammer: Boolean
    };
}

customElements.define("occupation-questions", OccupationQuestions);

class DivaQuestions extends StacheElement {
    static view = `
        <h3>Diva Questions</h3>
        <div class="content">
            <p>Check all expenses that apply:</p>
            <p>
                <input
                    id="swagger"
                    type="checkbox"
                    checked:bind="boolean-to-inList('Swagger', this.divaExpenses)"
                >
                <label for="swagger">Swagger</label>
            </p>
            <p>
                <input
                    id="fame"
                    type="checkbox"
                    checked:bind="boolean-to-inList('Fame', this.divaExpenses)"
                >
                <label for="fame">Fame</label>
            </p>
            <p><button on:click="this.next()">Next</button></p>
        </div>
    `;

    static props = {
        divaExpenses: type.Any
    };
}

customElements.define("diva-questions", DivaQuestions);

class ProgrammerQuestions extends StacheElement {
    static view = `
        <h3>Programmer Questions</h3>
        <div class="content">
            <p>What is your favorite language?</p>
            <p>
                <select value:to="this.programmingLanguage">
                    <option>C</option>
                    <option>C++</option>
                    <option>Java</option>
                    <option>JavaScript</option>
                </select>
            </p>
            <p><button on:click="this.next()">Next</button></p>
        </div>
    `;

    static props = {
        programmingLanguage: String
    };
}

customElements.define("programmer-questions", ProgrammerQuestions);

class IncomeQuestions extends StacheElement {
    static view = `
        <h3>Income</h3>
        <div class="content">
            <p>What do you get paid in?</p>
            <p>
                <select value:bind="string-to-any(this.paymentType)">
                    <option value="undefined">Select a type</option>
                    <option>Peanuts</option>
                    <option>Bread</option>
                    <option>Tamales</option>
                    <option>Cheddar</option>
                    <option>Dough</option>
                </select>
            </p>
            <p><button on:click="this.next()">Finish</button></p>
        </div>
    `;

    static props = {
        paymentType: type.maybeConvert(String)
    };
}

customElements.define("income-questions", IncomeQuestions);

class MyModals extends StacheElement {
    static view = `
        {{# for(componentData of this.componentsToShow) }}
            {{# if(componentData.last) }}
                <div class="background"></div>
            {{/ if }}
            <div
                class="modal-container"
                style="margin-top: {{ this.componentData.position }}px; margin-left: {{ this.componentData.position }}px"
            >
                {{ componentData.component }}
            </div>
        {{/ for }}
    `;

    static props = {
        get componentsToShow() {
            let distance = 20;
            let count = this.components.length;
            let start = -150 - distance / 2 * (count - 1);

            return this.components.map(function(component, i) {
                return {
                    position: start + i * distance,
                    component: component,
                    last: i === count - 1
                };
            });
        }
    };
}

customElements.define("my-modals", MyModals);

class MyApp extends StacheElement {
    static view = `
        <my-modals components:from="this.visibleQuestions"></my-modals>

        <p>isDiva: {{ this.isDiva }}</p>
        <p>isProgrammer: {{ this.isProgrammer }}</p>
        <p>diva expenses: {{ this.divaExpenses.join(', ') }}</p>
        <p>programmingLanguage: {{ this.programmingLanguage }}</p>
        <p>paymentType: {{ this.paymentType }}</p>
    `;

    static props = {
        // Stateful properties
        isDiva: false,

        divaExpenses: {
            get default() {
                return new ObservableArray();
            }
        },

        isProgrammer: false,
        programmingLanguage: String,
        paymentType: String,

        occupationQuestions: {
            get default() {
                return new OccupationQuestions().bindings({
                    isDiva: value.bind(this, "isDiva"),
                    isProgrammer: value.bind(this, "isProgrammer")
                });
            }
        },

        divaQuestions: {
            get default() {
                return new DivaQuestions().bindings({
                    divaExpenses: value.bind(this, "divaExpenses")
                });
            }
        },

        programmerQuestions: {
            get default() {
                return new ProgrammerQuestions().bindings({
                    programmingLanguage: value.bind(this, "programmingLanguage")
                });
            }
        },

        incomeQuestions: {
            get default() {
                return new IncomeQuestions().bindings({
                    paymentType: value.bind(this, "paymentType")
                });
            }
        },

        // Derived properties
        get allQuestions() {
            var forms = [this.occupationQuestions];
            if (this.isDiva) {
                forms.push(this.divaQuestions);
            }
            if (this.isProgrammer) {
                forms.push(this.programmerQuestions);
            }
            forms.push(this.incomeQuestions);

            return new ObservableArray(forms);
        },

        get visibleQuestions() {
            return this.allQuestions.slice(0).reverse();
        }
    };

    // Methods
}

customElements.define("my-app", MyApp);

Next should move to the next window

The problem

In this section, we will make it so when someone clicks the Next button in a modal, the next modal window will be displayed.

What you need to know

We can use a index of which question we have answered to know which questions should be returned by visibleQuestions.

The following creates a counting index and a method that increments it:

class MyModals extends StacheElement {
  // ...
  static props = {
    questionIndex: { default: 0 }
  };

  next() {
    this.questionIndex += 1;
  }
}

To pass the next function to a component, you must make sure that the right this is preserved. You can do that with function.bind like:

new ProgrammerQuestions().bindings({
  programmingLanguage: value.bind(this, "programmingLanguage"),
  next: this.next.bind(this)
});

The solution

import {
    ObservableArray,
    stache,
    stacheConverters,
    StacheElement,
    type,
    value
} from "//unpkg.com/can@pre/ecosystem.mjs";

stache.addConverter(stacheConverters);

class OccupationQuestions extends StacheElement {
    static view = `
    <h3>Occupation</h3>
    <div class="content">
      <p>
        Are you a diva?
        <input id="diva-yes" type="radio" checked:bind="equal(this.isDiva, true)">
        <label for="diva-yes">yes</label>

        <input id="diva-no" type="radio" checked:bind="equal(this.isDiva, false)">
        <label for="diva-no">no</label>
      </p>
      <p>
        Do you program?
        <input
          id="programmer-yes"
          type="radio"
          checked:bind="equal(this.isProgrammer, true)"
        >
        <label for="programmer-yes">yes</label>

        <input
          id="programmer-no"
          type="radio"
          checked:bind="equal(this.isProgrammer, false)"
        >
        <label for="programmer-no">no</label>
      </p>
      <p><button on:click="this.next()">Next</button></p>
    </div>
  `;

    static props = {
        isDiva: Boolean,
        isProgrammer: Boolean
    };
}

customElements.define("occupation-questions", OccupationQuestions);

class DivaQuestions extends StacheElement {
    static view = `
    <h3>Diva Questions</h3>
    <div class="content">
      <p>Check all expenses that apply:</p>
      <p>
        <input
          id="swagger"
          type="checkbox"
          checked:bind="boolean-to-inList('Swagger', this.divaExpenses)"
        >
        <label for="swagger">Swagger</label>
      </p>
      <p>
        <input
          id="fame"
          type="checkbox"
          checked:bind="boolean-to-inList('Fame', this.divaExpenses)"
        >
        <label for="fame">Fame</label>
      </p>
      <p><button on:click="this.next()">Next</button></p>
    </div>
  `;

    static props = {
        divaExpenses: type.Any
    };
}

customElements.define("diva-questions", DivaQuestions);

class ProgrammerQuestions extends StacheElement {
    static view = `
    <h3>Programmer Questions</h3>
    <div class="content">
      <p>What is your favorite language?</p>
      <p>
        <select value:to="this.programmingLanguage">
          <option>C</option>
          <option>C++</option>
          <option>Java</option>
          <option>JavaScript</option>
        </select>
      </p>
      <p><button on:click="this.next()">Next</button></p>
    </div>
  `;

    static props = {
        programmingLanguage: String
    };
}

customElements.define("programmer-questions", ProgrammerQuestions);

class IncomeQuestions extends StacheElement {
    static view = `
    <h3>Income</h3>
    <div class="content">
      <p>What do you get paid in?</p>
      <p>
        <select value:bind="string-to-any(this.paymentType)">
          <option value="undefined">Select a type</option>
          <option>Peanuts</option>
          <option>Bread</option>
          <option>Tamales</option>
          <option>Cheddar</option>
          <option>Dough</option>
        </select>
      </p>
      <p><button on:click="this.next()">Finish</button></p>
    </div>
  `;

    static props = {
        paymentType: type.maybeConvert(String)
    };
}

customElements.define("income-questions", IncomeQuestions);

class MyModals extends StacheElement {
    static view = `
    {{# for(component of componentsToShow) }}
      {{# if(component.last) }}
        <div class="background"></div>
      {{/ if }}
      <div
        class="modal-container"
        style="margin-top: {{ component.position }}px; margin-left: {{ component.position }}px"
      >
        {{ component.content }}
      </div>
    {{/ for }}
  `;

    static props = {
        get componentsToShow() {
            const distance = 20;
            const count = this.components.length;
            const start = -150 - distance / 2 * (count - 1);

            return this.components.map(function(component, i) {
                return {
                    position: start + i * distance,
                    content: component,
                    last: i === count - 1
                };
            });
        }
    };
}

customElements.define("my-modals", MyModals);

class MyApp extends StacheElement {
    static view = `
    <my-modals components:from="this.visibleQuestions"></my-modals>

    <p>isDiva: {{ this.isDiva }}</p>
    <p>isProgrammer: {{ this.isProgrammer }}</p>
    <p>diva expenses: {{ this.divaExpenses.join(', ') }}</p>
    <p>programmingLanguage: {{ this.programmingLanguage }}</p>
    <p>paymentType: {{ this.paymentType }}</p>
  `;

    static props = {
        // Stateful properties
        isDiva: { type: Boolean, default: false },

        divaExpenses: {
            get default() {
                return new ObservableArray();
            }
        },

        isProgrammer: { type: Boolean, default: false },
        programmingLanguage: type.maybeConvert(String),
        paymentType: String,

        occupationQuestions: {
            get default() {
                return new OccupationQuestions().bindings({
                    isDiva: value.bind(this, "isDiva"),
                    isProgrammer: value.bind(this, "isProgrammer"),
                    next: this.next.bind(this)
                });
            }
        },

        divaQuestions: {
            get default() {
                return new DivaQuestions().bindings({
                    divaExpenses: value.bind(this, "divaExpenses"),
                    next: this.next.bind(this)
                });
            }
        },

        programmerQuestions: {
            get default() {
                return new ProgrammerQuestions().bindings({
                    programmingLanguage: value.bind(this, "programmingLanguage"),
                    next: this.next.bind(this)
                });
            }
        },

        incomeQuestions: {
            get default() {
                return new IncomeQuestions().bindings({
                    paymentType: value.bind(this, "paymentType"),
                    next: this.next.bind(this)
                });
            }
        },

        questionIndex: { default: 0 },

        // Derived properties
        get allQuestions() {
            var forms = [this.occupationQuestions];
            if (this.isDiva) {
                forms.push(this.divaQuestions);
            }
            if (this.isProgrammer) {
                forms.push(this.programmerQuestions);
            }
            forms.push(this.incomeQuestions);

            return new ObservableArray(forms);
        },

        get visibleQuestions() {
            return this.allQuestions.slice(this.questionIndex).reverse();
        }
    };

    // Methods
    next() {
        this.questionIndex += 1;
    }
}

customElements.define("my-app", MyApp);

Result

When complete, you should have a working multiple modal form like the following CodePen:

See the Pen CanJS 6.0 - Multiple Modals - Final by Bitovi (@bitovi) on CodePen.

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 6.0.1.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news