From ef6d4641112e6c78da105bc9ba6c5c72f7994add Mon Sep 17 00:00:00 2001 From: Foxandxss Date: Sat, 26 Mar 2016 00:41:45 +0100 Subject: [PATCH] docs(cb-select-box-component): cookbook about creating a select box --- .../cb-select-box-component/e2e-spec.ts | 164 +++++++++++++++++ .../cb-select-box-component/ts/.gitignore | 1 + .../ts/app/app.component.1.ts | 12 ++ .../ts/app/app.component.ts | 14 ++ .../ts/app/app.module.ts | 21 +++ .../ts/app/hero-store.service.ts | 26 +++ .../cb-select-box-component/ts/app/main.ts | 5 + .../ts/app/select-verbose.component.1.html | 14 ++ .../ts/app/select-verbose.component.1.ts | 21 +++ .../ts/app/select-verbose.component.html | 22 +++ .../ts/app/select-verbose.component.ts | 52 ++++++ .../ts/app/selector-host.component.html | 39 ++++ .../ts/app/selector-host.component.ts | 60 +++++++ .../ts/app/selector.component.ts | 47 +++++ .../cb-select-box-component/ts/demo.css | 12 ++ .../ts/example-config.json | 0 .../cb-select-box-component/ts/index.html | 29 +++ .../cb-select-box-component/ts/plnkr.json | 9 + public/docs/ts/latest/cookbook/_data.json | 5 + .../latest/cookbook/select-box-component.jade | 167 ++++++++++++++++++ .../select-box-component/change-update.gif | Bin 0 -> 80438 bytes .../select-box-component/first-try.gif | Bin 0 -> 75818 bytes .../select-box-component/herofan.gif | Bin 0 -> 67584 bytes .../select-box-component/verbose-buttons.gif | Bin 0 -> 192485 bytes 24 files changed, 720 insertions(+) create mode 100644 public/docs/_examples/cb-select-box-component/e2e-spec.ts create mode 100644 public/docs/_examples/cb-select-box-component/ts/.gitignore create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/app.component.1.ts create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/app.component.ts create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/app.module.ts create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/hero-store.service.ts create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/main.ts create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.html create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.ts create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.html create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.ts create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.html create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.ts create mode 100644 public/docs/_examples/cb-select-box-component/ts/app/selector.component.ts create mode 100644 public/docs/_examples/cb-select-box-component/ts/demo.css create mode 100644 public/docs/_examples/cb-select-box-component/ts/example-config.json create mode 100644 public/docs/_examples/cb-select-box-component/ts/index.html create mode 100644 public/docs/_examples/cb-select-box-component/ts/plnkr.json create mode 100644 public/docs/ts/latest/cookbook/select-box-component.jade create mode 100644 public/resources/images/cookbooks/select-box-component/change-update.gif create mode 100644 public/resources/images/cookbooks/select-box-component/first-try.gif create mode 100644 public/resources/images/cookbooks/select-box-component/herofan.gif create mode 100644 public/resources/images/cookbooks/select-box-component/verbose-buttons.gif diff --git a/public/docs/_examples/cb-select-box-component/e2e-spec.ts b/public/docs/_examples/cb-select-box-component/e2e-spec.ts new file mode 100644 index 0000000000..768631bd88 --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/e2e-spec.ts @@ -0,0 +1,164 @@ +/// +'use strict'; + +const heroes = [ + {'id': 11, 'name': 'Mr. Nice'}, + {'id': 12, 'name': 'Narco'}, + {'id': 13, 'name': 'Bombasto'}, + {'id': 14, 'name': 'Celeritas'}, + {'id': 15, 'name': 'Magneta'}, + {'id': 16, 'name': 'RubberMan'}, + {'id': 17, 'name': 'Dynama'}, + {'id': 18, 'name': 'Dr IQ'}, + {'id': 19, 'name': 'Magma'}, + {'id': 20, 'name': 'Tornado'} +]; + +describe('Select box component', function () { + + beforeAll(function () { + browser.get(''); + }); + + describe('Verbose select', function () { + let verboseComponent: protractor.ElementFinder; + let firstSelect: protractor.ElementFinder; + + beforeEach(function () { + verboseComponent = element.all(by.tagName('my-select-verbose')).get(0); + firstSelect = verboseComponent.all(by.tagName('select')).get(0); + }); + + it('should show a selected hero both in the select and outside it', function () { + firstSelect.getAttribute('value').then(function (hero: any) { + expect(heroes[hero].name).toEqual('Narco'); + }); + + const heroOnPTag = verboseComponent.all(by.tagName('p')).last().getText(); + expect(heroOnPTag).toContain('Narco'); + }); + + it('should change the selected hero on option change', function () { + firstSelect.all(by.cssContainingText('option', 'Bombasto')).first().click(); + + firstSelect.getAttribute('value').then(function (hero: any) { + expect(heroes[hero].name).toEqual('Bombasto'); + }); + + const heroOnPTag = verboseComponent.all(by.tagName('p')).last().getText(); + expect(heroOnPTag).toContain('Bombasto'); + }); + + it('should change select collection on button clicks', function () { + const reloadButton = verboseComponent.all(by.buttonText('Reload')); + const clearButton = verboseComponent.all(by.buttonText('Clear')); + const removeButton = verboseComponent.all(by.buttonText('Remove')); + + let options = firstSelect.all(by.tagName('option')); + expect(options.count()).toBe(10); + + removeButton.click().then(function () { + options = firstSelect.all(by.tagName('option')); + expect(options.count()).toBe(9); + + clearButton.click().then(function () { + options = firstSelect.all(by.tagName('option')); + expect(options.count()).toBe(0); + + reloadButton.click().then(function () { + options = firstSelect.all(by.tagName('option')); + expect(options.count()).toBe(10); + }); + }); + }); + }); + }); + + describe('First selector', function () { + let selectorComponent: protractor.ElementFinder; + let firstSelector: protractor.ElementFinder; + + beforeEach(function () { + selectorComponent = element.all(by.tagName('my-selector-host')).get(0); + firstSelector = selectorComponent.all(by.tagName('select')).get(0); + }); + + it('should show a selected hero both in the select and outside it', function () { + firstSelector.getAttribute('value').then(function (hero: any) { + expect(heroes[hero].name).toEqual('Mr. Nice'); + }); + + const firstSelectorOutput = selectorComponent.all(by.tagName('div')).get(0); + const heroOnPTag = firstSelectorOutput.all(by.tagName('p')).last().getText(); + expect(heroOnPTag).toContain('Mr. Nice'); + }); + + it('should change the selected hero on option change', function () { + firstSelector.all(by.cssContainingText('option', 'Bombasto')).first().click(); + + firstSelector.getAttribute('value').then(function (hero: any) { + expect(heroes[hero].name).toEqual('Bombasto'); + }); + + const firstSelectorOutput = selectorComponent.all(by.tagName('div')).get(0); + const heroOnPTag = firstSelectorOutput.all(by.tagName('p')).last().getText(); + expect(heroOnPTag).toContain('Bombasto'); + }); + + it('should change select collection on button clicks', function () { + const reloadButton = selectorComponent.all(by.buttonText('Reload')); + const clearButton = selectorComponent.all(by.buttonText('Clear')); + const removeButton = selectorComponent.all(by.buttonText('Remove')); + + let options = firstSelector.all(by.tagName('option')); + expect(options.count()).toBe(10); + + removeButton.click().then(function () { + options = firstSelector.all(by.tagName('option')); + expect(options.count()).toBe(9); + + clearButton.click().then(function () { + options = firstSelector.all(by.tagName('option')); + expect(options.count()).toBe(0); + + reloadButton.click().then(function () { + options = firstSelector.all(by.tagName('option')); + expect(options.count()).toBe(10); + }); + }); + }); + }); + }); + + describe('Second selector', function () { + let selectorComponent: protractor.ElementFinder; + let secondSelector: protractor.ElementFinder; + + beforeEach(function () { + selectorComponent = element.all(by.tagName('my-selector-host')).get(0); + secondSelector = selectorComponent.all(by.tagName('select')).get(1); + }); + + it('should show a selected hero both in the select and outside it', function () { + secondSelector.getAttribute('value').then(function (hero: any) { + expect(heroes[hero].name).toEqual('Narco'); + }); + + const secondSelectorOutput = selectorComponent.all(by.tagName('div')).get(1); + const heroOnPTag = secondSelectorOutput.all(by.tagName('p')).last().getText(); + expect(heroOnPTag).toContain('Narco'); + }); + + it('should change the selected hero on option change', function () { + secondSelector.all(by.cssContainingText('option', 'Bombasto')).first().click(); + + secondSelector.getAttribute('value').then(function (hero: any) { + expect(heroes[hero].name).toEqual('Bombasto'); + }); + + const secondSelectorOutput = selectorComponent.all(by.tagName('div')).get(1); + const heroOnPTag = secondSelectorOutput.all(by.tagName('p')).last().getText(); + expect(heroOnPTag).toContain('Bombasto'); + }); + }); +}); diff --git a/public/docs/_examples/cb-select-box-component/ts/.gitignore b/public/docs/_examples/cb-select-box-component/ts/.gitignore new file mode 100644 index 0000000000..cf44e148ba --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/.gitignore @@ -0,0 +1 @@ +**/*.js \ No newline at end of file diff --git a/public/docs/_examples/cb-select-box-component/ts/app/app.component.1.ts b/public/docs/_examples/cb-select-box-component/ts/app/app.component.1.ts new file mode 100644 index 0000000000..a9a744d2e8 --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/app.component.1.ts @@ -0,0 +1,12 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +

Favorite Hero

+ + ` +}) +export class AppComponent {} +// #enddocregion diff --git a/public/docs/_examples/cb-select-box-component/ts/app/app.component.ts b/public/docs/_examples/cb-select-box-component/ts/app/app.component.ts new file mode 100644 index 0000000000..7d79b3b28a --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/app.component.ts @@ -0,0 +1,14 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +

Favorite Hero

+ +
+ + ` +}) +export class AppComponent {} +// #enddocregion diff --git a/public/docs/_examples/cb-select-box-component/ts/app/app.module.ts b/public/docs/_examples/cb-select-box-component/ts/app/app.module.ts new file mode 100644 index 0000000000..f3ab50871c --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/app.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { SelectVerboseComponent } from './select-verbose.component'; +import { SelectorHostComponent } from './selector-host.component'; +import { SelectorComponent } from './selector.component'; +import { HeroStoreService } from './hero-store.service'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ + AppComponent, + SelectVerboseComponent, + SelectorHostComponent, + SelectorComponent + ], + providers: [ HeroStoreService ], + bootstrap: [ AppComponent ] +}) +export class AppModule {} diff --git a/public/docs/_examples/cb-select-box-component/ts/app/hero-store.service.ts b/public/docs/_examples/cb-select-box-component/ts/app/hero-store.service.ts new file mode 100644 index 0000000000..5d88f770a8 --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/hero-store.service.ts @@ -0,0 +1,26 @@ +// #docregion +import { Injectable } from '@angular/core'; + +export interface Hero { + id: number; + name: string; +} + +@Injectable() +export class HeroStoreService { + get heroes(): Hero[] { return HeroStoreService.heroCache; } + + static heroCache: Hero[] = [ + {'id': 11, 'name': 'Mr. Nice'}, + {'id': 12, 'name': 'Narco'}, + {'id': 13, 'name': 'Bombasto'}, + {'id': 14, 'name': 'Celeritas'}, + {'id': 15, 'name': 'Magneta'}, + {'id': 16, 'name': 'RubberMan'}, + {'id': 17, 'name': 'Dynama'}, + {'id': 18, 'name': 'Dr IQ'}, + {'id': 19, 'name': 'Magma'}, + {'id': 20, 'name': 'Tornado'} + ]; +} +// #enddocregion diff --git a/public/docs/_examples/cb-select-box-component/ts/app/main.ts b/public/docs/_examples/cb-select-box-component/ts/app/main.ts new file mode 100644 index 0000000000..9be7775f4d --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/main.ts @@ -0,0 +1,5 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.html b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.html new file mode 100644 index 0000000000..629e2e1008 --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.html @@ -0,0 +1,14 @@ + +
+

Select verbose: List of heroes

+
+
+ + +

Selected Hero

+
{{selectedHero | json }}
+

Selected hero name = {{selectedHero?.name}}

+
+ diff --git a/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.ts b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.ts new file mode 100644 index 0000000000..d0589b63be --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.ts @@ -0,0 +1,21 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +import { Hero, HeroStoreService } from './hero-store.service'; + +@Component({ + selector: 'my-select-verbose', + templateUrl: 'app/select-verbose.component.html' +}) +export class SelectVerboseComponent implements OnInit { + heroes: Hero[] = []; + selectedHero: Hero; + + constructor(private store: HeroStoreService) { } + + ngOnInit(): void { + this.heroes = this.store.heroes.slice(); + this.selectedHero = this.heroes[1]; + } +} +// #enddocregion diff --git a/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.html b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.html new file mode 100644 index 0000000000..5046aac17f --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.html @@ -0,0 +1,22 @@ + + +
+

Select verbose: List of heroes

+
+
+ + +

Selected Hero

+
{{selectedHero | json }}
+

Selected hero name = {{selectedHero?.name}}

+
+ + + + + diff --git a/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.ts b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.ts new file mode 100644 index 0000000000..f0808e9dbb --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.ts @@ -0,0 +1,52 @@ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; + +import { Hero, HeroStoreService } from './hero-store.service'; + +@Component({ + selector: 'my-select-verbose', + templateUrl: 'app/select-verbose.component.html' +}) +export class SelectVerboseComponent implements OnInit { + heroes: Hero[]; + selectedHero: Hero; + + constructor(private store: HeroStoreService) { } + // #docregion buttons-methods + clear(): void { + this.heroes = undefined; + this.selectedHero = undefined; + } + // #enddocregion buttons-methods + // #docregion select-methods + isSelected(hero: Hero): boolean { + return this.selectedHero && hero.id === this.selectedHero.id; + } + // #enddocregion select-methods + // #docregion buttons-methods + ngOnInit(): void { + this.reload(); + this.selectedHero = this.heroes[1]; + } + // #enddocregion buttons-methods + // #docregion select-methods + onChange(index: string): void { + this.selectedHero = this.heroes[+index]; + } + // #enddocregion select-methods + // #docregion buttons-methods + reload(): void { + this.heroes = this.store.heroes.slice(); + this.selectedHero = this.heroes[0]; + } + removeSelected(): void { + if (this.heroes && this.selectedHero) { + const index = this.heroes.indexOf(this.selectedHero); + this.heroes.splice(index, 1); + this.selectedHero = this.heroes[1]; + } + } + // #enddocregion buttons-methods +} +// #enddocregion diff --git a/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.html b/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.html new file mode 100644 index 0000000000..3db2bf3ef3 --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.html @@ -0,0 +1,39 @@ + + +

SelectedHero Selector - [key] (change)

+
+ + + + +

Selected Hero

+
{{selectedHero | json}}
+

Selected hero name = {{selectedHero?.name}}

+
+ + + + + + + + + +

Fan.Hero Selector - [(key)]

+ +
+ + + + +

Hero Fan

+
{{heroFan | json}}
+

Fan's hero name = {{heroFan?.heroName}}

+
+ + diff --git a/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.ts b/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.ts new file mode 100644 index 0000000000..21785b9255 --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.ts @@ -0,0 +1,60 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Hero, HeroStoreService } from './hero-store.service'; + +// #docregion hero-fan +class HeroFan { + constructor( + public id = 0, + public name: string, + public heroId?: number) { + } + + get heroName(): string { + let hero = this.heroId && + // DEMO ONLY: Do NOT do it this way + HeroStoreService.heroCache + .filter(h => h.id === this.heroId)[0]; + return hero ? hero.name : ''; + } +} +// #enddocregion hero-fan + +// #docregion component +@Component({ + selector: 'my-selector-host', + templateUrl: 'app/selector-host.component.html' +}) +export class SelectorHostComponent implements OnInit { + heroes: Hero[]; + heroFan = new HeroFan(42, 'Lucy'); + selectedHero: Hero; + + constructor(private store: HeroStoreService) { } + + ngOnInit(): void { + this.reload(); + this.selectedHero = this.heroes[0]; + this.heroFan.heroId = this.heroes[1].id; + } + + clear(): void { + this.heroes = undefined; + this.selectedHero = undefined; + } + + reload(): void { + this.heroes = this.store.heroes.slice(0); + this.selectedHero = this.heroes[0]; + } + + removeSelected(): void { + if (this.heroes && this.selectedHero) { + let index = this.heroes.indexOf(this.selectedHero); + this.heroes.splice(index, 1); + this.selectedHero = this.heroes[0]; + } + } +} +// #enddocregion component +// #enddocregion diff --git a/public/docs/_examples/cb-select-box-component/ts/app/selector.component.ts b/public/docs/_examples/cb-select-box-component/ts/app/selector.component.ts new file mode 100644 index 0000000000..9fc466603c --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/app/selector.component.ts @@ -0,0 +1,47 @@ +// #docplaster +// #docregion +// #docregion skeleton +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +// #docregion next-id +let nextSelectId = 1; +// #enddocregion next-id + +@Component({ + selector: 'my-selector', + exportAs: 'mySelector', + template: ` + + ` +}) +// #docregion selector-class +export class SelectorComponent { +// #enddocregion skeleton + @Input('id') selectId = `select-${nextSelectId++}`; + @Input() display = 'name'; + @Input() key: any; + @Input() options: any[]; + @Input() optionKey = 'id'; + + @Output() optionChange = new EventEmitter(); + @Output() keyChange = new EventEmitter(); + + isSelected(option: any): boolean { + return this.key && this.options && option[this.optionKey] === this.key; + } + + onChange(index: string): void { + this.optionChange.emit(this.options[+index]); + this.keyChange.emit(this.options[+index][this.optionKey]); + } +// #docregion skeleton +} +// #enddocregion skeleton +// #enddocregion selector-class +// #enddocregion diff --git a/public/docs/_examples/cb-select-box-component/ts/demo.css b/public/docs/_examples/cb-select-box-component/ts/demo.css new file mode 100644 index 0000000000..8c50c5cdad --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/demo.css @@ -0,0 +1,12 @@ +pre { + background: #EEE; + padding: 4px +} + +select { + width: 100px; +} + +button { + width: 75px; +} \ No newline at end of file diff --git a/public/docs/_examples/cb-select-box-component/ts/example-config.json b/public/docs/_examples/cb-select-box-component/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-select-box-component/ts/index.html b/public/docs/_examples/cb-select-box-component/ts/index.html new file mode 100644 index 0000000000..21b7076801 --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/index.html @@ -0,0 +1,29 @@ + + + + + Select box component + + + + + + + + + + + + + + + + + + + Loading app... + + + diff --git a/public/docs/_examples/cb-select-box-component/ts/plnkr.json b/public/docs/_examples/cb-select-box-component/ts/plnkr.json new file mode 100644 index 0000000000..e88c53fd4c --- /dev/null +++ b/public/docs/_examples/cb-select-box-component/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Select Box Component", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags":"[cookbook]" +} \ No newline at end of file diff --git a/public/docs/ts/latest/cookbook/_data.json b/public/docs/ts/latest/cookbook/_data.json index f82d816cef..86cbcc80df 100644 --- a/public/docs/ts/latest/cookbook/_data.json +++ b/public/docs/ts/latest/cookbook/_data.json @@ -48,6 +48,11 @@ "intro": "Migrate your RC4 app to RC5 in minutes." }, + "select-box-component": { + "title": "Select Box Component", + "intro": "Building a custom select box" + }, + "set-document-title": { "title": "Set the Document Title", "intro": "Setting the document or window title using the Title service." diff --git a/public/docs/ts/latest/cookbook/select-box-component.jade b/public/docs/ts/latest/cookbook/select-box-component.jade new file mode 100644 index 0000000000..8d55975f4e --- /dev/null +++ b/public/docs/ts/latest/cookbook/select-box-component.jade @@ -0,0 +1,167 @@ +include ../_util-fns + +:marked + We definitely need to see which hero is our favorite one, we are going to build a select box and then refactor it into a component. + + + ## Table of contents + + [A heroes service](#hero-service) + + [A verbose component](#verbose-component) + + [Refactoring the select box into a component](#refactor) + + [Using the new component](#using) + +:marked + **See the [live example](/resources/live-examples/cb-select-box-component/ts/plnkr.html)**. + +.l-main-section + +:marked + ## A heroes service + + We need heroes, lots of heroes. Let's use a service. + ++makeExample('cb-select-box-component/ts/app/hero-store.service.ts', null, 'hero-store.service.ts')(format=".") + + +:marked + ## A verbose component + + Our first implementation involves creating a `