UNPKG

@vertigis/viewer-spec

Version:

VertiGIS Viewer Specification

732 lines (491 loc) 36.9 kB
# Layouts ## Notes A Microsoft Word version of this document is maintained in the Products Team SharePoint site. This document should be treated as the source of the Word document. ## Amendments As per the March 17, 2017 meeting, the following modifications were decided upon: - Config items referenced from layout should support an additional short hand notation where the item type is not specified. Instead, since the item type for the particular component is already known, we can use that in conjunction with a supplied item id, to find the item on the app.json. Example: <map id="secondary" config="ortho-photos-2017" /> The URI based syntax is also supported. Example: <map id="secondary" config="item://MapExtension/ortho-photos-2017" /> - We won’t be building shell components until we need them, if we need them. Additionally, the term "shell" may or may not be used. - The term "group" is preferable and more clear than "region". - Regions/groups will not be implemented until we need them, if we need them. - CSS style selectors for targeting components were using a dot (.) in front of component types. The dot should be dropped to be in alignment with CSS. ## Intro The _ArcGIS Extensions API_ introduces a powerful schema for defining and managing spatial applications. This schema is used by our next-generation _viewers_, and represents the ability to express fully cross-platform _apps_ via singular, shareable items. For example, an _inspections.json_ app in your organization's Portal that defines all of the configuration involved in realizing a running instance of that particular application. The new API also represents a paradigm shift in how we think of spatial apps. In the previous generation, our API and viewers were built entirely around the notion of a single map. In the new paradigm, our configuration model expresses apps that have 0 maps, or 1 map, or many maps. We call this concept "arity", and it applies equally to all components: scenes, toolbars, menus, et cetera. In order to achieve the objective of truly cross-platform apps, and to account for the new paradigm of "arity", a configuration vehicle for expressing _application interfaces_ in flexible, technology-neutral terms is required. This new entity is known as an _Application Layout_, and is a resource that can be referenced by apps or inferred from a simple Web Map. This new entry into our lexicon represents a design language capable of rapidly expressing simple and complex apps alike, and in terms of reusable, contextually bound components. ## Overview Application Layouts are simple, human readable XML documents. They express the _structural composition_ and _visual arrangement_ of apps in terms of their constituent components, and how those components interact with each other. **Layouts are strictly technology neutral**. As a hard rule, implementation details from _specific technologies_ should never leak into layouts, just like they should never leak into the API. Like the API, layouts are platform-agnostic, and thus they can be used to define user interfaces that can be realized in any viewer and on any platform. Apps reference one more _layouts_ as _items_. When a viewer loads an app, it "selects" the appropriate layout to load for that app. The details of how a viewer selects the appropriate layout to load on app startup is left out of this document and can safely be considered a viewer-level implementation detail. However, it's worth noting that layouts can be hardcoded or synthesized as well. For example, a viewer may derive a layout directly from a Web Map when loading an app that references no layouts, or simply fall back to a map centric, in-built default. ### Notes - Layouts are concise, technology-agnostic XML documents - Layouts define user interfaces in simple, cross-platform ways - Layouts bind user interface elements to configured items - Layouts are no different than any other item an app can reference - Apps can reference multiple layouts, in which case a viewer will select the one to load - Layouts can be derived from Web Maps or Web Scenes when none is available ## Proposed Schema Versions In contrasting GMV and GWV requirements and objectives as well as potential enhancements, we have decided to consider multiple layout spec versions: 1.0: A scope-constrained 1.0 suitable for initial versions of GMV to use. 1.1: Introduces support for multiple components, and mechanisms for working with "arity" 1.2: A set of progressive enhancements that possibly includes accessibility mechanisms (e.g. skip lists), control over fault-tolerance, support for printing, and support for drag and drop This document covers the core aspects of 1.0 and 1.1. GMV will initially use spec 1.0, and GWV will use 1.1 to allow more flexible layouts. Each version of the spec will have an XSD. ### Schema 1.0 Version 1.0 addresses ground-level compatibility topics between viewers, primarily by defining and normalizing patterns and practices that may have implications that affect compatibility across viewers. #### 1.0 Topics: - App compatibility across GWV and GMV - Syntax and semantics - Binding of configuration to components - Lazy loading and baseline fault tolerance for components - Core set of initial components (i.e. `map`, `scene`, "primitives") - Technology-neutral, resolution-independent sizing and positioning attributes (a.k.a. "liquid layouts") ### Schema 1.1 Version 1.1 addresses secondary concerns and value additions that GMV 1.0 can safely defer. GWV has already implemented "arity", and accessibility (WCAG 2.0 AA) is required for GVH parity. Because of this, GWV should ship with schema 1.1. #### Topics: - Declarative control over "arity" (i.e. handling 0-to-many maps, scenes, etc) - Accessibility enhancements, i.e. skip lists, declarative screen reader content and controls ### Schema 1.2 Schema 1.2 extends previous versions #### Topics: - Declarative control over fault tolerance (e.g. "failure mode" attributes) - Non-interactive use cases (i.e. relationships to _Print Templates_, _Reports_) ## Syntax and semantics Layouts use simple XML to represent user interfaces with as little cognitive load as possible. Here is a layout expressing a simple spatial application comprised of a map: ```XML <layout> <map /> </layout> ``` XML elements in layouts represent discrete, loosely-coupled _components_, and the _attributes_ they bear represent public properties that configurators can set on them. The root "layout" component in a layout is where XML namespaces can be specified. This provides a simple extensibility mechanism that will allow implementors (i.e. Prof. Services, partners) to extend the product with their own components that can be used in layouts and managed by a future management application. Example: ```XML <layout xmlns:cw="cityworks/geocortex/components/v1"> <split> <map /> <scene /> </split> <cw:work-items /> <popup> <cw:new-work-item active="false" /> </popup> </layout> ``` In this example, the custom namespace for Cityworks is added to a layout document so that its components may participate. How these components are "installed" into and loaded by a viewer is outside of the scope of this document. We recognize that admins and implementors frequently hand-edit configuration, and thus consider it a part of UX. With this in mind, our syntax and semantics are intended to be concise and human-friendly. Like apps, layouts are always _human readable_, and suitable for _hand-editing_. In layouts, element and attribute names are "kebab-cased". Words are always lowercase and separated by hyphens: ```XML <map> <my-awesome-component has-awesomeness="true" slot="top-left" /> </map> ``` Double-quotes are used for attributes, and we suggest that attribute values use the same kebab-cased convention. # Notes - Layouts are human-friendly, expressed in simple, concise XML - Layouts can include custom components via XML namespaces - Layout elements and attribute names are "kebab-cased": - All lowercase - Words separated by hyphens, i.e. `<my-custom-component spin-map="true" />` - Double-quotes are used for attributes - Kebab-casing suggested for attribute values as well ## Layouts bind components to config As a viewer loads a layout, it maps components to configuration items in an _app container_, resolving them asynchronously and passing them to the components for consumption during initialization. These bindings to config are known as "item references", and they can be explicit or implicit. Example: ```XML <stack> <map id="primary" /> <map id="secondary" config="item://MapExtension/ortho-photos-2017" /> </stack> ``` In this example, the map "primary" has an _implicit_ item reference, while the secondary map has an _explicit_ item reference. When a component does not have an explicit reference to configuration, a default one can be observed from viewer-level configuration and metadata using a component's "default item type". For example, metadata/config for a "map" component can specify that its default item type is "MapExtension". When the viewer finds a map component with no item reference specified, it can use the following pattern to infer a default: item://ITEM-TYPE/default ...where `ITEM-TYPE` is cased appropriately and in accordance to the API's convention, i.e.: item://Map/default ## Missing configuration When a component implicitly or explicitly references config that does not exist in the app, the component is said to have _missing config_. This type of fault is not treated as _exceptional_. Depending on use case, it may be A-OK to simply hide components who have missing config. For any component encountered that is found to have _missing config_, a viewer may choose to do one of the following: A. Halt the loading process, indicating to the user that a component is missing configuration and the app failed to load B. Issue a warning to the user, continuing the loading process, ignoring any sub-components configured under the failed one Which option a viewer takes for a given component is referred to as the component's _failure mode_, and declarative control over this is deferred as a possible 1.2 topic. ## Unresolved configuration When a component has an explicit or implicit item reference, but that item reference fails to load, it is considered as having _unresolved configuration_. Unlike _missing config_, this class of fault is exceptional - it indicates an erroneous situation. Something has caught on fire, or the user does not have access. There are 3 scenarios under which a component is considered as having _unresolved config_: A) A service failure, i.e. the item was an external reference pointing to a Portal instance that is down or faulting B) Failed authentication or authorization C) Malformed configuration As a viewer loads a layout, instances of unresolved config (and for why they are unresolved) are collected for the purposes of presenting a user a singular prompt for which to acknowledge the errors, and/or retry an authentication flow. As is the case with _missing config_, a viewer that loads components with _unresolved config_ can continue loading if and when components fail to load unresolved config. However, since unresolved configuration represents exceptional (erroneous) behavior, the user should receive notification of the fault(s). ## Loading sequence This layout consists of a single web map and for which a _binding to the default `MapExtension` in the app is implied:_ ```XML <map /> ``` Upon encountering a component bearing no item reference, a viewer will: 1. Check the component's metadata for a "default item type" 1. If no default item type is specified, the component can simply be initialized with empty configuration and this process completed 1. If a default item type is specified, then an appropriate _item ref_ is derived and requested from the _app container_ 1. If the item does not exist in the container, the viewer will consider the component as having _missing config_ 1. If the item exists in the container, but cannot be resolved to due auth or service failure, it is considered to have _unresolved config_ 1. When a component has _missing_ or _unresolved_ config, the viewer may choose to do one of the following: A. Halt the loading process, indicating to the user that a component is missing configuration and the app failed to load B. Fallback to "baked in" configuration for the component, colocated with the module, entering a mode of "outright failure", or option C C. Logging a warning, and continuing the loading process, ignoring any sub-components configured under the failed one Which option is chosen is considered a component's _failure mode_. The reason that the two types of configuration faults are distinct is that simply missing configuration is a different class of fault than unresolved config. Future schema versions may wish to treat these faults differently. Missing config simply means that the contents of the layout and the app don't necessarily line up, and that is OK in certain use cases. Debug warnings can be logged, but the viewer may not wish to expose any "failure" to the average user. This means that we can use any layout with any app, and simply gracefully degrade. In other words, a viewer's default layout is capable of loading _any_ app without outright failure. The user simply won't see components that are missing config. Contrast this to _unresolved_ config, where we are either not authorized, or a Portal is broken or has moved, or we have encountered outright broken configuration. For this class of failure, we will most likely want to at least notify the user to indicate that an exceptional situation has occurred. **Note:** For GMV 1.0 and GWV 1.0, we suggest a default "failure mode" of "continue loading whatever else you can, logging warnings to the console". Declarative control of this is proposed as a future topic for Schema 1.2. ### Summary - When an item reference and a _defaults.json_ are not available, the viewer will simply warn the user and continue loading - This "baseline" of fault tolerance may be controlled by admins and developers in the future, i.e. to explicitly fail on missing config - For 1.0, the viewer will just continue loading and emit useful log information saying that component X or Y could not be loaded to to missing config ## Composition: Slotting Consider the following example layout: ```XML <map> <scale-bar /> </map> ``` In this example, how is the _Scale Bar_ component hosted in the _Map_ component? Is it simply placed over top of the map in the center? How might a user anchor the scale bar component to a particular corner or edge of the map? To phrase in more technical terms: how are components _composed_ together? The answer to this question is "slotting". > Note: The behavior and terminology for "slotting" is borrowed from the _Web Components_ spec, where the major browser vendors have collectively solved and defined the problems and solutions to composing elements hierarchically. _Slotting_ is the act of hosting components inside of other components, and involves a declarative mechanism for layout authors to target "slots" when composing components together. For example, let's assume the map component allows child components to be anchored to its four corners, each one bearing a name such as "top-left". To anchor to the top right corner, a layout author may express the following: ```XML <map> <scale-bar slot="top-right" /> </map> ``` > Note: The behavior described below is currently specific to GWV. > When the layout loader encounters the scale bar, it will recognize the "slot" attribute, defined in our Geocortex namespace. > When it goes to "slot" the scale bar into the map, it will make a "slotting request" to the map component, passing it some information about the child component as well as the intended slot target. The map component may "accept" or "reject" the slotting request. If it accepts, it will place the child component in the correct position in its own DOM in order to satisfy the slotting request. If a component "rejects" a slotting attempt during loading, the child component enters a failure status and the app can either fail, or continue to load according to its policy on component failures. The notion of a slotting "request" is realized as nothing more than a simple method call on a component that may return true or false. > Note: While this mechanism is dirt-simple, formalizing it helps conceptually enable potential support for drag-and-drop app creation down the road. ### Implicit slots When a slot attribute is present, this is known as _named slotting_ or _explicit slotting_. When no slot attribute is present, this is known as _implicit slotting_. Consider a previous example: ```XML <map> <scale-bar /> </map> ``` In this example, the scale bar component specifies no "slot" argument and thus is requesting the "implicit slot". As with named slotting, components can reject implicit slotting. > Note: The notion of a rejectable slotting "request" is currently GWV specific. Here, the map may choose to reject the slotting request, since it only has named slots: the four corners of the map where content can be anchored. However, a component with no implicit slot behavior is free to satisfy a request for implicit slotting by simply slotting it into a default slot of its choosing, i.e. "bottom-left". This is a component-specific implementation detail. By supporting the simple compositional strategy of "slotting", we provide layout authors simple yet powerful tools for declaratively building simple and sophisticated apps alike, and entirely out of loosely-coupled, reusable components. ### Slotting examples Scale Bar and Overview Map explicitly slotted: ```XML <map> <scale-bar slot="bottom-left" /> <map config="item://MapExtension/overview" slot="bottom-right" /> </map> ``` A stack of implicitly slotted maps: ```XML <stack> <map /> <map /> <map /> </stack> ``` 2.x style "hoisting" into a panel's slot: ```XML <smart-panel id="data-frame"> <menu slot="hoist" config="item://Menu/results-list" /> <results-list /> </smart-panel> ``` Slotting into a "master detail page" component ```XML <mdp> <menu config="item://Menu/master" slot="master" /> <map config="item://MapExtension/detail-map" slot="detail" /> </mdp> ``` Slotting into a traditional "shell" component: ```XML <mobile-shell> <menu config="item://Menu/nav-mobile-main" slot="primary-menu" /> <map slot="main-area" /> <text slot="disclaimer-page"> Not for navigational use. Use at your own risk. </text> </mobile-shell> ``` ### Summary - When components are nested in other components in layouts, they are "slotted" into them - When a component attempts to slot into another, the layout loader asks the host component to satisfy the slotting - Host components can accept or reject slotting attempts (_Note:_ currently GWV specific) - When a slotting request fails during load, the slotee enters a failure state - When a child component bears a "slot" attribute, it targets a "named slot" in its parent - When a child component bears no "slot" attribute, it targets the "implicit slot" in its parent ## A note about "shells" Shells have been a winning pattern for us, particularly in the realm of GVH, where we can target wildly different form factors with specific arrangements of components and navigational structures. In the new "layout era", the role of shells is a bit diminished. It is layouts themselves that define the visual arrangement of components, as well as the compositional behavior between them. However, this is not to say that shells are obsolete. Sometimes, we need to address high-level UI concerns that aren't suitable for individual components to address themselves. For example, a complex interplay between config, a "home panel", and a "data frame" is best expressed specifically in a higher level component rather than polluting the constituents with specific application-level concerns. Shells are not obsolete. Shells are just high-level components comprised of other components! ## A note about "views" If components can implicitly host other components by default, and components can be parked inside other components via nominal "slots", it raises the following question: What happens to regions and views? In a nutshell: The term "view" now references the specific user interface of a component. For example, a Scale Bar _component_ may appear in a layout as "\<scale-bar />". This can be considered its public interface. However, under the hood, its _view_ will be comprised of native XAML components in .NET, and HTML primitives such as divs and spans in HTML. A view is a specific component's interface, realized using platform-specific UI controls and components. ## Regions Regions evolve a bit with the introduction of layouts, but are perhaps even more useful than before. In GVH, regions are nominal targets for views. In 2.x configs, user interfaces are expressed as flattened graphs, with views targeting regions by globally unique names and forming "edges" in the flattened graph. In the new hierarchical paradigm, regions serve as simple grouping constructs, as well as continuing to serve as nominal targets for commands and workflows. However, unlike GVH 2.x, regions are not defined in view templates: they are only defined in layouts. Regions are constructs that layout authors and management tools use to group and name components. Being top-level and participating in the declarative layout model, it is OK to give them globally unique identifiers: it is a configurable value. Inside the view of a reusable component, however, global IDs are harmful. For example, if you define a region on the map with a global ID, the map component effectively becomes a component you can only use _once_. When a developer of a component wants to define a named area for other components to park in, a _slot_ is defined and the name of the slot is local to the instance of that component. When a layout author wants to defined a named area for components to park in, they define a _region_ and can give it an ID globally unique in the layout. This example should demonstrate the difference: ```XML <map id="map1"> <region id="map1-top-left" slot="top-left" /> </map> <map id="map2"> <region id="map2-top-left" slot="top-left" /> </map> <button cmd="deactivate" targets="#map1-top-left"> Hide map 1 widgets. </button> ``` In the above example, the layout author has "slotted in" two regions into the "top-left" slots of two maps, assigning the global IDs "map1-top-left" and "map2-top-left" to the regions so they may be referenced elsewhere (i.e. by a button in the layout or in a workflow specific to the app). A less contrived example would be grouping components so that they may be treated as a singular block: ```XML <text>Here is some simple information.</text> <region id="advanced" active="false"> <text>Welcome, _power user_. Here is some more advanced information and some buttons to press</text> <button>beep</button> <button>boop</button> </region> <button cmd="activate" targets="#advanced">Show some advanced stuff</button> ``` **Note:** Regions don't even need "ids". They have utility as pure grouping structures, i.e. applying an attribute such as "active" to a block of components. While there are similarities between regions and slots, they are distinct because they serve different purposes for different audiences: Regions are defined by layout authors for naming groups of components, while slots are defined by developers as composition targets in views. ### Summary - Regions are defined in layouts - Slots are defined in views - Regions are useful grouping constructs ## Dispatch: Commands > Note: Dispatch is spec 1.1 Because our configuration and composition models support 0-to-many of any component, application authors need a way of specifying the targets of particular commands and event invocations. How messages such as commands and events are delivered to the correct recipients is known as "dispatch". Consider this example: ```XML <stack> <split> <map id="map" /> <map id="map2" /> <scene id="scene1" /> </split> <button cmd="zoom-in">Which map do I zoom?</button> <stack> ``` Here, a layout author has placed a button that invokes a "zoom in" command. Assuming the map and scene components implement the zoom command, all instances will zoom when the command is invoked. But what if the layout author's use case dictates only the first map to zoom? Layouts introduce a simple "binding syntax" that provides authors with a declarative mechanism for controlling dispatch. This syntax is entirely _nominal_ and bears no _structural_ concerns. Unlike a structural mechanism like XPath (which was considered) our syntax specifically avoids binding configuration to _application structure_. Since items in apps such as menus and toolbars may bear dispatch config, the nominal approach ensures that they aren't bound to the structure of any particular layout. For example, a configured menu item that zooms a particular map by name will function as intended no matter where in the app that map (should it exist) is hosted. This prevents app configs from becoming "brittle" and coupled to particular layouts. ### Binding syntax Binding syntax allows authors to target components by referencing components by ID or type. ### Nominal binding Components can be referenced by ID using the "#" character: ```XML <button cmd="zoom-in" targets="#map1">I zoom map1</button> ``` Multiple binding expressions (of any type) are allowed. Commas are used to separate bindings: ```XML <button cmd="zoom-in" targets"#map1, #map2">I zoom maps</button> ``` ### Type binding Components of a particular type can be target using the "." character: ```XML <button cmd="zoom-in" targets=".scene">I target scenes only</button> <button cmd="zoom-in" targets="#map1, .scene">I target #map1 and any scenes</button> ``` ### Attribute matching > Note: Spec version 1.2 Bindings can specify simple key-value patterns to match arbitrary attributes on component: ```XML <map role="navigation" /> <map /> <map role="imagery" /> <button cmd="zoom-in" targets=".map[role=navigation]">I target any map marked with role=navigation</button> ``` Attribute matching can apply to all components of any type as well, simply by omitting the name and type: ```XML <button cmd="zoom-in" targets="[role=navigation]">I target any component marked with role=navigation</button> ``` ## Model bindings > Note: Spec version 1.1 Components often observe and modify the state of other components. Consider the following example: ```XML <map config="item://MapExtension/mapA"> <scale-bar id="A" /> </map> <map config="item://MapExtension/mapB"> <scale-bar id="B" /> </map> ``` Our scale bar component displays information about a particular map. In our example, scale bars A and B are expected to show the correct scale based on their context. How can this be achieved without coupling the scale bar or map to any particular topology? Layouts achieve these simply by expressing data dependencies in the form of models that are marked as "imported" or "exported". If we know that the scale bar "imports" a particular type of model, and that the map "exports" the same type of model, we can resolve those dependencies simply by plugging exported models into the components that import them when the components are slotted together. Example (in TypeScript): ```TypeScript export class MapComponent { @exports("model://MapModel") model: MapModel; // ... map stuff ... } export class ScaleBarComponent { @imports("model://MapModel") mapModel: MapModel; // ... scale bar stuff ... } ``` When the system slots each scale bar into their parent map controls, it will attempt to satisfy any model imports declared by the component being slotted. By default, this happens by walking up the tree from the location of the component being slotted and simply finding the first ancestor that exports a model with the matching type. In this case, the system will immediately find the matching, exported model of the map and place it in the scale bar's "mapModel" property before completing the slotting. Now consider the following example, where the contents of the left panel all have dependency on a map model, yet none is available in the ancestry: ```XML <split> <panel id="left-panel"> <layer-list /> <legend /> <query-builder /> <scale-bar /> </panel> <region id="map-region"> <map id="map1" /> </region> </split> ``` When the system tries to resolve a map model for the layer list component, it will walk all the way up the tree before hitting the root layout component. Same for the legend, QB, and other components. In this particular layout, they do not have an ancestor that exposes a map model for consumption. To address this fairly common scenario, the same binding syntax used for dispatch is leveraged. This gives layout authors the ability to nominally bind components together at the configuration level: ```XML <layer-list models="#map" /> ``` By using the "models" attribute, the layout author provides enough of a "hint" to the system to satisfy the model dependency. The system will simply observe the binding and find the models it needs on the specified component. This is a simple yet effective way of expressing data dependencies between components. However, specifying model bindings on each component is less than ideal: ```XML <split> <panel id="left-panel"> <!-- gross --> <layer-list models="#map" /> <legend models="#map" /> <query-builder models="#map" /> <scale-bar models="#map" /> </panel> <region id="map-region"> <map id="map1" /> </region> </split> ``` In order to get around this, we simply observe the "models" attribute when we walk the ancestry looking for models. This allows the layout author to specify the "#map" hint on a parent component, and it will be used in model resolution: ```XML <split> <panel id="left-panel" models="#map"> <layer-list /> <legend /> <query-builder /> <scale-bar /> </panel> <region id="map-region"> <map id="map" /> </region> </split> ``` This is known as "model pinning". We've "pinned" any exported models from the map to the panel for its children to consume. Note that the panel component itself remains wholly uncoupled from any map concerns - it is simply decorated in this particular layout in order to keep the layout clean and concise. When a binding resolves to multiple models, we can simply install them as a collection. Consider a component that displays attribution information in one place and for a number of maps or scenes. In this case, the layout author can specify their binding as a binding to multiple items, and this will be respected during model resolution, placing the resolved models in an array before installing the collection on the components import. Example: ```XML <attribution models=".map" /> ``` It's worth noting that this strategy of contextually resolving model dependencies between components lends itself extremely well to a potential drag and drop implementation. When components are "dropped" into new locations, we can simply re-slot them and re-resolve their model dependencies. ## Presentation > Note: Originally out of scope for 1.0, but required by GMV to express basic UI structure. Layouts can bear attributes related to visual presentation such as widths, heights, margins, and paddings. These attributes are applied on components to control their presentation in the layout. Presentation attributes are explicitly _resolution independent_. Widths, heights, margins, and padding are expressed in terms _relative to the font size_, and pixel-based values are expressly forbidden. This keeps layouts and thus apps compatible and usable across everything from phone screens to large-scale displays, as well as the nice side effect of being printable at print DPIs, i.e. 300 DPI vs 96 DPI. If a layout author defines the width of an element of 20, what they are effectively stating is that the component is 20 characters wide in the layout. Same with height, margins, and paddings. The only attribute that is not relative to font size is the "grow" attribute. This attribute indicates how much space a component takes relative to others. When a component hosts multiple children with "grow" values, the total amount of space is the sum total of these value. Consider this example: ```XML <split> <map grow="1" /> <map grow="9" /> </split> ``` In this case, there are 10 units of space available. The first map has 10% of them, while the second has 90%. This particular layout can be understand to have a map that's 10% the height of the available space, and a second map that takes up the other 90%. ## Internationalization In order to support localized apps, layouts must be able to reference localized strings. A simple method is proposed: ```XML <panel title="@lang-panel-title" /> ``` This is how we represent configured, localizable strings in GVH 2.x today, and it makes sense to extend the mechanism to layouts. On top of localized string resources, RTL is desirable as well. Since layouts cannot express absolute pixel values, or use absolute positioning, the implementation/execution of RTL is a lot simpler than it would normally be in a system that uses absolute pixel offsets to "anchor" components in screen space. The primitive "split" component goes a long way here as well - we can simply reverse the order of child components to express the directional switch. By avoiding absolute coordinates, we create easy solutions for RTL, which is typically a tricky bag of issues to solve (particularly in open-ended systems such as ours). > Note: RTL is a 1.2 topic ## Common components As part of the layout specification, a common set of components for both viewer should be defined. Viewer specific components can live in viewer-specific namespaces, and components that we decide to make common can be elevated into the common namespace. The common set of components we have defined so far are: - stack - split - text - dialog - panel - button - map - scene Aside from map and scene, these components are considered "primitives": simple building blocks for layouts that are easy to implement and suitable for us to begin exploring and experimenting with application interfaces. Along with simple presentation attributes such as width and height, stack and split components give us trivially simply blocks that can form the basis for almost any user interface. Stack simply vertically stacks components, while split horizontally partitions screen real estate, flipping the order of components for RTL. Both stacks and splits can allow their child components to be resized. Dialogs handle popup behavior, and panels can provide functionality similar to "smart panels" today: accessible containers for content, and with panel-specific menu actions. The text component is similar in nature to our Markdown form item, and can show arbitrary blocks of rich content including images, and possibly command hyperlinks and substituted tokens, similar to our _Home Panel_ and _Feature Description_ functionality. ## Summary With app layouts, we have defined a simple yet extremely powerful visual design language that complements our configuration model by binding to it in flexible ways, and realizing the vision of truly cross-platform apps. This design language is suitable for representing spatial applications on the lowest and highest ends of the complexity spectrum. By remaining strictly technology neutral, and resolution independent, layouts also provide opportunities expressing visual arrangements for _Print Templates_, and/or _Reports_. Future value opportunities we've earmarked include powerful enhancements for progressing our accessibility story, as well as opportunities for live editing and drag and drop functionality. This spec and how layouts fit into our story around cross-platform apps is the result of multiple years worth of conversations, whiteboard sessions, and brainstorms. Thanks to everybody involved in bringing these ideas to fruition.