Skip to content
Thom van Kalkeren edited this page Aug 21, 2019 · 32 revisions

Demo TODO application

Link provides the functionality to build a fully-featured linked-data browser, it's also possible to just mix-and-match the functionality you need for your specific application.

Most tasks (e.g. data fetching) can be done in a number of ways, each with their own considerations. Link generally provides both imperative and declarative ways to attain the desired result. The imperative tools usually provide quick and easy results with a lot of flexibility, while the declarative tools provide a stable base for scaling and adapting to a variety of data sources.

Most applications will use both to achieve the best balance between performance, readability, scalability and maintainability.

Note: Link-redux has been through a lot of changes since its inception, the name implies the use of the popular Redux framework, while this was true in the past, link-redux has dropped its dependency and provides it's own middleware which is better adopted for processing linked data.

Preparations

Use createStore to initialize a store to hold all data, and pass it to the RenderStoreProvider to provide your child components access;

const lrs = createStore()

ReactDOM.render(
  (
    <RenderStoreProvider value={lrs}>
      // The rest of your app
    </RenderStoreProvider>
  )
  document.getElementById('app')
)

IRI's

Linked data uses URL's for resources and properties. It can be tedious to write and instantiate those URL's, so the store provides a helper with some namespaces for that.

const lrs = createStore()
export const NS = lrs.namespaces

Use NS.app('my/path') for all app-related things and when you're not sure. It is always relative to the domain your app is viewed on (github.com in the case of this documentation).

// The `app` namespace is relative, it uses window.location.origin (the current location)
NS.app('fletcher91/link-lib') // https://github.com/fletcher91/link-lib

A lot of popular namespaces are preconfigured (see the list)

NS.foaf('name')
NS.owl('sameAs')

The following popular ontologies are available as plain objects as well;

Which can be called more easily;

NS.schema.name
NS.rdf.type

Accessing the store

Retrieve the store in your component;

Hooks

const MyComponent = () => {
  const lrs = useLRS()
}

HOC

const MyComponent = (props) => {
  const lrs = props.lrs
}

const EnhancedComponent = withLRS(MyComponent)

Retrieving server data

Declarative

Mounting a LinkedResourceContainer will retrieve the resource passed as subject when it's not in the store yet.

<LinkedResourceContainer subject={NS.app('person/5')} />

Store

The simplest method to fetch data from a server is via getEntity, which returns a promise which will resolve once the resource has been fetched. The promise has no value, rather your component should re-render when it finishes loading.

await lrs.getEntity(NS.app('person/5'))

Use queueEntity to let the store know it should be fetched in the near future. It is considerably more performant than getEntity since it doesn't create a promise and allows the store to batch data requests more efficiently.

lrs.queueEnitity(NS.app('person/5'))

Querying data

Hooks

Not available at the time of writing

HOC

const MyComponent = (props) => {
  return (
    <div>
      <h1>{props.name.value}</h1>
      <p>{props.description.value}</p>
    </div>
  )
}

const EnhancedComponent = link([NS.schema.name, NS.schema.description])(MyComponent)

Store

Use tryEntity to retrieve actual data from the store. An empty array is returned when no data was found.

const data = lrs.tryEntity(NS.app('person/5'))

Rendering resources

Declarative

When mounting a LinkedResourceContainer, the resource will be fetched and if an accompanying view can be found that will be rendered.

<LinkedResourceContainer subject={resource} />

You can pass a view implementation as a child when you only need to fetch the resource.

<LinkedResourceContainer subject={resource}>
  <Property label={NS.schema.name} />
</LinkedResourceContainer>

Rendering properties

Declarative

Mounting the Property component will render the best fitting view if present.

<Property label={NS.schema.name} />

If the property is another resource and no view can be found, a LinkedResourceContainer will be mounted automatically rendering the containing resource. If it's a value (e.g. string, number, dollars, centimetres) and no view can be found the plain value will be displayed.

<Property label={NS.schema.name} /> // Will render e.g. 'Bob'
<Property label={NS.schema.author} /> // Will render the author mentioned in the property

Rendering the associated resource of a property is a good default in almost any case, but rendering a value as-is might not. See the rendering data types section for more info on how to customize the rendering behaviour.

Even if a resource has a property multiple times, it will render only one view by default. If you want to render the view multiple times, pass a custom limit.

// Will render the alternate name property at most 10 times
<Property label={NS.schema.alternateName} limit={10} />

Using limit will render the entire view multiple times. If you need more than one value for a single view, you can request that in your view.

const CommentsRenderer = ({ linkedProp }) => (
  <React.Fragment>
    {linkedProp.map((name) => <p>{name.value}</p>)}
  </React.Fragment>
);
CommentsRenderer.property = NS.schema.Person
CommentsRenderer.property = NS.schema.alternateName
CommentsRenderer.renderOpts = {
  limit: 10,
}

Rendering data types

Most programming languages have certain built-in datatypes (e.g. string, bool, char, byte, longlong, etc), RDF has some built-in types as well (many from xsd such as string, boolean, or dateTime and from RDFS langstring), but it isn't limited to those types. Datatypes are just RDF resources and make sense to use for most quantities (e.g. expressing money like dollars, distance like centimetre, or volume like gallons) but also for more complex types (e.g. markdown formatted strings, DNA codon).

When the Property component terminates at a value (no view could be found), then it defaults to rendering the plain value. This works fine for (lang)strings, but when a datatype needs more formatting to be valuable to the user, a renderer for a class of data types can be registered.

const DollarRenderer ({ linkedProp }) => (
  <span>
    ${Number(linkedProp.value).toFixed(2)}
  </span>
) // E.g. $2.50
DollarRenderer.type = NS.rdfs.Literal
DollarRenderer.property = new NamedNode("http://dbpedia.org/datatype/usDollar")

Actions, middleware and state changes

Inheritance and reasoning

Clone this wiki locally