Skip to content

Inline Creation in Views

Martynas Jusevičius edited this page Jan 15, 2026 · 4 revisions

Problem Statement

Currently, when viewing a resource with related items displayed via ldh:template views, users cannot create new related items inline. They must navigate away to create items separately and then link them back.

Example: Viewing a SKOS Concept with a "Narrower Concepts" view requires leaving the page to create narrower concepts and manually linking them.

This breaks context and creates poor UX for enterprise CRUD applications.

Solution: Inline Creation Metadata

Enable [Add New] buttons within views that open modal forms for creating related items inline, with automatic linking.


Vocabulary Additions

ldh:property

ldh:property a owl:ObjectProperty ;
    rdfs:domain ldh:View ;
    rdfs:range rdf:Property ;
    rdfs:label "Linking property" ;
    rdfs:comment "The RDF property used to link the parent resource to items created from this view" .

Purpose: Specifies which property links the parent resource to newly created items.

Example:

:NarrowerConcepts a ldh:View ;
    ldh:property skos:narrower .

Creates triple: <parent> skos:narrower <new-item> when item is created.

ldh:container

ldh:container a owl:ObjectProperty ;
    rdfs:domain ldh:View ;
    rdfs:range rdfs:Resource ;
    rdfs:label "Container for creating items" ;
    rdfs:comment "The container URI where new items from this view will be created via HTTP PUT" .

Purpose: Specifies where to create new documents (PUT target base URL).

Example:

:NarrowerConcepts a ldh:View ;
    ldh:container </concepts/> .

Creates new items at: PUT /concepts/{user-provided-slug}

ldh:targetClass (Optional)

ldh:targetClass a owl:ObjectProperty ;
    rdfs:domain ldh:View ;
    rdfs:range rdfs:Class ;
    rdfs:label "Target class for creation" ;
    rdfs:comment "Override for the RDF class of items created from this view. Only needed when range inference fails or needs override." .

Purpose: Explicit class specification when rdfs:range lookup fails or needs override.

Example:

:ComplexView a ldh:View ;
    ldh:property ex:complexProperty ;
    ldh:targetClass ex:ConcreteClass .  # Override abstract range

ldh:showWhenEmpty

ldh:showWhenEmpty a owl:DatatypeProperty ;
    rdfs:domain ldh:View ;
    rdfs:range xsd:boolean ;
    rdfs:label "Show when empty" ;
    rdfs:comment "If false, the view is not rendered when the query returns no results. Default is true (show even when empty)." .

Purpose: Controls whether a view is displayed when its query returns no results.

Default: true (show even when empty)

  • Shows empty state message
  • [Add New] button remains visible
  • User knows the section exists

When set to false: View is hidden if query returns no results

  • Cleaner UI for conditional sections
  • Useful for VIP features, optional content

Example:

# Only show VIP benefits if customer has them
:VIPBenefits a ldh:View ;
    spin:query :SelectVIPBenefits ;
    ldh:showWhenEmpty false .

# Always show orders section (even if empty)
:Orders a ldh:View ;
    spin:query :SelectOrders ;
    ldh:showWhenEmpty true ;  # Default behavior
    ldh:property sales:customer ;
    ldh:container </orders/> .

Implementation Approach

Pattern Inference (Primary)

Goal: Minimize metadata required by inferring from SPARQL query and ontology.

Algorithm:

  1. Parse SPARQL query for pattern: $about <property> ?variable

    SELECT ?narrower WHERE {
        $about skos:narrower ?narrower .
    }
  2. Extract property: skos:narrower

  3. Lookup range in ontology:

    skos:narrower rdfs:range skos:Concept .
  4. Result:

    • Link via: skos:narrower
    • Create: skos:Concept instances

Works for ~80% of cases with simple property patterns.

Explicit Metadata (Fallback)

When inference fails (property paths, UNIONs, multi-hop relations, missing range), use explicit metadata:

:ComplexView a ldh:View ;
    spin:query :ComplexQuery ;
    ldh:property ex:relation ;
    ldh:targetClass ex:ItemClass ;
    ldh:container </items/> .

Complete Example

Package Definition

@prefix : <https://example.com/packages/skos#> .

# Attach view to class
skos:Concept ldh:template :NarrowerConcepts .

# View with inline creation
:NarrowerConcepts a ldh:View ;
    dct:title "Narrower concepts" ;
    spin:query :SelectNarrowerConcepts ;
    ac:mode ac:TableMode ;

    # Inline creation metadata
    ldh:property skos:narrower ;
    ldh:container </concepts/> .

:SelectNarrowerConcepts a sp:Select ;
    sp:text """
        SELECT DISTINCT ?narrower WHERE {
            $about skos:narrower ?narrower .
        }
        ORDER BY ?prefLabel
    """ .

User Flow

  1. View concept at /concepts/animals
  2. See "Narrower Concepts" view with [Add New] button
  3. Click [Add New] → Modal form opens
  4. Form shows:
    • Slug field (required)
    • Fields from skos:Concept spin:constructor
    • Parent link pre-filled (hidden): skos:narrower/concepts/animals#this
  5. User enters:
    • Slug: "mammals"
    • Label: "Mammals"
  6. Submit → PUT to /concepts/mammals:
    <> a dh:Item ;
        foaf:primaryTopic <#this> .
    
    <#this> a skos:Concept ;
        skos:prefLabel "Mammals"@en .
    
    # Linking triple from parent
    </concepts/animals#this> skos:narrower <#this> .
  7. Modal closes, view refreshes, shows new narrower concept

Benefits

User Experience

  • ✅ Stay in context (no navigation away)
  • ✅ Automatic linking (no manual triple creation)
  • ✅ Immediate feedback (view refreshes)

Developer Experience

  • ✅ Minimal metadata (property + container)
  • ✅ Inference handles common cases
  • ✅ Reuses existing constructors

Architecture

  • ✅ Consistent with LinkedDataHub patterns
  • ✅ Standard HTTP PUT for creation
  • ✅ Works with existing security model

Implementation Checklist

Vocabulary

  • Add ldh:property to ldh.ttl ontology
  • Add ldh:container to ldh.ttl ontology
  • Add ldh:targetClass to ldh.ttl ontology (optional)

XSLT Templates

  • Pattern inference logic (parse SPARQL, extract property)
  • Range lookup (query ontology for rdfs:range)
  • [Add New] button rendering (when metadata present)
  • Modal form generation (from spin:constructor)
  • Pre-fill linking property in form

Client-Side (Saxon-JS)

  • Modal open/close logic
  • Form submission handler
  • PUT request to {container}{slug}
  • Include linking triple in payload
  • View refresh after creation

Java Backend

  • Handle PUT with linking triples
  • Validate parent resource exists
  • Validate property domain/range
  • Return created resource URI

Interactive Components with Saxon-JS

LinkedDataHub uses Saxon-JS for client-side XSLT execution, which includes ixsl: extensions for browser interactivity. This enables building fully interactive components entirely in XSLT without separate JavaScript files.

What's Possible

Interactive UI components can be built using XSLT + ixsl: event handlers:

Tab Containers

  • Click tabs to switch views
  • Pure XSLT with ixsl:onclick handlers
  • Manages CSS classes to show/hide panes

Sortable Tables

  • Click column headers to sort
  • Client-side sorting with xsl:perform-sort
  • No server round-trip needed

Filterable Lists

  • Type in filter inputs
  • ixsl:onkeyup handlers filter rows
  • Show/hide based on text match

Bulk Operations

  • Checkboxes for multi-select
  • Bulk delete/export actions
  • Selection state managed in XSLT

Modal Dialogs

  • Dynamic modal forms
  • Fetch and render constructor forms
  • Submit via ixsl: fetch API

Status Workflows

  • Status badges with colors
  • Transition buttons
  • PATCH requests to update state

Drag-and-Drop

  • Kanban boards
  • Reorderable lists
  • ixsl:ondragstart, ixsl:ondrop handlers

Advantages

vs. React/Vue/JavaScript:

  • ✅ No build step (beyond SEF generation)
  • ✅ Unified language (XSLT for both server + client)
  • ✅ RDF-native (templates match RDF structures naturally)
  • ✅ Server/client code reuse (same templates work both sides)
  • ✅ Type-safe (XSLT 3.0 type system)
  • LLMs can generate XSLT (declarative, structured)

Component Library Pattern:

/static/xsl/components/
├── tabs.xsl         # Tab containers
├── table.xsl        # Interactive tables with sort/filter
├── modal.xsl        # Modal dialogs
├── form.xsl         # Dynamic forms from constructors
├── badges.xsl       # Status indicators
├── bulk-ops.xsl     # Bulk selection and actions
└── dashboard.xsl    # Widget layouts

Each component is:

  • Declarative (XSLT templates)
  • Interactive (ixsl: event handlers)
  • Reusable (import into packages)
  • Themeable (CSS classes)

Enterprise UI Feasibility

With Saxon-JS + proposed view features, you can build enterprise CRUD applications (CRM, ERP, order management, issue tracking) using:

Schema.org vocabulary for domain entities:

  • schema:Organization, schema:Person (contacts/accounts)
  • schema:Order, schema:OrderItem (orders)
  • schema:Invoice (billing)
  • schema:Event (scheduling)

XSLT components for interactive UIs:

  • Tabbed organization detail pages
  • Sortable/filterable order tables
  • Bulk operations on records
  • Inline contact creation
  • Status workflow transitions

Result: Functional enterprise applications with modern UX, fully declarative architecture, and LLM-friendly codebase.

The UI may not have the visual polish of modern SaaS (which uses component libraries like Material/Ant Design), but provides all essential functionality for internal business applications, MVPs, and enterprise tools.


Future Enhancements

Inverse Properties

Support inverse linking direction:

ldh:inverseProperty skos:broader .

Creates: <new-item> skos:broader <parent> instead of <parent> skos:narrower <new-item>

Bulk Creation

Allow creating multiple items at once from view.

Inline Editing

Edit items directly in view (not just creation).

View Composition

Multiple views with tabs/switcher UI (requires additional metadata like ldh:label, ldh:priority, ldh:default).

Component Library

Develop reusable XSLT component library:

  • Tab containers
  • Interactive tables
  • Modal dialogs
  • Bulk operation patterns
  • Status badges and workflows
  • Dashboard layouts

Summary

Agreed Vocabulary:

  • ldh:property - Linking property (required for inline creation)
  • ldh:container - Container URL for PUT (required for inline creation)
  • ldh:targetClass - Target class override (optional)
  • ldh:showWhenEmpty - Control view visibility when no results (optional, default: true)

Approach:

  • Pattern inference for simple cases
  • Explicit metadata for complex cases
  • Modal form with slug for PUT creation
  • Conditional view rendering based on results

This enables inline creation in views with minimal metadata while maintaining LinkedDataHub's declarative architecture.

Clone this wiki locally