├── .gitignore ├── README.md ├── browser ├── index.html └── src │ └── app.ts ├── package-lock.json ├── package.json ├── patterns ├── AccessibleNodes.tsx ├── Binding.tsx ├── BooleanAttributes.tsx ├── CleaningUp.tsx ├── CustomEventHandlerArguments.tsx ├── DealingWithWhitespace.tsx ├── DefiningGettersAndSetters.tsx ├── DistinguishableChildren.tsx ├── HandlingChildrenEvents.tsx ├── HandlingMultipleEvents.tsx ├── InnerHTML.tsx ├── ManagingListItemFocus.tsx ├── NodeCreation.tsx ├── ReferencingNodesOnDemand.tsx ├── RenderingArrayItems.tsx ├── RenderingCollectionItems.tsx ├── RoleAttribute.tsx ├── SchedulingRender.tsx ├── SpreadingAttributes.tsx ├── SyntheticEvents.tsx ├── TogglingNodeVisibility.tsx ├── UsingDgrid.tsx ├── UsingDijit.tsx ├── WorkingWithSVG.tsx ├── manifest.json └── support │ └── loadStylesheet.ts ├── tsconfig.json └── typings ├── arcgis-js-api-4.7.d.ts └── arcgis-js-api-extensions.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | node_modules/ 4 | *.js 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 4x Widget Resources 2 | 3 | Collection of focused examples useful for widget development. 4 | 5 | ## Interactive Browser 6 | 7 | https://jcfranco.github.io/4x-widget-patterns/browser 8 | 9 | ## Requirements 10 | 11 | * [node & npm](https://nodejs.org/) 12 | 13 | ## Usage 14 | 15 | * `npm install` - installs required node and bower packages 16 | * Open a browser and go to `examples/index.html` (assumes repository is web-accessible) 17 | 18 | ## Notes 19 | 20 | * IIS (Windows) requires adding `*.tsx` as a MIME type for examples to load correctly. 21 | -------------------------------------------------------------------------------- /browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Widget 4.x widget patterns 7 | 8 | 9 | 10 | 32 | 48 | 49 | 52 | 53 | 54 | 55 |
56 |
57 | 61 |
62 |

Code

63 |
64 |             
65 |           
66 |
67 |
68 |

Preview

69 |
70 |
71 |
72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /browser/src/app.ts: -------------------------------------------------------------------------------- 1 | import manifest = require("dojo/text!patterns/manifest.json"); 2 | 3 | window.addEventListener("hashchange", function() { 4 | window.location.reload(true); 5 | }); 6 | 7 | function byId(id: string): HTMLElement { 8 | return document.getElementById(id); 9 | } 10 | 11 | function toLabel(pattern: string): string { 12 | return pattern.replace(/([a-z])([A-Z])/g, "$1 $2"); 13 | } 14 | 15 | interface Manifest { 16 | patterns: string[]; 17 | } 18 | 19 | const patternToken = decodeURI(location.hash).slice(1); 20 | const patternName = patternToken.replace(/-/g, ""); // strip delimiters 21 | const { patterns } = JSON.parse(manifest) as Manifest; 22 | 23 | const select = byId("pattern-select") as HTMLSelectElement; 24 | 25 | function redirectToOption(optionIndex: number): void { 26 | window.location.hash = select.options[optionIndex].value 27 | } 28 | 29 | patterns.forEach(pattern => { 30 | const option = document.createElement("option"); 31 | option.label = toLabel(pattern); 32 | option.value = pattern; 33 | option.selected = pattern.toLowerCase() === patternName.toLowerCase(); 34 | select.appendChild(option); 35 | }); 36 | 37 | select.addEventListener("input", function() { 38 | redirectToOption(select.selectedIndex); 39 | }); 40 | 41 | if (!patternName) { 42 | redirectToOption(0); 43 | } 44 | 45 | document.title = `${document.title} - ${patternToken}`; 46 | 47 | const patternMid = `patterns/${patternName}`; 48 | const patternCodeMid = `dojo/text!${patternMid}.tsx`; 49 | 50 | require([ 51 | patternMid, 52 | patternCodeMid, 53 | "highlight" 54 | ], (Pattern: any, patternCode: string, highlight: any) => { 55 | const codeDiv = byId("codeDiv"); 56 | codeDiv.textContent = patternCode; 57 | highlight.highlightBlock(codeDiv); 58 | 59 | new Pattern({ container: "widgetDiv" }); 60 | }); 61 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4x-widget-snippets", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/chai": { 8 | "version": "4.1.2", 9 | "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.2.tgz", 10 | "integrity": "sha512-D8uQwKYUw2KESkorZ27ykzXgvkDJYXVEihGklgfp5I4HUP8D6IxtcdLTMB1emjQiWzV7WZ5ihm1cxIzVwjoleQ==", 11 | "dev": true 12 | }, 13 | "@types/highlight.js": { 14 | "version": "9.12.2", 15 | "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.2.tgz", 16 | "integrity": "sha512-y5x0XD/WXDaGSyiTaTcKS4FurULJtSiYbGTeQd0m2LYZGBcZZ/7fM6t5H/DzeUF+kv8y6UfmF6yJABQsHcp9VQ==", 17 | "dev": true 18 | }, 19 | "dojo-typings": { 20 | "version": "1.11.9", 21 | "resolved": "https://registry.npmjs.org/dojo-typings/-/dojo-typings-1.11.9.tgz", 22 | "integrity": "sha512-mh8w+Mau2Y1QfTEszEAdO7j6ycNhYxF/Ing6nAk1eUg6NxjeT0viVHjICMd9sU3U463vM2G+KfBBK5grk3/Mlw==", 23 | "dev": true, 24 | "requires": { 25 | "@types/chai": "4.1.2" 26 | } 27 | }, 28 | "typescript": { 29 | "version": "2.7.2", 30 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", 31 | "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==", 32 | "dev": true 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4x-widget-snippets", 3 | "version": "1.0.0", 4 | "description": "4x widget snippets", 5 | "scripts": { 6 | "postinstall": "tsc" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/jcfranco/4x-widget-snippets" 11 | }, 12 | "keywords": [ 13 | "jsapi", 14 | "widget", 15 | "resources" 16 | ], 17 | "author": "JC Franco", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "@types/highlight.js": "~9.12.2", 21 | "dojo-typings": "~1.11.9", 22 | "typescript": "~2.7.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /patterns/AccessibleNodes.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared, property} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {renderable, tsx, accessibleHandler} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-accessible-nodes" 12 | }; 13 | 14 | @subclass("examples.AccessibleNodes") 15 | class AccessibleNodes extends declared(Widget) { 16 | 17 | //-------------------------------------------------------------------------- 18 | // 19 | // Properties 20 | // 21 | //-------------------------------------------------------------------------- 22 | 23 | //---------------------------------- 24 | // enabled 25 | //---------------------------------- 26 | 27 | @property() 28 | @renderable() 29 | enabled = true; 30 | 31 | //-------------------------------------------------------------------------- 32 | // 33 | // Public Methods 34 | // 35 | //-------------------------------------------------------------------------- 36 | 37 | render() { 38 | const child = this.enabled ? ( 39 |
( ^∇^)ノ {'{'} ohai!
40 | ) : null; 41 | 42 | return ( 43 |
48 | Click or press space/enter key (when focused) to toggle 49 | {child} 50 |
51 | ); 52 | } 53 | 54 | //-------------------------------------------------------------------------- 55 | // 56 | // Private Methods 57 | // 58 | //-------------------------------------------------------------------------- 59 | 60 | @accessibleHandler() 61 | private _handleClick(): void { 62 | this.enabled = !this.enabled; 63 | } 64 | 65 | } 66 | 67 | export = AccessibleNodes; 68 | -------------------------------------------------------------------------------- /patterns/Binding.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-binding" 12 | }; 13 | 14 | @subclass("examples.Binding") 15 | class Binding extends declared(Widget) { 16 | 17 | //-------------------------------------------------------------------------- 18 | // 19 | // Public Methods 20 | // 21 | //-------------------------------------------------------------------------- 22 | 23 | render() { 24 | return ( 25 |
26 |
this === widget
28 |
this === node
29 |
30 | ); 31 | } 32 | 33 | //-------------------------------------------------------------------------- 34 | // 35 | // Private Methods 36 | // 37 | //-------------------------------------------------------------------------- 38 | 39 | private _handlerWhereThisIsWidget(): void { 40 | console.log(this); // this === widget instance 41 | } 42 | 43 | private _handlerWithThisIsNode(): void { 44 | console.log(this); // this === DOM node 45 | } 46 | 47 | } 48 | 49 | export = Binding; 50 | -------------------------------------------------------------------------------- /patterns/BooleanAttributes.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-boolean-attributes" 12 | }; 13 | 14 | @subclass("examples.BooleanAttributes") 15 | class BooleanAttributes extends declared(Widget) { 16 | 17 | //-------------------------------------------------------------------------- 18 | // 19 | // Lifecycle 20 | // 21 | //-------------------------------------------------------------------------- 22 | 23 | postInitialize() { 24 | setInterval(() => { 25 | this._selectedIndex = Math.floor(Math.random() * 10); 26 | this.scheduleRender(); 27 | }, 500); 28 | } 29 | 30 | //-------------------------------------------------------------------------- 31 | // 32 | // Variables 33 | // 34 | //-------------------------------------------------------------------------- 35 | 36 | _selectedIndex = 0; 37 | 38 | //-------------------------------------------------------------------------- 39 | // 40 | // Public Methods 41 | // 42 | //-------------------------------------------------------------------------- 43 | 44 | render() { 45 | return ( 46 |
47 | {/* boolean attributes require setting their value to an expression */} 48 | 61 |
62 | ); 63 | } 64 | 65 | } 66 | 67 | export = BooleanAttributes; 68 | -------------------------------------------------------------------------------- /patterns/CleaningUp.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared, property} from "esri/core/accessorSupport/decorators"; 5 | 6 | import promiseUtils = require("esri/core/promiseUtils"); 7 | 8 | import Widget = require("esri/widgets/Widget"); 9 | 10 | 11 | import {renderable, tsx} from "esri/widgets/support/widget"; 12 | 13 | const CSS = { 14 | base: "esri-cleaning-up" 15 | }; 16 | 17 | @subclass("examples.CleaningUp") 18 | class CleaningUp extends declared(Widget) { 19 | 20 | //-------------------------------------------------------------------------- 21 | // 22 | // Lifecycle 23 | // 24 | //-------------------------------------------------------------------------- 25 | 26 | postInitialize() { 27 | const dayInSeconds = 86400; 28 | 29 | const dayElapsedPromise = promiseUtils.timeout( 30 | promiseUtils.resolve(), dayInSeconds, null 31 | ); 32 | 33 | const counterWatcher = this.watch("counter", (value: number, oldValue: number, name: string) => { 34 | console.log(`${name} changed from ${oldValue} to ${value}`); 35 | }); 36 | 37 | const counterIntervalId = setInterval(() => { 38 | console.log(`counter: ${this.counter++}`); 39 | }, 500); 40 | 41 | const nonHandleRemover = { 42 | remove() { 43 | clearInterval(counterIntervalId); 44 | dayElapsedPromise.cancel(); 45 | } 46 | }; 47 | 48 | // 'owned' handles will be destroyed automatically 49 | this.own([ 50 | nonHandleRemover, 51 | counterWatcher 52 | ]); 53 | } 54 | 55 | //-------------------------------------------------------------------------- 56 | // 57 | // Properties 58 | // 59 | //-------------------------------------------------------------------------- 60 | 61 | //---------------------------------- 62 | // counter 63 | //---------------------------------- 64 | 65 | @property() 66 | @renderable() 67 | counter: number = 0; 68 | 69 | //-------------------------------------------------------------------------- 70 | // 71 | // Public Methods 72 | // 73 | //-------------------------------------------------------------------------- 74 | 75 | render() { 76 | return ( 77 |
{this.counter}
78 | ); 79 | } 80 | 81 | } 82 | 83 | export = CleaningUp; 84 | -------------------------------------------------------------------------------- /patterns/CustomEventHandlerArguments.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-custom-event-handler-arguments" 12 | }; 13 | 14 | function numberBetween0And100() { 15 | return Math.floor((Math.random() * 100) + 1); 16 | } 17 | 18 | function luckyNumbers() { 19 | const nums = [ 20 | numberBetween0And100(), 21 | numberBetween0And100(), 22 | numberBetween0And100(), 23 | numberBetween0And100(), 24 | numberBetween0And100(), 25 | numberBetween0And100(), 26 | numberBetween0And100() 27 | ]; 28 | 29 | return `${nums[0]}-${nums[1]}-${nums[2]}-${nums[3]}-${nums[4]}-${nums[5]}-${nums[6]}`; 30 | } 31 | 32 | @subclass("examples.CustomEventHandlerArguments") 33 | class CustomEventHandlerArguments extends declared(Widget) { 34 | 35 | //-------------------------------------------------------------------------- 36 | // 37 | // Public Methods 38 | // 39 | //-------------------------------------------------------------------------- 40 | 41 | render() { 42 | return ( 43 |
46 | Click me for your lucky numbers! 47 |
48 | ); 49 | } 50 | 51 | //-------------------------------------------------------------------------- 52 | // 53 | // Private Methods 54 | // 55 | //-------------------------------------------------------------------------- 56 | 57 | private _handleClick(event: MouseEvent) { 58 | const node = event.currentTarget as Element; 59 | const luckyNums = node.getAttribute("data-lucky-numbers"); 60 | console.log(`Today's lucky numbers are: ${luckyNums}`); 61 | } 62 | 63 | } 64 | 65 | export = CustomEventHandlerArguments; 66 | -------------------------------------------------------------------------------- /patterns/DealingWithWhitespace.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {tsx} from "esri/widgets/support/widget"; 9 | 10 | @subclass("examples.DealingWithWhitespace") 11 | class DealingWithWhitespace extends declared(Widget) { 12 | 13 | //-------------------------------------------------------------------------- 14 | // 15 | // Public Methods 16 | // 17 | //-------------------------------------------------------------------------- 18 | 19 | render() { 20 | 21 | // taken from http://www.bennadel.com/blog/2880-a-quick-look-at-rendering-white-space-using-jsx-in-reactjs.htm 22 | return ( 23 |
24 | 25 |

26 | { 27 | // This content will render with no visible white space 28 | // since each sibling Element node that is defined on a 29 | // different line of CODE will be rendered right up 30 | // against the other ones. 31 | } 32 | Bro! 33 | You 34 | had 35 | me 36 | at 37 | "White space." 38 |

39 | 40 |

41 | { 42 | // This content will render with "expected" white space 43 | // since the Element node is on the same line with its 44 | // other content. As such, the white space around the 45 | // element is preserved. 46 | } 47 | There's literally nothing more exciting! 48 |

49 | 50 |

51 | { 52 | // In this case, we're going to lose the white space to 53 | // the LEFT of the first STRONG Element node and the RIGHT 54 | // of the second one. The plain-text lines are implicitly 55 | // wrapped in Span tags, which creates a multi-line sibling 56 | // relationship between Element nodes, which removes white 57 | // space (as we saw in the first example). 58 | } 59 | But don't get me started on 60 | kerning and tracking 61 | or we'll be here all day. 62 |

63 | 64 |

65 | { 66 | // This will render as expected since there are no 67 | // Element nodes. This entire value gets rendered as a 68 | // single value, NO implicit SPAN container. 69 | } 70 | That said, if you want to get groovy on the use of 71 | negative space in design, then I will be more than 72 | happy to chat. I have nowhere else to be at this moment. 73 |

74 | 75 |

76 | { 77 | // We can always force white space by interpolating a 78 | // white space literal. This way, when the JSX is 79 | // compiled down into React Element children, the white 80 | // space literal will be an explicit child. 81 | } 82 | Bro! 83 | { " " } 84 | You 85 | { " " } 86 | had 87 | { " " } 88 | me 89 | { " " } 90 | at 91 | { " " } 92 | "White space." 93 |

94 | 95 |
96 | ); 97 | 98 | } 99 | 100 | } 101 | 102 | export = DealingWithWhitespace; 103 | -------------------------------------------------------------------------------- /patterns/DefiningGettersAndSetters.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared, property} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-defining-getters-and-setters" 12 | }; 13 | 14 | @subclass("examples.DefiningGettersAndSetters") 15 | class DefiningGettersAndSetters extends declared(Widget) { 16 | 17 | //-------------------------------------------------------------------------- 18 | // 19 | // Properties 20 | // 21 | //-------------------------------------------------------------------------- 22 | 23 | //---------------------------------- 24 | // normalProperty 25 | //---------------------------------- 26 | 27 | @property() 28 | normalProperty = "normal"; 29 | 30 | //---------------------------------- 31 | // getterSetterProperty 32 | //---------------------------------- 33 | 34 | @property({ 35 | value: "getter-setter" 36 | }) 37 | get getterSetterProperty(): boolean { 38 | return this._get("getterSetterProperty"); 39 | } 40 | 41 | set getterSetterProperty(value: boolean) { 42 | this._set("getterSetterProperty", value); 43 | } 44 | 45 | //---------------------------------- 46 | // readOnlyProperty 47 | //---------------------------------- 48 | 49 | @property({ 50 | value: "read-only", 51 | readOnly: true 52 | }) 53 | get readOnlyProperty(): boolean { 54 | return this._get("readOnlyProperty"); 55 | } 56 | 57 | //-------------------------------------------------------------------------- 58 | // 59 | // Public Methods 60 | // 61 | //-------------------------------------------------------------------------- 62 | 63 | render() { 64 | return ( 65 |
66 |
{this.normalProperty}
67 |
{this.getterSetterProperty}
68 |
{this.readOnlyProperty}
69 |
70 | ); 71 | } 72 | 73 | } 74 | 75 | export = DefiningGettersAndSetters; 76 | -------------------------------------------------------------------------------- /patterns/DistinguishableChildren.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-distinguishable-children", 12 | item: "esri-distinguishable-children__item" 13 | }; 14 | 15 | function createRandomListItems(): any[] { 16 | const children: any[] = []; 17 | 18 | for (let i = 0; i < randomNum(); i++) { 19 | children.push( 20 | // use `key` to distinguish this elements that will be added/removed dynamically 21 |
  • item {i}
  • 22 | ); 23 | } 24 | 25 | return children; 26 | } 27 | 28 | function randomNum(max = 10): number { 29 | return Math.random() * max; 30 | } 31 | 32 | @subclass("examples.DistinguishableChildren") 33 | class DistinguishableChildren extends declared(Widget) { 34 | 35 | //-------------------------------------------------------------------------- 36 | // 37 | // Lifecycle 38 | // 39 | //-------------------------------------------------------------------------- 40 | 41 | postInitialize() { 42 | setInterval(() => this.scheduleRender(), 500); 43 | } 44 | 45 | //-------------------------------------------------------------------------- 46 | // 47 | // Public Methods 48 | // 49 | //-------------------------------------------------------------------------- 50 | 51 | render() { 52 | return ( 53 |
    { 54 |
      {createRandomListItems()}
    55 | }
    56 | ); 57 | } 58 | 59 | } 60 | 61 | export = DistinguishableChildren; 62 | -------------------------------------------------------------------------------- /patterns/HandlingChildrenEvents.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import Widget = require("esri/widgets/Widget"); 5 | 6 | import { subclass, declared } from "esri/core/accessorSupport/decorators"; 7 | 8 | import {tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-handling-children-events", 12 | item: "esri-handling-children-events__item" 13 | }; 14 | 15 | @subclass("examples.HandlingChildrenEvents") 16 | class HandlingChildrenEvents extends declared(Widget) { 17 | 18 | //-------------------------------------------------------------------------- 19 | // 20 | // Variables 21 | // 22 | //-------------------------------------------------------------------------- 23 | 24 | _focusedItem: string; 25 | 26 | _items = ["tab", "between", "us"]; 27 | 28 | //-------------------------------------------------------------------------- 29 | // 30 | // Public Methods 31 | // 32 | //-------------------------------------------------------------------------- 33 | 34 | render() { 35 | return ( 36 |
    37 |
    44 | Tab 45 |
    46 | 53 | between 54 | 55 |

    62 | us 63 |

    64 |
    65 | ); 66 | } 67 | 68 | //-------------------------------------------------------------------------- 69 | // 70 | // Private Methods 71 | // 72 | //-------------------------------------------------------------------------- 73 | 74 | _itemToStyles(id: string): HashMap { 75 | // using inline-styles for demo purposes only 76 | return { 77 | "font-weight": this._focusedItem === id ? "bolder" : "" 78 | }; 79 | } 80 | 81 | private _handleFocus(event: FocusEvent): void { 82 | const node = event.currentTarget as Element; 83 | this._focusedItem = node.getAttribute("data-item-id"); 84 | } 85 | 86 | private _handleBlur(event: FocusEvent): void { 87 | const node = event.currentTarget as Element; 88 | 89 | if (this._focusedItem === node.getAttribute("data-item-id")) { 90 | this._focusedItem = null; 91 | } 92 | } 93 | 94 | } 95 | 96 | export = HandlingChildrenEvents; 97 | -------------------------------------------------------------------------------- /patterns/HandlingMultipleEvents.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-handling-multiple-events", 12 | item: "esri-handling-multiple-events__item" 13 | }; 14 | 15 | @subclass("examples.HandlingMultipleEvents") 16 | class HandlingMultipleEvents extends declared(Widget) { 17 | 18 | //-------------------------------------------------------------------------- 19 | // 20 | // Variables 21 | // 22 | //-------------------------------------------------------------------------- 23 | 24 | _focusedItem: string; 25 | 26 | _items = ["tab", "between", "us"]; 27 | 28 | //-------------------------------------------------------------------------- 29 | // 30 | // Public Methods 31 | // 32 | //-------------------------------------------------------------------------- 33 | 34 | render() { 35 | return ( 36 |
    37 |
    44 | Tab 45 |
    46 | 53 | between 54 | 55 |

    62 | us 63 |

    64 |
    65 | ); 66 | } 67 | 68 | //-------------------------------------------------------------------------- 69 | // 70 | // Private Methods 71 | // 72 | //-------------------------------------------------------------------------- 73 | 74 | private _itemToStyles(id: string): HashMap { 75 | // using inline-styles for demo purposes only 76 | return { 77 | "font-weight": this._focusedItem === id ? "bolder" : "" 78 | }; 79 | } 80 | 81 | private _handleFocus(event: FocusEvent): void { 82 | const node = event.currentTarget as Element; 83 | this._focusedItem = node.getAttribute("data-item-id"); 84 | } 85 | 86 | private _handleBlur(event: FocusEvent): void { 87 | const node = event.currentTarget as Element; 88 | 89 | if (this._focusedItem === node.getAttribute("data-item-id")) { 90 | this._focusedItem = null; 91 | } 92 | } 93 | 94 | } 95 | 96 | export = HandlingMultipleEvents; 97 | -------------------------------------------------------------------------------- /patterns/InnerHTML.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-innerhtml" 12 | }; 13 | 14 | @subclass("examples.InnerHTML") 15 | class InnerHTML extends declared(Widget) { 16 | 17 | //-------------------------------------------------------------------------- 18 | // 19 | // Public Methods 20 | // 21 | //-------------------------------------------------------------------------- 22 | 23 | render() { 24 | const someExternalContent = "I am bold!"; 25 | 26 | return ( 27 |
    28 | ); 29 | } 30 | 31 | } 32 | 33 | export = InnerHTML; 34 | -------------------------------------------------------------------------------- /patterns/ManagingListItemFocus.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | 5 | import {subclass, declared, property} from "esri/core/accessorSupport/decorators"; 6 | 7 | import Widget = require("esri/widgets/Widget"); 8 | 9 | import {renderable, tsx} from "esri/widgets/support/widget"; 10 | 11 | const UP_ARROW = 38; 12 | const DOWN_ARROW = 40; 13 | 14 | const CSS = { 15 | base: "esri-hello-list", 16 | item: "esri-hello-list__item", 17 | text: "esri-hello-list__item-text" 18 | }; 19 | 20 | @subclass("examples.ManagingListItemFocus") 21 | class ManagingListItemFocus extends declared(Widget) { 22 | 23 | //-------------------------------------------------------------------------- 24 | // 25 | // Variables 26 | // 27 | //-------------------------------------------------------------------------- 28 | 29 | _focusIndex = 0; 30 | 31 | //-------------------------------------------------------------------------- 32 | // 33 | // Properties 34 | // 35 | //-------------------------------------------------------------------------- 36 | 37 | //---------------------------------- 38 | // items 39 | //---------------------------------- 40 | 41 | @property() 42 | @renderable() 43 | items: string[] = ["Jerry", "George", "Elaine", "Kramer"]; 44 | 45 | //-------------------------------------------------------------------------- 46 | // 47 | // Public Methods 48 | // 49 | //-------------------------------------------------------------------------- 50 | 51 | render() { 52 | return ( 53 |
      58 | { this.items.map(this._simpleToComplexItem) } 59 |
    60 | ); 61 | } 62 | 63 | //-------------------------------------------------------------------------- 64 | // 65 | // Private Methods 66 | // 67 | //-------------------------------------------------------------------------- 68 | 69 | private _handleAfterUpdate(node: any, options: any, selector: string, props: any, children: any[]): void { 70 | const focusIndex = this._focusIndex; 71 | 72 | if (children[focusIndex]) { 73 | children[focusIndex].domNode.focus(); 74 | } 75 | } 76 | 77 | private _handleListClick(event: MouseEvent): void { 78 | const item = (event.target as HTMLInputElement); 79 | const list = (event.currentTarget as HTMLOListElement); 80 | const children = Array.prototype.slice.apply(list.children); 81 | 82 | this._focusIndex = children.indexOf(item); 83 | } 84 | 85 | private _handleKeyDown(event: KeyboardEvent): void { 86 | if (event.keyCode !== UP_ARROW && event.keyCode !== DOWN_ARROW) { 87 | return; 88 | } 89 | 90 | let nextIndex: number; 91 | const numItems = this.items.length - 1; 92 | 93 | if (event.keyCode === UP_ARROW) { 94 | nextIndex = this._focusIndex - 1; 95 | nextIndex = nextIndex < 0 ? 0 : nextIndex; 96 | } 97 | 98 | if (event.keyCode === DOWN_ARROW) { 99 | nextIndex = this._focusIndex + 1; 100 | nextIndex = nextIndex > numItems ? numItems : nextIndex; 101 | } 102 | 103 | this._focusIndex = nextIndex; 104 | } 105 | 106 | private _simpleToComplexItem(item: string): any { 107 | return ( 108 |
  • 111 | {item} 112 |
  • 113 | ); 114 | } 115 | 116 | } 117 | 118 | export = ManagingListItemFocus; 119 | -------------------------------------------------------------------------------- /patterns/NodeCreation.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared, property} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {renderable, tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-node-creation", 12 | innerChild: "esri-node-creation__inner-child", 13 | outerChild: "esri-node-creation__outer-child" 14 | }; 15 | 16 | @subclass("examples.AccessibleNodes") 17 | class AccessibleNodes extends declared(Widget) { 18 | 19 | //-------------------------------------------------------------------------- 20 | // 21 | // Lifecycle 22 | // 23 | //-------------------------------------------------------------------------- 24 | 25 | postInitialize() { 26 | this._intervalId = setInterval(() => { 27 | this.text = this._options[this._currentOptionIndex++ % this._options.length]; 28 | }, 500); 29 | } 30 | 31 | destroy() { 32 | clearInterval(this._intervalId); 33 | } 34 | 35 | //-------------------------------------------------------------------------- 36 | // 37 | // Variables 38 | // 39 | //-------------------------------------------------------------------------- 40 | 41 | private _options = ["first", "second", "third"]; 42 | private _currentOptionIndex = 0; 43 | private _intervalId: number; 44 | 45 | //-------------------------------------------------------------------------- 46 | // 47 | // Properties 48 | // 49 | //-------------------------------------------------------------------------- 50 | 51 | //---------------------------------- 52 | // text 53 | //---------------------------------- 54 | 55 | @property() 56 | @renderable() 57 | text: string = this._options[this._currentOptionIndex++]; 58 | 59 | //-------------------------------------------------------------------------- 60 | // 61 | // Public Methods 62 | // 63 | //-------------------------------------------------------------------------- 64 | 65 | render() { 66 | const childContent = this.text === "first" ? ( 67 | {this.text} 68 | ) : this.text; 69 | 70 | return ( 71 |
    72 |
    { 73 | childContent 74 | }
    75 |
    76 | ); 77 | } 78 | 79 | //-------------------------------------------------------------------------- 80 | // 81 | // Private Methods 82 | // 83 | //-------------------------------------------------------------------------- 84 | 85 | private _afterParentCreate(): void { 86 | console.log("root created"); 87 | } 88 | 89 | private _afterOuterChildCreate(): void { 90 | console.log("outer child created"); 91 | } 92 | 93 | private _afterInnerChildCreate(): void { 94 | console.log("inner child created"); 95 | } 96 | 97 | } 98 | 99 | export = AccessibleNodes; 100 | -------------------------------------------------------------------------------- /patterns/ReferencingNodesOnDemand.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-referencing-nodes-on-demand" 12 | }; 13 | 14 | @subclass("examples.ReferencingNodesOnDemand") 15 | class AccessibleNodes extends declared(Widget) { 16 | 17 | //-------------------------------------------------------------------------- 18 | // 19 | // Variables 20 | // 21 | //-------------------------------------------------------------------------- 22 | 23 | private _nodes: HTMLElement[] = []; 24 | 25 | private _nodeIndex = 0; 26 | 27 | //-------------------------------------------------------------------------- 28 | // 29 | // Public Methods 30 | // 31 | //-------------------------------------------------------------------------- 32 | 33 | render() { 34 | return ( 35 |
    36 |
    37 | 38 | 39 | 40 |
    41 |
    42 | Focus on 43 | 48 | child 49 |
    50 |
    51 | 52 |
    53 |
    54 | ); 55 | } 56 | 57 | //-------------------------------------------------------------------------- 58 | // 59 | // Private Methods 60 | // 61 | //-------------------------------------------------------------------------- 62 | 63 | private _focusNode(): void { 64 | this._nodes[this._nodeIndex].focus(); 65 | } 66 | 67 | private _storeNode(node: HTMLElement): void { 68 | const index = node["data-child-index"] as number; 69 | this._nodes[index] = node; 70 | } 71 | 72 | private _storeNodeIndex(event: Event): void { 73 | const node = event.target as HTMLSelectElement; 74 | this._nodeIndex = node.selectedIndex; 75 | } 76 | 77 | } 78 | 79 | export = AccessibleNodes; 80 | -------------------------------------------------------------------------------- /patterns/RenderingArrayItems.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared, property} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {renderable, tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-rendering-array-items", 12 | item: "esri-rendering-array-items__item", 13 | text: "esri-rendering-array-items__item-text" 14 | }; 15 | 16 | @subclass("examples.RenderingArrayItems") 17 | class RenderingArrayItems extends declared(Widget) { 18 | 19 | //-------------------------------------------------------------------------- 20 | // 21 | // Properties 22 | // 23 | //-------------------------------------------------------------------------- 24 | 25 | //---------------------------------- 26 | // items 27 | //---------------------------------- 28 | 29 | @property() 30 | @renderable() 31 | items: string[] = ["Jerry", "George", "Elaine", "Kramer"]; 32 | 33 | //-------------------------------------------------------------------------- 34 | // 35 | // Public Methods 36 | // 37 | //-------------------------------------------------------------------------- 38 | 39 | render() { 40 | return ( 41 |
      42 | { 43 | this.items.map((item, key) => 44 |
    1. 47 | {item} 48 |
    2. 49 | ) 50 | } 51 |
    52 | ); 53 | } 54 | 55 | } 56 | 57 | export = RenderingArrayItems; 58 | -------------------------------------------------------------------------------- /patterns/RenderingCollectionItems.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared, property} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Collection = require("esri/core/Collection"); 7 | 8 | import Widget = require("esri/widgets/Widget"); 9 | 10 | import {renderable, tsx} from "esri/widgets/support/widget"; 11 | 12 | const CSS = { 13 | base: "esri-rendering-collection-items", 14 | item: "esri-rendering-collection-items__item", 15 | text: "esri-rendering-collection-items__item-text" 16 | }; 17 | 18 | @subclass("examples.RenderingCollectionItems") 19 | class RenderingCollectionItems extends declared(Widget) { 20 | 21 | //-------------------------------------------------------------------------- 22 | // 23 | // Lifecycle 24 | // 25 | //-------------------------------------------------------------------------- 26 | 27 | postInitialize() { 28 | 29 | setInterval(() => { 30 | const kramer = "Kramer"; 31 | 32 | // @renderable schedules rendering when the collection is modified 33 | 34 | if (this.items.indexOf(kramer) === -1) { 35 | this.items.add(kramer); 36 | } 37 | else { 38 | this.items.remove(kramer); 39 | } 40 | }, 500); 41 | } 42 | 43 | //-------------------------------------------------------------------------- 44 | // 45 | // Properties 46 | // 47 | //-------------------------------------------------------------------------- 48 | 49 | //---------------------------------- 50 | // items 51 | //---------------------------------- 52 | 53 | @property() 54 | @renderable() 55 | // TODO: update to Collection once typings support generics 56 | items: Collection = new Collection(["Jerry", "George", "Elaine", "Kramer"]); 57 | 58 | //-------------------------------------------------------------------------- 59 | // 60 | // Public Methods 61 | // 62 | //-------------------------------------------------------------------------- 63 | 64 | render() { 65 | const items = this.items.toArray(); 66 | 67 | return ( 68 |
      69 | { 70 | items.map((item, key) => 71 |
    1. 74 | {item} 75 |
    2. 76 | ) 77 | } 78 |
    79 | ); 80 | } 81 | 82 | } 83 | 84 | export = RenderingCollectionItems; 85 | -------------------------------------------------------------------------------- /patterns/RoleAttribute.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcfranco/4x-widget-patterns/b67ba53f1cfb794aba3ccb7ab5c6b26ea5339633/patterns/RoleAttribute.tsx -------------------------------------------------------------------------------- /patterns/SchedulingRender.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-scheduling-render" 12 | }; 13 | 14 | @subclass("examples.SchedulingRender") 15 | class SchedulingRender extends declared(Widget) { 16 | 17 | //-------------------------------------------------------------------------- 18 | // 19 | // Lifecycle 20 | // 21 | //-------------------------------------------------------------------------- 22 | 23 | postInitialize() { 24 | setInterval(() => { 25 | 26 | // variable is not renderable, so we schedule rendering after modifying it 27 | this._working = !this._working; 28 | this.scheduleRender(); 29 | 30 | }, 1000); 31 | } 32 | 33 | //-------------------------------------------------------------------------- 34 | // 35 | // Variables 36 | // 37 | //-------------------------------------------------------------------------- 38 | 39 | private _working = false; 40 | 41 | //-------------------------------------------------------------------------- 42 | // 43 | // Public Methods 44 | // 45 | //-------------------------------------------------------------------------- 46 | 47 | render() { 48 | const label = this._working ? "working" : "!working"; 49 | 50 | return ( 51 |
    {label}
    52 | ); 53 | } 54 | 55 | } 56 | 57 | export = SchedulingRender; 58 | -------------------------------------------------------------------------------- /patterns/SpreadingAttributes.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | import {subclass, declared} from "esri/core/accessorSupport/decorators"; 6 | 7 | import Widget = require("esri/widgets/Widget"); 8 | 9 | import {tsx} from "esri/widgets/support/widget"; 10 | 11 | const CSS = { 12 | base: "esri-spreading-attributes" 13 | }; 14 | 15 | @subclass("examples.SpreadingAttributes") 16 | class SpreadingAttributes extends declared(Widget) { 17 | 18 | private _propsToSpread: any = { 19 | "aria-role": "button", 20 | tabIndex: "0", 21 | title: "static-title" 22 | } 23 | 24 | //-------------------------------------------------------------------------- 25 | // 26 | // Public Methods 27 | // 28 | //-------------------------------------------------------------------------- 29 | 30 | render() { 31 | return ( 32 |
    Check out my attributes!
    33 | ); 34 | } 35 | 36 | } 37 | 38 | export = SpreadingAttributes; 39 | -------------------------------------------------------------------------------- /patterns/SyntheticEvents.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import Widget = require("esri/widgets/Widget"); 5 | 6 | import { 7 | declared, 8 | property, 9 | subclass 10 | } from "esri/core/accessorSupport/decorators"; 11 | 12 | import {tsx, renderable} from "esri/widgets/support/widget"; 13 | 14 | const CSS = { 15 | base: "esri-synthetic-events", 16 | header: "esri-synthetic-events__header", 17 | input: "esri-synthetic-events__input", 18 | footer: "esri-synthetic-events__footer" 19 | }; 20 | 21 | @subclass("examples.SyntheticEvents") 22 | class SyntheticEvents extends declared(Widget) { 23 | 24 | //-------------------------------------------------------------------------- 25 | // 26 | // Properties 27 | // 28 | //-------------------------------------------------------------------------- 29 | 30 | //---------------------------------- 31 | // selectedItem 32 | //---------------------------------- 33 | 34 | @property() 35 | @renderable() 36 | selectedNodeType: string = null; 37 | 38 | //-------------------------------------------------------------------------- 39 | // 40 | // Public Methods 41 | // 42 | //-------------------------------------------------------------------------- 43 | 44 | render() { 45 | const lastSelection = this.selectedNodeType ? this.selectedNodeType : "n/a"; 46 | 47 | return ( 48 |
    51 |
    54 | Clicking on an element will emit a synthetic event 55 |
    56 | 61 |
    64 | Last selected element is: {lastSelection}
    65 |
    66 | ); 67 | } 68 | 69 | //-------------------------------------------------------------------------- 70 | // 71 | // Private Methods 72 | // 73 | //-------------------------------------------------------------------------- 74 | 75 | private _handleClick(event: MouseEvent): void { 76 | const node = event.target as HTMLElement; 77 | const nodeName = node.nodeName; 78 | 79 | this.selectedNodeType = nodeName; 80 | 81 | this.emit("item-selected", { 82 | selectedItemNode: nodeName 83 | }); 84 | } 85 | 86 | } 87 | 88 | export = SyntheticEvents; 89 | -------------------------------------------------------------------------------- /patterns/TogglingNodeVisibility.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared, property} from "esri/core/accessorSupport/decorators"; 5 | 6 | import Widget = require("esri/widgets/Widget"); 7 | 8 | import {renderable, tsx} from "esri/widgets/support/widget"; 9 | 10 | const CSS = { 11 | base: "esri-toggling-node-visibility", 12 | header: "esri-toggling-node-visibility__header", 13 | content: "esri-toggling-node-visibility__content", 14 | footer: "esri-toggling-node-visibility__footer" 15 | }; 16 | 17 | @subclass("examples.TogglingNodeVisibility") 18 | class TogglingNodeVisibility extends declared(Widget) { 19 | 20 | //-------------------------------------------------------------------------- 21 | // 22 | // Properties 23 | // 24 | //-------------------------------------------------------------------------- 25 | 26 | //---------------------------------- 27 | // headerVisible 28 | //---------------------------------- 29 | 30 | @property() 31 | @renderable() 32 | headerVisible = true; 33 | 34 | //---------------------------------- 35 | // contentVisible 36 | //---------------------------------- 37 | 38 | @property() 39 | @renderable() 40 | contentVisible = true; 41 | 42 | //---------------------------------- 43 | // footerVisible 44 | //---------------------------------- 45 | 46 | @property() 47 | @renderable() 48 | footerVisible = true; 49 | 50 | //-------------------------------------------------------------------------- 51 | // 52 | // Public Methods 53 | // 54 | //-------------------------------------------------------------------------- 55 | 56 | render() { 57 | const headerNode = this.headerVisible ? ( 58 |
    header
    59 | ) : null; 60 | 61 | const contentNode = this.contentVisible ? ( 62 |
    content
    63 | ) : null; 64 | 65 | const footerNode = this.footerVisible ? ( 66 |
    footer
    67 | ) : null; 68 | 69 | return ( 70 |
    71 | {headerNode} 72 | {contentNode} 73 | {footerNode} 74 |
    75 | ); 76 | } 77 | 78 | } 79 | 80 | export = TogglingNodeVisibility; 81 | -------------------------------------------------------------------------------- /patterns/UsingDgrid.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | /// 5 | /// 6 | 7 | import {subclass, declared, property} from "esri/core/accessorSupport/decorators"; 8 | 9 | import Widget = require("esri/widgets/Widget"); 10 | 11 | import {renderable, tsx} from "esri/widgets/support/widget"; 12 | 13 | declare const OnDemandGrid: any; 14 | declare const Memory: any; 15 | 16 | const CSS = { 17 | base: "esri-using-dgrid", 18 | grid: "esri-using-dgrid__grid" 19 | }; 20 | 21 | @subclass("examples.UsingDgrid") 22 | class UsingDgrid extends declared(Widget) { 23 | 24 | //-------------------------------------------------------------------------- 25 | // 26 | // Lifecycle 27 | // 28 | //-------------------------------------------------------------------------- 29 | 30 | destroy() { 31 | if (this._grid) { 32 | this._grid.destroy(); 33 | this._grid = null; 34 | } 35 | } 36 | 37 | //-------------------------------------------------------------------------- 38 | // 39 | // Variables 40 | // 41 | //-------------------------------------------------------------------------- 42 | 43 | _grid: any = null; 44 | 45 | _items: any = new Memory({ 46 | idProperty: "firstName", 47 | data: [ 48 | { 49 | firstName: "Jerry", 50 | lastName: "Seinfeld" 51 | }, 52 | { 53 | firstName: "George", 54 | lastName: "Costanza" 55 | }, 56 | { 57 | firstName: "Elaine", 58 | lastName: "Benes" 59 | }, 60 | { 61 | firstName: "Cosmo", 62 | lastName: "Kramer" 63 | } 64 | ] 65 | }); 66 | 67 | //-------------------------------------------------------------------------- 68 | // 69 | // Properties 70 | // 71 | //-------------------------------------------------------------------------- 72 | 73 | //---------------------------------- 74 | // gridVisible 75 | //---------------------------------- 76 | 77 | @property() 78 | @renderable() 79 | gridVisible = true; 80 | 81 | //-------------------------------------------------------------------------- 82 | // 83 | // Public Methods 84 | // 85 | //-------------------------------------------------------------------------- 86 | 87 | render() { 88 | const gridNode = this.gridVisible ? ( 89 |
    91 | ) : null; 92 | 93 | return ( 94 |
    95 | {gridNode} 96 |
    97 | ); 98 | } 99 | 100 | //-------------------------------------------------------------------------- 101 | // 102 | // Private Methods 103 | // 104 | //-------------------------------------------------------------------------- 105 | 106 | private _handleAfterCreate(node: Element): void { 107 | if (!this._grid) { 108 | this._grid = new OnDemandGrid({ 109 | className: CSS.grid, 110 | collection: this._items, 111 | columns: [ 112 | {label: "First Name", field: "firstName", sortable: true}, 113 | {label: "Last Name", field: "lastName"} 114 | ] 115 | }, node); 116 | 117 | this._grid.on("click", (event: any) => this.emit("click", event)); 118 | 119 | return; 120 | } 121 | 122 | node.appendChild(this._grid.domNode); 123 | } 124 | 125 | } 126 | 127 | export = UsingDgrid; 128 | -------------------------------------------------------------------------------- /patterns/UsingDijit.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | /// 5 | /// 6 | 7 | /// 8 | 9 | import {subclass, declared, property} from "esri/core/accessorSupport/decorators"; 10 | 11 | import Widget = require("esri/widgets/Widget"); 12 | 13 | import {renderable, tsx} from "esri/widgets/support/widget"; 14 | 15 | declare const Button: any; 16 | 17 | // using any since not all dijits have type definitions 18 | // submitting type definitions to https://github.com/dojo/typings is encouraged 19 | declare const HorizontalSlider: any; 20 | 21 | const CSS = { 22 | base: "esri-using-dijit", 23 | button: "esri-using-dijit__button", 24 | slider: "esri-using-dijit__slider" 25 | }; 26 | 27 | function randomNumber(): number { 28 | return Math.floor(Math.random() * 100); 29 | } 30 | 31 | @subclass("examples.UsingDijit") 32 | class UsingDijit extends declared(Widget) { 33 | 34 | //-------------------------------------------------------------------------- 35 | // 36 | // Lifecycle 37 | // 38 | //-------------------------------------------------------------------------- 39 | 40 | destroy() { 41 | if (this._button) { 42 | this._button.destroy(); 43 | this._button = null; 44 | } 45 | 46 | if (this._slider) { 47 | this._slider.destroy(); 48 | this._slider = null; 49 | } 50 | } 51 | 52 | //-------------------------------------------------------------------------- 53 | // 54 | // Variables 55 | // 56 | //-------------------------------------------------------------------------- 57 | 58 | private _button: any; 59 | private _slider: any; 60 | 61 | //-------------------------------------------------------------------------- 62 | // 63 | // Properties 64 | // 65 | //-------------------------------------------------------------------------- 66 | 67 | //---------------------------------- 68 | // minValue 69 | //---------------------------------- 70 | 71 | @property() 72 | @renderable() 73 | minValue = 0; 74 | 75 | //---------------------------------- 76 | // maxValue 77 | //---------------------------------- 78 | 79 | @property() 80 | @renderable() 81 | maxValue = 100; 82 | 83 | //---------------------------------- 84 | // value 85 | //---------------------------------- 86 | 87 | @property() 88 | @renderable() 89 | value = 50; 90 | 91 | //---------------------------------- 92 | // randomNumber 93 | //---------------------------------- 94 | 95 | @property({ 96 | readOnly: true 97 | }) 98 | @renderable() 99 | randomNumber = randomNumber(); 100 | 101 | //-------------------------------------------------------------------------- 102 | // 103 | // Public Methods 104 | // 105 | //-------------------------------------------------------------------------- 106 | 107 | render() { 108 | return ( 109 |
    110 |
    111 | {this.randomNumber} 112 |
    113 |
    114 | {this.value} 115 |
    116 |
    117 | ); 118 | } 119 | 120 | //-------------------------------------------------------------------------- 121 | // 122 | // Private Methods 123 | // 124 | //-------------------------------------------------------------------------- 125 | 126 | private _createButton(node: Element): void { 127 | this._button = new Button({ 128 | class: CSS.button, 129 | label: "I am a dijit button", 130 | onClick: () => { 131 | console.log("dijit button was clicked"); 132 | this._set("randomNumber", randomNumber()); 133 | // this.scheduleRender(); 134 | } 135 | }, node); 136 | 137 | this._button.startup(); 138 | } 139 | 140 | private _createSlider(node: Element): void { 141 | this._slider = new HorizontalSlider({ 142 | class: CSS.slider, 143 | intermediateChanges: true, 144 | minimum: this.minValue, 145 | maximum: this.maxValue, 146 | value: this.value, 147 | onChange: (value: number) => { 148 | this.value = value; 149 | } 150 | }, node); 151 | 152 | this._slider.startup(); 153 | } 154 | 155 | } 156 | 157 | export = UsingDijit; 158 | -------------------------------------------------------------------------------- /patterns/WorkingWithSVG.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import {subclass, declared, property} from "esri/core/accessorSupport/decorators"; 5 | import {renderable, tsx} from "esri/widgets/support/widget"; 6 | 7 | import Widget = require("esri/widgets/Widget"); 8 | 9 | import Color = require("esri/Color"); 10 | 11 | const CSS = { 12 | base: "esri-working-with-svg" 13 | }; 14 | 15 | @subclass("examples.WorkingWithSVG") 16 | class WorkingWithSVG extends declared(Widget) { 17 | 18 | //-------------------------------------------------------------------------- 19 | // 20 | // Lifecycle 21 | // 22 | //-------------------------------------------------------------------------- 23 | 24 | postInitialize() { 25 | 26 | // changing colors for demo purposes 27 | setInterval(() => { 28 | this._currentColorIndex = (this._currentColorIndex + 1) % this._demoColors.length; 29 | this.color = new Color(this._demoColors[this._currentColorIndex]); 30 | }, 800); 31 | } 32 | 33 | //-------------------------------------------------------------------------- 34 | // 35 | // Variables 36 | // 37 | //-------------------------------------------------------------------------- 38 | 39 | private _currentColorIndex = 0; 40 | 41 | private _demoColors = [ 42 | "#E44D26", 43 | "97270A", 44 | "9DE83E", 45 | "0C34B1", 46 | "123297" 47 | ]; 48 | 49 | //-------------------------------------------------------------------------- 50 | // 51 | // Properties 52 | // 53 | //-------------------------------------------------------------------------- 54 | 55 | //---------------------------------- 56 | // color 57 | //---------------------------------- 58 | 59 | @property({ 60 | type: Color 61 | }) 62 | @renderable() 63 | color = new Color(this._demoColors[this._currentColorIndex]); 64 | 65 | //---------------------------------- 66 | // headerVisible 67 | //---------------------------------- 68 | 69 | @property() 70 | @renderable() 71 | headerVisible = true; 72 | 73 | //-------------------------------------------------------------------------- 74 | // 75 | // Public Methods 76 | // 77 | //-------------------------------------------------------------------------- 78 | 79 | render() { 80 | const hex = this.color.toHex(); 81 | 82 | const header = this.headerVisible ? ( 83 | 84 | 85 | 86 | 87 | 88 | 89 | ) : null; 90 | 91 | return ( 92 |
    93 | {/* source: https://www.w3.org/html/logo/ */} 94 | 95 | HTML5 Logo 96 | 97 | {header} 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 |
    107 | ); 108 | } 109 | 110 | } 111 | 112 | export = WorkingWithSVG; 113 | -------------------------------------------------------------------------------- /patterns/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "patterns": [ 3 | "AccessibleNodes", 4 | "Binding", 5 | "BooleanAttributes", 6 | "CleaningUp", 7 | "CustomEventHandlerArguments", 8 | "DistinguishableChildren", 9 | "DealingWithWhitespace", 10 | "DefiningGettersAndSetters", 11 | "HandlingChildrenEvents", 12 | "HandlingMultipleEvents", 13 | "InnerHTML", 14 | "ManagingListItemFocus", 15 | "NodeCreation", 16 | "ReferencingNodesOnDemand", 17 | "RenderingArrayItems", 18 | "RenderingCollectionItems", 19 | "RoleAttribute", 20 | "SchedulingRender", 21 | "SpreadingAttributes", 22 | "SyntheticEvents", 23 | "TogglingNodeVisibility", 24 | "UsingDgrid", 25 | "UsingDijit", 26 | "WorkingWithSVG" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /patterns/support/loadStylesheet.ts: -------------------------------------------------------------------------------- 1 | import Require = dojo.Require; 2 | 3 | function loadStylesheet(url: string, done: Function): void { 4 | const link = document.createElement("link"); 5 | link.type = "text/css"; 6 | link.rel = "stylesheet"; 7 | link.href = url; 8 | 9 | link.onload = (): void => { 10 | link.onload = null; 11 | link.onerror = null; 12 | done(link); 13 | }; 14 | 15 | link.onerror = (): void => { 16 | link.onload = null; 17 | link.onerror = null; 18 | done(link); 19 | }; 20 | 21 | document.head.appendChild(link); 22 | } 23 | 24 | const plugin = { 25 | load(name: string, require: Require, done: Function): void { 26 | loadStylesheet(require.toUrl(name), done); 27 | } 28 | }; 29 | 30 | export = plugin; 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "amd", 4 | "noImplicitAny": true, 5 | "jsx": "react", 6 | "jsxFactory": "tsx", 7 | "target": "es5", 8 | "experimentalDecorators": true, 9 | "preserveConstEnums": true, 10 | "suppressImplicitAnyIndexErrors": true 11 | }, 12 | "files": [ 13 | "node_modules/dojo-typings/dojo/1.11/index.d.ts", 14 | "node_modules/dojo-typings/dojo/1.11/modules.d.ts", 15 | "node_modules/dojo-typings/dojo/1.11/loader.d.ts", 16 | 17 | "node_modules/dojo-typings/dijit/1.11/index.d.ts", 18 | "node_modules/dojo-typings/dijit/1.11/modules.d.ts", 19 | 20 | "node_modules/dojo-typings/dojox/1.11/index.d.ts", 21 | "node_modules/dojo-typings/dojox/1.11/modules.d.ts" 22 | ], 23 | "include": [ 24 | "browser/", 25 | "patterns/", 26 | "typings/" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /typings/arcgis-js-api-extensions.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace __esri { 2 | 3 | interface promiseUtils { 4 | timeout(promise: IPromise, timeout: number, timeoutRejectValue: any): IPromise; 5 | } 6 | 7 | } 8 | --------------------------------------------------------------------------------