Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Arches Vue Components

A Vue 3 / PrimeVue component library for building custom applications with Arches versions 8.2.0+

Installation

pip install arches-vue-components

Project Configuration

  1. If you do not already have an Arches project, create one by following the instructions in the Arches documentation.

  2. Add arches_querysets and arches_vue_components to INSTALLED_APPS below the name of your project but above arches:

INSTALLED_APPS = (
    "my_project_name",
    ...
    "arches_querysets",
    "arches_vue_components",
    "arches",
    ...
)
  1. Add arches_vue_components as a dependency in package.json:
"dependencies": {
    "arches_vue_components": "archesproject/arches-vue-components#dev/2.1.x"
}
  1. Add the arches_vue_components URLs to urls.py:
urlpatterns = [
    path("", include("arches_vue_components.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
  1. Start your project and install frontend dependencies:
python manage.py runserver
npm install && npm run build_development
  1. Check for missing WidgetMapping records:
python manage.py validate --codes 2001 --verbosity 2

See Extending Arches Vue Components if any are missing.

Frontend API

Bootstrapping an app

import MyComponent from '@/my_project/MyComponent.vue';

import { createVueApplication } from '@/arches_vue_components/application';

createVueApplication({ component: MyComponent }).then(app => app.mount('#app'));

Widgets

GenericWidget looks up the widget mapped to a node (see Extending Arches Vue Components) and resolves the real component at runtime. In edit mode it wraps the resolved widget in a GenericFormField, which registers the node as a PrimeVue Forms FormField keyed by nodeAlias, ties its dirty/touched state and validation errors into an ancestor <Form>, and renders those errors:

<script setup lang="ts">
import { ref } from 'vue';

import GenericWidget from '@/arches_vue_components/generics/GenericWidget/GenericWidget.vue';

import type { WidgetMode } from '@/arches_vue_components/widgets';
import type { AliasedNodeData } from '@/arches_vue_components/generics';

const MODE: WidgetMode = 'edit';
const nodeData = ref<AliasedNodeData | null>(null);
</script>

<template>
    <GenericWidget
        node-alias="my_text_node"
        graph-slug="my_graph"
        :mode="MODE"
        :aliased-node-data="nodeData"
        @update:aliased-node-data="nodeData = $event"
    />
</template>

aliasedNodeData/value are both optional. Without either, GenericWidget falls back to the node's configured default (cardXNodeXWidgetData.config.defaultValue, part of the widget config it already fetches), so it renders from just graphSlug/nodeAlias/mode:

<template>
    <GenericWidget
        node-alias="my_text_node"
        graph-slug="my_graph"
        mode="edit"
    />
</template>

Importing a widget directly skips the runtime resolution and the FormField integration described above. The widget emits update:aliasedNodeData/update:value on its own (some widgets also emit update:isDirty); the caller wires that into any surrounding form:

<script setup lang="ts">
import { ref } from 'vue';

import { TextWidget } from '@/arches_vue_components/widgets';

import type { WidgetMode } from '@/arches_vue_components/widgets';
import type { StringAliasedNodeData } from '@/arches_vue_components/datatypes';

const MODE: WidgetMode = 'edit';
const nodeData = ref<StringAliasedNodeData | null>(null);
</script>

<template>
    <TextWidget
        :mode="MODE"
        :aliased-node-data="nodeData"
        @update:aliased-node-data="nodeData = $event"
    />
</template>

Cards

GenericCard renders a whole nodegroup. It fetches the tile (fetchTileData) and every node's widget config, then renders GenericCardEditor or GenericCardViewer depending on mode. The editor wraps a PrimeVue Form and renders one GenericWidget per node, and saves the collected tile with upsertTile — from its own save button, or by calling .save() on it directly (exposed via defineExpose).

<script setup lang="ts">
import { ref } from 'vue';

import GenericCard from '@/arches_vue_components/generics/GenericCard/GenericCard.vue';

import type { AliasedTileData } from '@/arches_vue_components/generics';

const tileData = ref<AliasedTileData>();
</script>

<template>
    <GenericCard
        graph-slug="my_graph"
        nodegroup-alias="my_nodegroup"
        :resource-instance-id="resourceInstanceId"
        :tile-id="tileId"
        :tile-data="tileData"
        mode="edit"
        @update:tile-data="tileData = $event"
        @save="tileData = $event"
        @reset="() => {}"
    />
</template>

Reference

@/arches_vue_components/widgets

Name Type Description
WidgetMode 'edit' | 'view' | 'configure' The three states a widget can render in
BaseWidgetProps { mode: WidgetMode; nodeAlias?: string; graphSlug?: string } Props every widget accepts

Every widget's aliasedNodeData/cardXNodeXWidgetData prop is typed to one specific datatype. Where no widget-specific cardXNodeXWidgetData type is listed, the widget uses the base CardXNodeXWidgetData (no extra config fields). Both columns import from @/arches_vue_components/datatypes:

Widget aliasedNodeData type cardXNodeXWidgetData type
TextWidget, RichTextWidget StringAliasedNodeData StringCardXNodeXWidgetData
NonLocalizedTextWidget NonLocalizedTextAliasedNodeData CardXNodeXWidgetData
NumberWidget NumberAliasedNodeData NumberCardXNodeXWidgetData
DatePickerWidget DateAliasedNodeData DateCardXNodeXWidgetData
EDTFWidget EDTFAliasedNodeData CardXNodeXWidgetData
ConceptSelectWidget ConceptAliasedNodeData CardXNodeXWidgetData
ConceptRadioWidget ConceptAliasedNodeData ConceptCardXNodeXWidgetData
ConceptMultiselectWidget ConceptListAliasedNodeData CardXNodeXWidgetData
ConceptCheckboxWidget ConceptListAliasedNodeData ConceptCardXNodeXWidgetData
DomainSelectWidget, DomainRadioWidget DomainAliasedNodeData DomainCardXNodeXWidgetData
DomainCheckboxWidget, DomainMultiselectWidget DomainListAliasedNodeData DomainCardXNodeXWidgetData
LanguageSelectWidget LanguageAliasedNodeData CardXNodeXWidgetData
RadioBooleanWidget, SwitchWidget BooleanAliasedNodeData BooleanCardXNodeXWidgetData
ResourceInstanceSelectWidget ResourceInstanceAliasedNodeData ResourceInstanceCardXNodeXWidgetData
ResourceInstanceMultiselectWidget ResourceInstanceListAliasedNodeData ResourceInstanceListCardXNodeXWidgetData
FileListWidget FileListAliasedNodeData FileListCardXNodeXWidgetData
NodeValueSelectWidget NodeValueAliasedNodeData CardXNodeXWidgetData
URLWidget URLAliasedNodeData CardXNodeXWidgetData
MapWidget GeoJSONFeatureCollectionAliasedNodeData MapCardXNodeXWidgetData

@/arches_vue_components/components

The arches-agnostic map underneath MapWidget. See Customizing the Map for a full example.

Export Description
MapComponent The map itself: FeatureCollection in/out, no cardXNodeXWidgetData/mode
MapComponentProps Props interface, table below
MapContext Reactive state/actions object every interaction tool reads/writes through
MapInteractionTool Shape of one entry in interactionTools
InteractionsDrawer, BasemapPanel, OverlayPanel, DrawPanel, DrawControls, BufferControls, DrawnFeaturesList, ShapefileDropZone, FeaturePopup The built-in interaction tools and default feature popup, exported for reuse/composition
useMapContext Composable MapComponent itself calls; not typically used directly
useResolvedMapContext(context, componentName) Resolves a MapContext from a prop, falling back to inject(mapContextKey); what every built-in tool uses so it works both in-tree and passed a context explicitly
resolveDefaultOverlayLayers(candidateOverlayLayers) The default resolveOverlayLayers resolver MapComponent falls back to; compose on top of it (e.g. to also include searchonly layers) instead of reimplementing its filter/sort logic
mapContextKey The provide/inject key MapComponent provides MapContext under
useDefaultMapInteractionTools() Returns the default Draw/Basemap/Overlays tool set, for composing with your own

MapComponentProps:

Name Type Description
value FeatureCollection | null Drawn features
zoom, pitch, bearing, centerX, centerY, minZoom, maxZoom number Initial camera position/constraints
basemap string Initially active basemap
allowedGeometryTypes string[] Constrains which geometry types can be drawn (filters the default Draw tool's options)
interactionTools MapInteractionTool[] Sidebar tools shown in the drawer; [] renders no drawer at all; omitted defaults to Draw/Basemap/Overlays
featurePopupComponent Component Replaces the default click-to-view-resource popup
maxFeatures number Rejects drawing/adding features past this count, with an error toast
resolveOverlayLayers (candidateOverlayLayers: MapLayer[]) => MapLayer[] Resolves the final overlay layer set from the raw fetched candidates (map_layers + resource_map_layers); omitted defaults to resolveDefaultOverlayLayers, which excludes searchonly layers. Pass a resolver that also includes layer.searchonly layers to show them (see resolveDefaultOverlayLayers above)

MapComponent emits:

Event Payload Fires
update:value FeatureCollection Drawn features changed
update:isLoading boolean
update:overlays Overlay layers changed
ready Once, when the underlying maplibregl.Map is actually usable. Not the same thing as MapWidget's initialized event, which fires immediately on mount and just means "has an initial value"

MapContext:

Name Type Description
map ShallowRef<maplibregl.Map | null> The live MapLibre instance
isLoading, basemaps, overlays, drawnFeatures, selectedDrawnFeature, allowedGeometryTypes Reactive state. Same values the built-in tools already use
setDrawMode(mode), selectDrawnFeature(feature), deleteSelectedDrawnFeature(), deleteAllDrawnFeatures(), setBufferForSelectedFeature(distance, units), addFeatures(features) Actions. Every built-in tool calls these instead of touching MapLibre or mapbox-gl-draw directly

@/arches_vue_components/generics

GenericWidget/GenericCard resolve their concrete component at runtime instead of being imported directly — that is the "generic" here, not a TypeScript <T>.

Export Description
GenericCard Card editor/viewer for a nodegroup
GenericWidget Single widget resolved from widget config
GenericCardProps, GenericWidgetProps Props interfaces
AliasedNodeData, AliasedTileData Node/tile value types

GenericWidgetProps:

Name Type Description
graphSlug string Graph the node belongs to
nodeAlias string Node to render
mode WidgetMode 'edit', 'view', or 'configure'
aliasedNodeData AliasedNodeData | null Current value; takes priority over value
value unknown Raw value fallback, used only if aliasedNodeData is omitted
cardXNodeXWidgetData CardXNodeXWidgetData Pre-fetched widget config; skips GenericWidget's own fetch
cardXNodeXWidgetDataOverrides Partial<CardXNodeXWidgetData> Merged into the fetched config after fetching
isDirty boolean Marks the field dirty on the surrounding <Form>
shouldShowLabel boolean Show the node's label (default true)

GenericCardProps:

Name Type Description
graphSlug string Graph the nodegroup belongs to
nodegroupAlias string Nodegroup to render
mode WidgetMode 'edit', 'view', or 'configure'
resourceInstanceId string | null Resource this tile belongs to, for a new tile
selectedNodeAlias string | null Node to focus within the card
shouldShowFormButtons boolean Show the built-in save/reset buttons (default true)
tileData AliasedTileData Pre-fetched tile; skips GenericCard's own fetch
tileId string | null Tile to fetch when tileData is not provided

@/arches_vue_components/datatypes

*AliasedNodeData types are listed in the widget table above. Supporting types:

Export Description
LanguageValue Per-language string value ({ value: string; direction: 'ltr' | 'rtl' })
URLNodeValue URL node value ({ url: string; url_label: string })
FileReference File attachment reference
ResourceInstanceReference Resource instance link reference
DomainOption Domain value option

@/arches_vue_components/application

Name Type
createVueApplication (options: CreateVueApplicationOptions) => Promise<App>
generateArchesURL (urlName: string, urlParameters?: Record<string, string | number>, queryParameters?: Record<string, string | number>, languageCode?: string) => string
CreateVueApplicationOptions { component: Component; themeConfiguration?: ArchesThemeConfiguration; initialProps?: Record<string, unknown> }

@/arches_vue_components/themes

Name Type Description
DEFAULT_THEME ArchesThemeConfiguration Theme createVueApplication uses when themeConfiguration is not passed
ArchesThemeConfiguration — PrimeVue theme configuration shape

Creating a Custom Widget

A widget is an editor/viewer pair plus a dispatcher that picks between them by mode. The example below, RatingWidget, is a new widget for the existing number datatype.

  1. Create widgets/RatingWidget/ with a types.ts extending BaseWidgetProps:
import type { BaseWidgetProps } from "@/arches_vue_components/widgets/types.ts";
import type {
    NumberAliasedNodeData,
    NumberCardXNodeXWidgetData,
} from "@/arches_vue_components/datatypes/number/types.ts";

export interface RatingWidgetProps extends BaseWidgetProps {
    cardXNodeXWidgetData?: NumberCardXNodeXWidgetData;
    aliasedNodeData?: NumberAliasedNodeData | null;
    value?: number | null;
}
  1. Add RatingWidget.vue, the dispatcher — switches on mode, re-emits update:aliasedNodeData, update:value, initialized:
<script setup lang="ts">
import { computed } from "vue";

import RatingWidgetEditor from "@/arches_vue_components/widgets/RatingWidget/components/RatingWidgetEditor.vue";
import RatingWidgetViewer from "@/arches_vue_components/widgets/RatingWidget/components/RatingWidgetViewer.vue";

import { EDIT, VIEW } from "@/arches_vue_components/widgets/constants.ts";
import { buildNumberAliasedNodeData } from "@/arches_vue_components/datatypes/number/utils.ts";

import type { NumberAliasedNodeData } from "@/arches_vue_components/datatypes/number/types.ts";
import type { RatingWidgetProps } from "@/arches_vue_components/widgets/RatingWidget/types.ts";

const { aliasedNodeData, value } = defineProps<RatingWidgetProps>();

const emit = defineEmits<{
    "update:value": [updatedValue: number | null];
    "update:aliasedNodeData": [updatedValue: NumberAliasedNodeData];
    initialized: [updatedValue: NumberAliasedNodeData];
}>();

const resolvedAliasedNodeData = computed(
    () => aliasedNodeData ?? buildNumberAliasedNodeData(value ?? null),
);

function onUpdateAliasedNodeData(updated: NumberAliasedNodeData) {
    emit("update:aliasedNodeData", updated);
    emit("update:value", updated.node_value);
}
</script>

<template>
    <RatingWidgetEditor
        v-if="mode === EDIT"
        :card-x-node-x-widget-data="cardXNodeXWidgetData"
        :aliased-node-data="resolvedAliasedNodeData"
        @update:aliased-node-data="onUpdateAliasedNodeData"
        @initialized="emit('initialized', $event)"
    />
    <RatingWidgetViewer
        v-else-if="mode === VIEW"
        :aliased-node-data="resolvedAliasedNodeData"
        @initialized="emit('initialized', $event)"
    />
</template>
  1. Add the editor and viewer. Both emit initialized on mount:
<!-- components/RatingWidgetEditor.vue -->
<script setup lang="ts">
import { onMounted } from "vue";
import Rating from "primevue/rating";

import { buildNumberAliasedNodeData } from "@/arches_vue_components/datatypes/number/utils.ts";

import type { NumberAliasedNodeData } from "@/arches_vue_components/datatypes/number/types.ts";

const { aliasedNodeData } = defineProps<{
    aliasedNodeData: NumberAliasedNodeData | null;
}>();

const emit = defineEmits<{
    (event: "update:aliasedNodeData", updatedValue: NumberAliasedNodeData): void;
    (event: "initialized", updatedValue: NumberAliasedNodeData): void;
}>();

onMounted(() => {
    emit("initialized", aliasedNodeData ?? buildNumberAliasedNodeData(null));
});

function onUpdateModelValue(updatedValue: number | null) {
    emit("update:aliasedNodeData", buildNumberAliasedNodeData(updatedValue));
}
</script>

<template>
    <Rating
        :model-value="aliasedNodeData?.node_value ?? null"
        @update:model-value="onUpdateModelValue($event)"
    />
</template>
<!-- components/RatingWidgetViewer.vue -->
<script setup lang="ts">
import { onMounted } from "vue";

import type { NumberAliasedNodeData } from "@/arches_vue_components/datatypes/number/types.ts";

const { aliasedNodeData } = defineProps<{ aliasedNodeData: NumberAliasedNodeData }>();

const emit = defineEmits<{ initialized: [updatedValue: NumberAliasedNodeData] }>();

onMounted(() => emit("initialized", aliasedNodeData));
</script>

<template>
    <div>{{ aliasedNodeData?.display_value }}</div>
</template>
  1. Reuse an existing datatype module (@/arches_vue_components/datatypes — string, number, boolean, concept, domain, etc.) for the value shape, as above, or add a new one following the same types.ts + build<Datatype>AliasedNodeData utils.ts pattern.

  2. If contributing to Arches Vue Components, export it from widgets/index.ts:

export { default as RatingWidget } from "@/arches_vue_components/widgets/RatingWidget/RatingWidget.vue";
export type { RatingWidgetProps } from "@/arches_vue_components/widgets/RatingWidget/types.ts";
  1. Register it — see Extending Arches Vue Components.

Customizing the Map

MapWidget is a thin adapter over MapComponent. It translates a node's cardXNodeXWidgetData.config into MapComponent's props and back. If you're not editing a resource at all, say you're building a search filter, embed MapComponent directly and skip the adapter.

The example below replaces the default interactions drawer with a custom floating panel, replaces the feature-click popup, and constrains drawing to a single point:

<script setup lang="ts">
import { ref, useTemplateRef } from "vue";

import { MapComponent } from "@/arches_vue_components/components";

import MyDrawTools from "./MyDrawTools.vue";
import MySearchResultPopup from "./MySearchResultPopup.vue";

import type { FeatureCollection } from "geojson";
import type { MapInteractionTool } from "@/arches_vue_components/components";

const NO_INTERACTION_TOOLS: MapInteractionTool[] = [];
const MAX_DRAWN_FEATURES = 1;
const ALLOWED_GEOMETRY_TYPES = ["point"];

const value = ref<FeatureCollection | null>(null);
const mapRef = useTemplateRef<InstanceType<typeof MapComponent>>("map");
</script>

<template>
    <div class="my-map-shell">
        <MapComponent
            ref="map"
            :value="value"
            :interaction-tools="NO_INTERACTION_TOOLS"
            :allowed-geometry-types="ALLOWED_GEOMETRY_TYPES"
            :max-features="MAX_DRAWN_FEATURES"
            :feature-popup-component="MySearchResultPopup"
            @update:value="value = $event"
        />
        <!-- Rendered outside MapComponent's own tree entirely -->
        <MyDrawTools :context="mapRef?.context ?? null" />
    </div>
</template>

<style scoped>
.my-map-shell {
    position: relative; /* MyDrawTools floats over the map via its own CSS */
    display: flex;
    flex: 1;
}
</style>

interactionTools: [] suppresses the built-in drawer entirely, rather than swapping in a different set of tools for it. context comes from MapComponent's defineExpose, and it's the same reactive MapContext every built-in tool already reads and writes: drawnFeatures, selectedDrawnFeature, actions like setDrawMode/addFeatures/deleteAllDrawnFeatures. So MyDrawTools is driving the exact same map state. It can look like a column of buttons, a floating toolbar, whatever you want. The map doesn't care.

MyDrawTools.vue and MySearchResultPopup.vue are just regular components; the pattern that matters is how they reach MapContext:

<!-- MyDrawTools.vue -->
<script setup lang="ts">
import type { MapContext } from "@/arches_vue_components/components";

const { context } = defineProps<{ context: MapContext | null }>();
</script>

<template>
    <div class="floating-draw-tools">
        <button @click="context?.setDrawMode('point')">Drop a point</button>
        <button @click="context?.deleteAllDrawnFeatures()">Clear</button>
    </div>
</template>

Extending Arches Vue Components

Arches Vue Components uses the WidgetMapping model to map widgets to their Vue components. To check for missing mappings:

python manage.py widget check_mappings

To add a mapping:

python manage.py widget add_mapping -wn language-select -cp arches_vue_components/widgets/LanguageSelectWidget/LanguageSelectWidget.vue

Migrating a WidgetMapping Migration from arches-component-lab

arches_vue_components is the renamed successor to arches_component_lab; the two are separate Django apps with independent migration histories, so a project migration that targets arches_component_lab.WidgetMapping (for example, a data migration registering a mapping for a custom widget) breaks once arches_component_lab is dropped from INSTALLED_APPS. Don't edit that migration in place to point at arches_vue_components — its slot in your migration history is already applied on real projects and can't be removed or repurposed. Instead:

  1. Edit the existing migration to be a no-op, and comment where the replacement lives:
from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ("my_project", "0004_some_prior_migration"),
    ]

    # RatingWidget's mapping is registered by 0011_add_rating_widget_mapping
    # instead, against arches_vue_components.WidgetMapping. This migration's
    # slot can't be removed since it's already applied on real projects, so
    # it's kept as a no-op.
    operations = [
        migrations.RunPython(migrations.RunPython.noop, migrations.RunPython.noop),
    ]
  1. Create a new migration that depends on arches_vue_components and re-creates the mapping against it:
import uuid

from django.db import migrations


def create_rating_widget_mapping(apps, schema_editor):
    WidgetMapping = apps.get_model("arches_vue_components", "WidgetMapping")

    # first delete old mapping if it exists
    WidgetMapping.objects.filter(widget_id="<widget-uuid>").delete()

    WidgetMapping.objects.create(
        id=uuid.uuid4(),
        widget_id="<widget-uuid>",
        component="my_project/widgets/RatingWidget/RatingWidget.vue",
    )


def revert_rating_widget_mapping(apps, schema_editor):
    WidgetMapping = apps.get_model("arches_vue_components", "WidgetMapping")
    WidgetMapping.objects.filter(widget_id="<widget-uuid>").delete()


class Migration(migrations.Migration):
    dependencies = [
        ("my_project", "0010_some_later_migration"),
        ("arches_vue_components", "0002_populate_widget_mappings"),
    ]

    operations = [
        migrations.RunPython(
            create_rating_widget_mapping,
            revert_rating_widget_mapping,
        ),
    ]

Metadata

Release files for arches-vue-components 2.1.0a2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for arches-vue-components 2.1.0a2
File Size Uploaded
arches_vue_components-2.1.0a2.tar.gz 322.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for arches-vue-components 2.1.0a2
File Interpreter ABI Platform
arches_vue_components-2.1.0a2-py3-none-any.whl Python 3 none any Details

Total release size: 554.3 kB

Release files / arches_vue_components-2.1.0a2.tar.gz

Download URL arches_vue_components-2.1.0a2.tar.gz
Size 322.0 kB
Tags Source
SHA-256 checksum
How to use checksums
45a4192c8bbb55e5a80e1849c25e529f2af8aa632dfa9ad0c95e09db2ab9513a
BLAKE2b-256 checksum
How to use checksums
ebf6ec226dbf0abe3b764f948fdb42d295b40d12a8c5bf98c47ef226cd73dd4d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / arches_vue_components-2.1.0a2-py3-none-any.whl

Download URL arches_vue_components-2.1.0a2-py3-none-any.whl
Size 232.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c1d2bd2297348976e2effddbc23417b70976f7f275329ed153adcc5a88564309
BLAKE2b-256 checksum
How to use checksums
e70ecc5e379071537f13866a046c7628e572d9da97b1dd7816aa3026510854bf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page