vie.js | |
---|---|
VIE enables you to make RDFa -annotated content on your web pages editable. For example, if your page contains the following mark-up:
Then with VIE you can get a proper Backbone Model object for that. First scan your page for RDFa entities:
And then just access the entity by subject:
Properties of the entity may also be modified, and these changes will also happen on the page itself:
You can also access the entities via the | (function() { |
Initial setupThe VIE library is fully contained inside a | var VIE;
if (typeof exports !== 'undefined') {
VIE = exports;
} else {
VIE = this.VIE = {};
} |
Handle dependenciesVIE tries to load its dependencies automatically. Please note that this autoloading functionality only works on the server. On browser Backbone needs to be included manually. | |
Require underscore.js using CommonJS require if it isn't yet loaded. On node.js underscore.js can be installed via: | var _ = this._;
if (!_ && (typeof require !== 'undefined')) { _ = require('underscore')._; }
if (!_) {
throw 'VIE requires underscore.js to be available';
} |
Require Backbone.js using CommonJS require if it isn't yet loaded. On node.js Backbone.js can be installed via: | var Backbone = this.Backbone;
if (!Backbone && (typeof require !== 'undefined')) { Backbone = require('backbone'); }
if (!Backbone) {
throw 'VIE requires Backbone.js to be available';
} |
Require jQuery using CommonJS require if it isn't yet loaded. On node.js jQuery can be installed via: | var jQuery = this.jQuery;
if (!jQuery && (typeof require !== 'undefined')) { jQuery = require('jquery'); }
if (!jQuery) {
throw 'VIE requires jQuery to be available';
} |
VIE.EntityManagerVIE.EntityManager keeps track of all RDFa entities loaded via VIE. This means that entities matched by a common subject can be treated as singletons. It is possible to access all loaded entities via the
| VIE.EntityManager = {
Entities: {},
allEntities: [],
Types: {}, |
VIE.EntityManager.getBySubjectIt is possible to get an entity that has been loaded from the page
via the The entities accessed this way are singletons, so multiple calls to same
subject will all return the same Example: | getBySubject: function(id) {
if (typeof VIE.EntityManager.Entities[id] === 'undefined') {
return null;
}
return VIE.EntityManager.Entities[id];
}, |
VIE.EntityManager.getByJSONLDAnother way to get or load entities is by passing EntityManager a valid JSON-LD object. This can be either called with a JavaScript object representing JSON-LD, or with a JSON-LD string. Example: | getByJSONLD: function(jsonld) {
var entityInstance;
var properties;
if (typeof jsonld !== 'object') {
try {
jsonld = jQuery.parseJSON(jsonld);
} catch (e) {
return null;
}
}
properties = VIE.EntityManager._JSONtoProperties(jsonld); |
The entities accessed this way are singletons, so multiple calls
to same subject ( | if (typeof jsonld['@'] !== 'undefined') {
entityInstance = VIE.EntityManager.getBySubject(jsonld['@']);
if (entityInstance) {
entityInstance.set(properties);
return entityInstance;
}
}
entityInstance = new VIE.RDFEntity(properties); |
Namespace prefixes are handled by the | if (typeof jsonld['#'] !== 'undefined') {
entityInstance.namespaces = jsonld['#'];
} |
Types are handled by the | if (typeof jsonld.a !== 'undefined') {
entityInstance.type = jsonld.a;
} |
Subjects are handled by the | if (typeof jsonld['@'] !== 'undefined') {
entityInstance.id = jsonld['@'];
VIE.EntityManager.Entities[entityInstance.id] = entityInstance;
} |
All new entities must be added to the | VIE.EntityManager.allEntities.push(entityInstance);
return entityInstance;
}, |
Helper for cleaning up JSON-LD so that it can be used as properties of a Backbone Model | _JSONtoProperties: function(jsonld) {
var properties;
properties = jQuery.extend({}, jsonld);
delete properties['@'];
delete properties.a;
delete properties['#'];
return properties;
}
}; |
VIE.RDFEntityVIE.RDFEntity defines a common Backbone Model for RDF entities handled in VIE. | VIE.RDFEntity = Backbone.Model.extend({
namespaces: {},
type: '', |
VIE's entities have a method for generating JSON-LD representations of themselves. JSON-LD is a lightweight format for handling Linked Data (RDF) information. Using the book example from above, we could call:
And we would get a JSON object looking like the following:
Calling | toJSONLD: function() {
var instance = this;
var instanceLD = {};
var property;
if (typeof instance.id !== 'undefined') {
instanceLD['@'] = '<' + instance.id + '>';
} else {
instanceLD['@'] = instance.cid.replace('c', '_:bnode');
}
if (instance.namespaces.length > 0) {
instanceLD['#'] = instance.namespaces;
}
if (instance.type) {
instanceLD.a = instance.type;
}
for (property in instance.attributes) {
if (instance.attributes.hasOwnProperty(property)) {
if (['id'].indexOf(property) === -1) {
instanceLD[property] = instance.attributes[property];
}
}
}
return instanceLD;
}
});
|
VIE.RDFaEntitiesVIE.RDFaEntities provide mapping between RDFa on a page and Backbone Views.
When you load RDFa entities from a page, new If you're working with RDFa -annotated content and want to access it as Backbone Models, then VIE.RDFaEntities is the main access point. | VIE.RDFaEntities = { |
RDFaEntities manages a list of Views so that every view instance will be a singleton. | Views: [], |
VIE.RDFaEntities.getInstanceThe Example: | getInstance: function(element) {
element = jQuery(element);
var entityInstance;
var viewInstance;
var jsonld;
jsonld = VIE.RDFa.readEntity(element);
if (!jsonld) {
return null;
}
entityInstance = VIE.EntityManager.getByJSONLD(jsonld); |
Check whether we already have a View instantiated for the DOM element | jQuery.each(VIE.RDFaEntities.Views, function() {
if (this.el.get(0) === element.get(0)) {
viewInstance = this;
return false;
}
}); |
If no matching View was found, create a view for the RDFa | if (!viewInstance) {
viewInstance = new VIE.RDFaView({
model: entityInstance,
el: element,
tagName: element.get(0).nodeName
});
VIE.RDFaEntities.Views.push(viewInstance);
}
return entityInstance;
}, |
VIE.RDFaEntities.getInstancesGet a list of Backbone Model instances for all RDFa-marked content in an element. The method accepts jQuery selectors as argument. If no selector is given, then the whole HTML document will be searched. Example: | getInstances: function(element) {
var entities = [];
var entity;
if (typeof element === 'undefined') {
element = jQuery(document);
}
jQuery(VIE.RDFa.subjectSelector, element).add(jQuery(element).filter(VIE.RDFa.subjectSelector)).each(function() {
entity = VIE.RDFaEntities.getInstance(this);
if (entity) {
entities.push(entity);
}
});
return entities;
}
};
|
VIE.RDFaViewVIE.RDFaView defines a common Backbone View
for all RDFa -annotated elements on a page that have been loaded as
In normal operation, the RDFaView objects are automatically handled by
| VIE.RDFaView = Backbone.View.extend({ |
We ensure view gets updated when properties of the Entity change. | initialize: function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
}, |
Rendering a view means writing the properties of the Entity back to the element containing our RDFa annotations. | render: function() {
VIE.RDFa.writeEntity(this.el, this.model.toJSONLD());
return this;
}
}); |
VIE.RDFaRDFa reading and writing utilities. VIE.RDFa acts as a mapping tool between JSON-LD -encoded RDF triples and RDFa -annotated content on a page. | VIE.RDFa = {
Namespaces: {}, |
By default we look for RDF subjects based on elements that have a
For more specialized scenarios this can be overridden: | subjectSelector: '[about],[typeof],[src],html',
|
By default we look for RDF predicates based on elements that have a
For more specialized scenarios this can be overridden: | predicateSelector: '[property],[rel]', |
VIE.RDFa.getSubjectGet the RDF subject for an element. The method accepts jQuery selectors as arguments. If no argument is given, then the base URL of the page is used. Returns the subject as a string if one can be found, and if the
given element has no valid subjects returns Example: | getSubject: function(element) {
if (typeof document !== 'undefined') {
if (element === document) {
return document.baseURI;
}
}
var subject;
jQuery(element).closest(VIE.RDFa.subjectSelector).each(function() {
if (jQuery(this).attr('about')) {
subject = jQuery(this).attr('about');
return true;
}
if (jQuery(this).attr('src')) {
subject = jQuery(this).attr('src');
return true;
}
if (jQuery(this).attr('typeof')) {
subject = this;
return true;
} |
We also handle baseURL outside browser context by manually
looking for the | if (jQuery(this).get(0).nodeName === 'HTML') {
jQuery(this).find('base').each(function() {
subject = jQuery(this).attr('href');
});
}
});
return subject;
}, |
VIE.RDFa.readEntityGet a JSON-LD object for an RDFa-marked entity in
an element. The method accepts jQuery selectors
as argument. If the element contains no RDFa entities, the this method
returns Example:
Would return a JSON-LD object looking like the following: | readEntity: function(element) {
var entity;
var subject;
var namespaces = {};
var namespace;
var type;
var propertyName;
subject = VIE.RDFa.getSubject(element);
entity = VIE.RDFa._getElementProperties(subject, element, false);
if (jQuery.isEmptyObject(entity)) {
return null;
} |
We also try to resolve namespaces used in the RDFa entity. If they
can be found, we will write them to the | for (propertyName in entity) {
if (entity.hasOwnProperty(propertyName)) {
var propertyParts = propertyName.split(':');
if (propertyParts.length === 2) {
namespace = VIE.RDFa._resolveNamespace(propertyParts[0], element);
if (namespace) {
namespaces[propertyParts[0]] = namespace;
}
}
}
}
if (!jQuery.isEmptyObject(namespaces)) {
entity['#'] = namespaces;
} |
If the RDF type is defined, that will be set to the | type = VIE.RDFa._getElementValue(element, 'typeof');
if (type) {
entity.a = type;
}
if (typeof subject === 'string') {
entity['@'] = subject;
}
return entity;
}, |
VIE.RDFa.readEntitiesGet a list of JSON-LD objects for RDFa-marked entities in an element. The method accepts jQuery selectors as argument. If no selector is given, then the whole HTML document will be searched. Example:
Would produce something like: | readEntities: function(element) {
var entities = [];
var entity;
if (typeof element === 'undefined') {
element = jQuery(document);
}
jQuery(VIE.RDFa.subjectSelector, element).add(jQuery(element).filter(VIE.RDFa.subjectSelector)).each(function() {
entity = VIE.RDFa.readEntity(this);
if (entity) {
entities.push(entity);
}
});
return entities;
}, |
VIE.RDFa.writeEntityWrite the contents of a JSON-LD object into the given DOM element. This method accepts jQuery selectors as arguments. Only properties matching RDFa-annotated predicates found found from the selected DOM element will be written. | writeEntity: function(element, jsonld) {
VIE.RDFa.findPredicateElements(VIE.RDFa.getSubject(element), element, true).each(function() {
var propertyElement = jQuery(this);
var propertyName = propertyElement.attr('property');
if (typeof jsonld[propertyName] === 'undefined') {
return true;
} |
Before writing to DOM we check that the value has actually changed. | if (VIE.RDFa._readPropertyValue(propertyElement) !== jsonld[propertyName]) {
VIE.RDFa._writePropertyValue(propertyElement, jsonld[propertyName]);
}
});
return this;
},
|
VIE.RDFa.findPredicateElementsFind RDFa-annotated predicates for a given subject inside the DOM. This method accepts jQuery selectors as arguments. The method returns a list of matching DOM elements. Only predicates matching the given subject will be returned. You can also tell whether to allow nested predicates to be returned, which is useful for example when instantiating WYSIWYG editors for editable properties, as most editors do not like getting nested. | findPredicateElements: function(subject, element, allowNestedPredicates) {
return jQuery(element).find(VIE.RDFa.predicateSelector).add(jQuery(element).filter(VIE.RDFa.predicateSelector)).filter(function() {
if (VIE.RDFa.getSubject(this) !== subject) {
return false;
}
if (!allowNestedPredicates) {
if (!jQuery(this).parents('[property]').length) {
return true;
}
return false;
}
return true;
});
}, |
Get value of a DOM element defining a RDFa predicate. | _readPropertyValue: function(element) { |
The | var content = element.attr('content');
if (content) {
return content;
}
|
The | var resource = element.attr('resource');
if (resource) {
return '<' + resource + '>';
}
|
| var href = element.attr('href');
if (href) {
return '<' + href + '>';
} |
If the predicate is a relation, we look for identified child objects
and provide their identifiers as the values. To protect from scope
creep, we only support direct descentants of the element where the
| if (element.attr('rel')) {
var value = [];
jQuery(element).children(VIE.RDFa.subjectSelector).each(function() {
var subject = VIE.RDFa.getSubject(this);
if (typeof subject === 'string') {
value.push('<' + subject + '>');
}
});
return value;
} |
If none of the checks above matched we return the HTML contents of the element as the literal value. | return element.html();
}, |
Write a value to a DOM element defining a RDFa predicate. | _writePropertyValue: function(element, value) { |
For now we don't deal with multivalued properties when writing contents. | if (value instanceof Array) {
return true;
}
|
The | var content = element.attr('content');
if (content) {
element.attr('content', value);
return;
}
|
The | var resource = element.attr('resource');
if (resource) {
element.attr('resource', value);
} |
Property has inline value. Change the HTML contents of the property element to match the new value. | element.html(value);
}, |
Namespace resolution, find namespace declarations from inside a DOM element. | _resolveNamespace: function(prefix, element) {
if (typeof VIE.RDFa.Namespaces[prefix] !== 'undefined') {
return VIE.RDFa.Namespaces[prefix];
}
jQuery('[xmlns\\:' + prefix + ']').each(function() {
VIE.RDFa.Namespaces[prefix] = jQuery(this).attr('xmlns:' + prefix);
});
return VIE.RDFa.Namespaces[prefix];
}, |
Get the value of an attribute from the element or from one of its children | _getElementValue: function(element, propertyName) {
element = jQuery(element);
if (typeof element.attr(propertyName) !== 'undefined')
{
return element.attr(propertyName);
}
return element.children('[' + propertyName + ']').attr(propertyName);
}, |
Get JSON-LD properties from a DOM element. | _getElementProperties: function(subject, element, emptyValues) {
var containerProperties = {};
VIE.RDFa.findPredicateElements(subject, element, true).each(function() {
var propertyName;
var propertyValue;
var objectProperty = jQuery(this);
propertyName = objectProperty.attr('property');
if (!propertyName) {
propertyName = objectProperty.attr('rel');
}
propertyValue = VIE.RDFa._readPropertyValue(objectProperty);
if (typeof containerProperties[propertyName] !== 'undefined') {
if (containerProperties[propertyName] instanceof Array) {
if (emptyValues) {
return;
}
containerProperties[propertyName].push(propertyValue);
return;
} |
Multivalued properties, are converted to an Array | var previousValue = containerProperties[propertyName];
containerProperties[propertyName] = [];
if (emptyValues) {
return;
}
containerProperties[propertyName].push(previousValue);
containerProperties[propertyName].push(propertyValue);
return;
}
if (emptyValues) {
containerProperties[propertyName] = '';
return;
}
containerProperties[propertyName] = propertyValue;
});
return containerProperties;
}
}; |
VIE.cleanup()By default VIE keeps track of all RDF entities, RDFa views and namespaces handled. If you want to clear all of these (for example in unit tests), then call: | VIE.cleanup = function() {
VIE.EntityManager.Entities = {};
VIE.EntityManager.allEntities = [];
VIE.EntityManager.Types = {};
VIE.RDFaEntities.Views = [];
VIE.RDFa.Namespaces = {};
};
}).call(this);
|