D e e p D i v e o n H o w A r c G I S A P I f o r J av aS...
Transcript of D e e p D i v e o n H o w A r c G I S A P I f o r J av aS...
Deep Dive on How ArcGIS API forJavaScript Widgets Were BuiltMatt Driscoll –
JC Franco –
@driskull
@arfncode
1/51
AgendaPrerequisitesHow we got hereOur developmentlifecycleWidget development tipsTools we useResourcesQ & A
1/51
Prereqs: Accessor esri/core/AccessorAccessor SDK
Building Classes Using Accessor and theArcGIS API for JavaScript
1/51
Prereqs: TypeScriptLeverage ES6 (syntactic sugar)InterfacesTypingconst and let vs var() => {} vs bind or this-binding utilityTypeScript SetupUsing TypeScript with ArcGIS API forJavascript
1/51
How we got here3.x
Dojo DijitDijit ThemesLogic tied to UI
4.xAbstracted & framework independentViewModels
1/51
Why?Framework independentEasily customizable themesResponsive designRedesigned Widget API
Consistent with core API
1/51
Our development lifecycleHow do we go about developing widgets?
1/51
Development lifecycleAPI designKickoff UI/UX designDevelop ViewModelDevelop ViewWrite testsPull requestAPI merge!!!
1/51
Development lifecycle: API DesignWidget developer writes objective for widget
Widget dev defines API in markdownView & ViewModel
PropertiesMethodsEvents
Sample code snippetsDemosQ & A
API reviewed and tweakedJS doc written and approved
Sample
1/51
Development lifecycle: ViewModelFriendly, consistent namingPublic methods
Return typesArguments
Public propertiesMake sure no view/UI logic
1/51
Development lifecycle: ViewResearch DOM structure needed for widget
Layout containers neededUsing proper semantic tags for nodes
CSS lookup object used in render()Accessible, Aria roles present if necessaryProperties, events, methods aliased as necessaryMake sure no API logic
1/51
Development lifecycle: StylesClasses neededBEM naming of classes4x Widgets can use flexbox for layout
1/51
Development lifecycle: UI/UX DesignMeeting with our creative labDiscuss needs, APICollaborate on design and tweak markup asnecessaryReceive mockup/wireframes/assets/SassImplement design
1/51
Development lifecycle: TestsMake sure we have tests that hit all the APIUnit, integration, functional, screenshot testsMethods are tested with all options and returntypesAssert properties behave as expected whenmodifiedScreenshot testsTest early
1/51
Development lifecycle: Pull RequestAll the code changes done in a git branchPR is opened with all changes and tests includedPR is reviewed and testedAPI build is successfulMerge!
1/51
Development tips
1/51
Development tips: API DesignHow things can be done differently in 4 compared to3
LeverageCollectionAccessor
View properties instead of eventsRead-only properties
Promises for async operationsSupport modules
Offloading logic where appropriate. Moremodular.
1/51
Development tips: render()Hiding nodes
render() { const childNode = this.childVisible? < return ( <div>{childNode}</div> ); }
1/51
Development tips: render()Reusing classes
const CSS = { root: "example", part: "example__part", disabled: "example--disabled" };
1/51
Development tips: render()Toggling classes
render() { const dynamicClasses = { [CSS.disabled]: this.isDisabled }; return ( <div classes={dynamicClasses}>...</ ); }
1/51
Development tips: render()class cannot change between renders
render() { const rootClass = someCondition ? CSS.foo : CSS.bar; // throws error - cannot change class return ( <div class={rootClass}>...</div> ); }
1/51
Development tips: render()Use join to apply multiple classes
render() { return ( <div class={join(CSS.root, CSS.button, CSS.shadow)}>...</ ); }
1/51
Development tips: render()Toggle styles (similar to classes)
render() { const dynamicStyles = { "x": getX(), "y": getY() }; return ( <div styles={dynamicStyles}>...</div ); }
1/51
Development tips: render()Attributes are not removed between render calls
render() { const tabIndex = this.focusable ? 0 : // `tabIndex` attribute will get rendered when value is falsy return ( <div tabIndex={tabIndex}>...</div> ); }
1/51
Development tips: render()String templates!
render() { return ( <div class={CSS.root}>`Hello, ${this ); }
1/51
Development tips: render()Distinguishable children
render() { // children are NOT dynamically added/removed, `key` is NOT needed return ( <div> <div>foo</div> <div>bar</div> </div> ); }
1/51
Development tips: render()Distinguishable children
Note: key can be a string, number or object
render() { const foo = this.showFoo? <div key="foo" const bar = this.showBar? <div key="bar" // children are dynamically added/removed, `key` IS needed return ( <div> {foo} {bar} </div> ); }
1/51
Development tips: render()Storing data on attributes
render() { return ( <div onclick={this._handleClick} data-lucky-numbers={luckyNumbers()}> ); } private _handleClick(event: MouseEvent) { const node = event.currentTarget as Element; const luckyNums = node.getAttribute("data-lucky-numbers" console.log(`Today's lucky numbers are: ${luckyNums}`);}
1/51
Development tips: render()Binding
render() { return ( <div class={CSS.base}> <div onclick={this._logThis}>this <div bind={this} onclick={this._logThis}>this </div> ); }
1/51
Development tips: render()DOM events
render() { return ( <div class={CSS.base}> <img onclick={this._handleClickEvent} /> </div> ); } private _handleClickEvent(event: MouseEvent) { // do something with event }
1/51
Development tips: render()Real nodes
Markup in render() is virtualNeed to store reference to actual node withafterCreate or afterUpdate
private _realNode: Element = null; render() { return ( <div afterCreate={this._storeThisNode}>...</div>; ) } private _storeThisNode(node: Element): this._realNode = node; }
1/51
Development tips: render()accessibleHandler
render() { return ( <div onclick={this._doSomething} onkeydown={this._doSomething} /> ); } @accessibleHandler() private _doSomething(): void { // ... }
1/51
Development tips: render()Close childless node tags for conciseness
render() { return ( <div> <div class={CSS.childless} /> </div> ); }
1/51
Development tips: render()Keep render manageable by extracting pieces asit grows
render() { return <div>{this._renderContent()}</} private _renderContent(): any { return ( <div> <h1>{this.title}</h1> {this._renderItems()} </div> ); }
1/51
Development tips: ViewModelsRethinking APIs
More collectionsMore AccessorsView properties instead of eventsRead-only properties
Support modulesOffloading logic where appropriate. Moremodular
Autocasting
1/51
Development tips: StylingCSS to Sass
VariablesThemingMixins
/ SVG
SDK Guide: StylesSassBEMIcon font
1/51
Widget Theming: SassCSS preprocessorVariables@mixin (group statements)@include - (use mixins)@import - (split up files)@extend - (inheritance)More power!
1/51
Sass InstallInstalling gruntsass
$ npm install --save-dev grunt-sass
1/51
Widget BEM: Block Element Modifier
Methodology to create reusable componentsUses delimiters to separate block, element,modifiersProvides semantics (albeit verbose)Keeps specificity lowScopes styles to blocks
BEM
/* block */ .example-widget {} /* block__element */ .example-widget__input {} .example-widget__submit {} /* block--modifier */ .example-widget--loading {} /* block__element--modifier */ .example-widget__submit--disabled {}
1/51
Development tips: Styling withinViewCSS lookup object
Lookup referenced in JSX
const CSS = { base: "my-widget", title: "my-widget__title" };
<div class={CSS.base}/> <h1 class={CSS.title}>Hello world</h1</div>
1/51
Our ToolsIDEsTasksTestingOther
1/51
Tools: IDEsMultiple flavors
Visual Studio CodeWebStorm
Pluginsand more...
1/51
Tools: TasksNode/npmInstalling GruntCompile TS/Sass
1/51
TestingInternJasmineQUnitKarma (test runner)
1/51
Other toolsBesides an IDE and browser dev tools...
SourceTreeTerminalGitHub EnterpriseSlack :DA handful of browsersCoffee
1/51
Additional Resources4x widget snippetsJavaScript Sessions at DevSummitDocumentation 4.3
1/51
Please Take Our Survey!1. Download the Esri Events app and go to
DevSummit2. Select the session you attended3. Scroll down to the "Feedback" section4. Complete Answers, add a Comment, and Select
"Submit"
1/51
Questions?
1/51
1/51
1/51