- Intro
- Class Api
- Functional Api
- Advanced
- Access Control
- Permissions
- OperationStack
- Metrics
- Queries & Filters
- Catalogs & Collections
- Composing Workflows
- Developers
- Legacy
Working with Hub Classes
Core Hub business logic has been wrapped into a set of javascript classes, representing the core Hub Entities: Site, Page, Initiative and Project.
Additional classes are planned for Event, Team, Discussion and Person.
For most developers, the Classes are the simplest way to work with ArcGIS Hub.
Class Patterns
The Class pattern used in hub.js separates the methods (the behavior) from the underlying entity (the structure). In the example below we have the HubSite
class which contains an entity
of type IHubSite
. The methods on the class are composed from "behavioral" interfaces, which ensure consistent methods across types. Similarly the entity is composed from structural interfaces, ensuring consistent object structures across entities.
To manipulate properties of the underlying entity, we first extract the entity into an object-literal via the .toJson()
method. At this point we have a simple object that adheres to the entity interface (IHubSite
in the diagram). We can then make changes to the object directly, but more commonly we will pass it into a component, which can then treat it as an immutable/clonable object. Once the componet has applied changed to the object, we reload that back into the class instance using the .update(...)
method. It should be noted that changes are not stored until .save()
is called.
Available Classes
- WIP
HubSite
- WIP
HubPage
HubProject
HubInitiative
Working with Classes
Classes are instantiated via factory functions, which all take an ArcGISContext
to provide platform and identity information. The ArcGISContextManager
documentation has additional background information.
Factory Function Examples
import { HubProject, ArcGISContextManager } from "@esri/hub-common";
// Typically your application will manage a context manager instance
const ctxMgr = await ArcGISContextManager.create({
username: "casey",
password: "abc123",
});
// get the context from the manager
const ctx = ctxMgr.context;
// If you have the slug or id, use the `.fetch(...)` factory function
// In Ember, this is commonly used in the model hook of a route, and the instance can
// be passed forward into the controller
const smithStProject = await HubProject.fetch("dc::smith-st-forestry", ctx);
// If your app has already fetched the IHubProject object literal,
// create an instance using the `.fromJson(...)` factory function
// note: since it's not fetching anything, this is a sync call
const pineStProject = HubProject.fromJson(pineStObjectLiteral, ctx);
// To create a new project, pass in an Partial<IHubProject> to `.create(..)`
// which will return instance for a new Project. If you want to immediately save the
// it, pass in `true` as the third argument.
const oakStProject = await HubProject.create({ title: "Oak St Project" }, ctx);
Working with Editors
The Hub Classes are designed to work with the ArcGIS Configuration Editor component. This component is a generic editor interface that's driven by a json-schema for the entity being edited. While the editor will have a default interface layout, additional control is available by passing in a ui-schema.
In order to ensure consistent editing behavior and user experience, all Hub classes expose these schema's via static properties:
.jsonSchema
- full schema for all properties of the entity.fullUiSchema
full ui for comprehensive editing of the entity.minimalUiSchema
minimal fields needed to create a new entity
Regardless if you are using the ArcGIS Configuration Editor, or some other component or just need to change properties on the entity programatically, you must "extract" the entity object literal from the class via .toJson()
, make changes to the object literal, and then push those changes back into the class via .update(objectLiteral)
. While nominally cumbersome in some situations, this pattern ensures developers do not pass class instances into components, which would be extremely problematic.
Editing Properties Example
const ctxMgr = await ArcGISContextManager.create({
username: "casey",
password: "abc123",
});
// get the context from the manager
const ctx = ctxMgr.context;
// get a project
const smithStProject = await HubProject.fetch("dc::smith-st-forestry", ctx);
// extract the IHubProject object literal
const prj = smithStProject.toJson();
// set the status and change the summary text
prj.status = "complete";
prj.summary =
"The Smith Street re-forestation project concluded on August 25th, 2022";
// apply the changes back into the instance
smithStProject.update(prj);
// now save changes back to the Portal
await smithStProject.save();
Editing via ArcGIS Configuration Editor Example
This is a simplistic example of editing a project using the <arcgis-configuration-editor>
component, showing a number of things:
- using the class to load the project from an id
- how we use
.toJson()
to get the "values" (aka the Object Literal) out of the class instance and into the editor - how we can use the schema static properties on the class to drive the editor
- how to apply changes from the editor into the instance in response to an event
- and finally, how to save changes when the Save button is clicked
This same pattern can be used with other components or frameworks.
export class myEditor {
@Prop
projectId: string;
@Prop
context: IArcGISContext;
private _project: HubProject;
async componentWillLoad() {
this._project = await HubProject.fetch(this.projectId, this.context);
}
get values() {
// wrap the object literal into a {values: <any>} structure
return { values: this.project.toJson() };
}
@Listen("arcgisConfigurationEditorChange")
updateValues(evt: CustomEvent<IChangeEventDetail>): void {
// apply changes into the project if valid
if (evt.detail.valid) {
this.project.update(evt.detail.values);
}
// typically we'd disable the save button if the editor is in an invalid state
}
handleSave() {
// since project is kept up to date, we can just call Save
this.project.save();
}
render() {
return (
<Fragment>
<arcgis-configuration-editor
schema={this.project.jsonSchema}
uiSchema={this.project.fullUiSchema}
values={this.values}
/>
<calcite-button onClick={handleSave}>Save</calcite-button>
</Fragment>
);
}
}
Sections to Write
Key Patterns for Dev's building classes
- separation of structure from behavior
IWith<Noun>
structural interfaces vsIWith<Noun>Behavior
interfaces
- limited inheritance
- delegation to functions
Developer Experience
- create instance via factory functions
- "extract to edit"
- optimized for reactive ui layers
- working with editor components
- json schema + ui schema
Composition via Interfaces and Functions
Manager classes and the entity objects they work with are defined via interface composition.
The Managers all implement the IHubEntityManager
interface, which defines the basic CRUD+Search operations.
Additionally, Managers also implement "trait interfaces" (e.g. IWithLayoutManager
), which define standardized behaviors. This allows different entity types of compose their behavior and properties based on common patterns, while still enabling per-entity customization where necessary. The actual implementation of the behaviors is typically delegated to a generic function, ensuring all entities implement the same behavior the same way, while still allowing for minor variations.
Similarly, the entity objects will conform to the entity specific interface (e.g. IHubProject
) but those interfaces are composed with "trait interfaces" to ensure consistent properties are used across the types.
For example IHubProject
extends the IWithSlug
interface, and the HubProjectManager
implements the IWithSlugManager
interface, operating on the properties defined by IWithSlug
.
This pattern ensures consistent API's across entities, powered by stand alone functions, composed into the Manager Classes and entity objects.