core/misc/create/create-editonly.js | 1643 +++++++++
core/misc/vie/vie-core.js | 3682 ++++++++++++++++++++
core/modules/edit/css/edit.css | 405 +++
core/modules/edit/edit.info | 7 +
core/modules/edit/edit.module | 381 ++
core/modules/edit/images/attention.png | 4 +
core/modules/edit/images/close.png | 4 +
core/modules/edit/images/icon-edit-active.png | 3 +
core/modules/edit/images/icon-edit.png | 5 +
core/modules/edit/images/throbber.gif | 6 +
core/modules/edit/includes/form.inc | 105 +
core/modules/edit/includes/pages.inc | 165 +
core/modules/edit/js/app.js | 318 ++
core/modules/edit/js/backbone.drupalform.js | 157 +
core/modules/edit/js/createjs/editable.js | 43 +
.../editingWidgets/drupalcontenteditablewidget.js | 110 +
.../edit/js/createjs/editingWidgets/formwidget.js | 149 +
core/modules/edit/js/createjs/storage.js | 11 +
core/modules/edit/js/edit.js | 55 +
core/modules/edit/js/models/edit-app-model.js | 22 +
core/modules/edit/js/routers/edit-router.js | 54 +
core/modules/edit/js/theme.js | 156 +
core/modules/edit/js/util.js | 142 +
core/modules/edit/js/viejs/EditService.js | 201 ++
core/modules/edit/js/views/menu-view.js | 42 +
core/modules/edit/js/views/modal-view.js | 108 +
core/modules/edit/js/views/overlay-view.js | 82 +
.../edit/js/views/propertyeditordecoration-view.js | 322 ++
core/modules/edit/js/views/toolbar-view.js | 445 +++
.../edit/lib/Drupal/edit/Ajax/BaseCommand.php | 52 +
.../edit/lib/Drupal/edit/Ajax/FieldFormCommand.php | 27 +
.../lib/Drupal/edit/Ajax/FieldFormSavedCommand.php | 28 +
.../edit/Ajax/FieldFormValidationErrorsCommand.php | 28 +
...RenderedWithoutTransformationFiltersCommand.php | 28 +
core/modules/edit/lib/Drupal/edit/EditBundle.php | 26 +
.../Drupal/edit/Plugin/ProcessedTextEditorBase.php | 35 +
.../Plugin/Type/ProcessedTextEditorManager.php | 31 +
.../field/formatter/TextDefaultFormatter.php | 3 +
.../Plugin/field/formatter/TextPlainFormatter.php | 3 +
.../formatter/TextSummaryOrTrimmedFormatter.php | 3 +
.../field/formatter/TextTrimmedFormatter.php | 3 +
core/modules/system/system.module | 33 +-
42 files changed, 9125 insertions(+), 2 deletions(-)
diff --git a/core/misc/create/create-editonly.js b/core/misc/create/create-editonly.js
new file mode 100644
index 0000000..c705ba0
--- /dev/null
+++ b/core/misc/create/create-editonly.js
@@ -0,0 +1,1643 @@
+// Create.js - On-site web editing interface
+// (c) 2011-2012 Henri Bergius, IKS Consortium
+// Create may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://createjs.org/
+(function (jQuery, undefined) {
+ // Run JavaScript in strict mode
+ /*global jQuery:false _:false window:false console:false */
+ 'use strict';
+
+ // # Widget for adding items to a collection
+ jQuery.widget('Midgard.midgardCollectionAdd', {
+ options: {
+ editingWidgets: null,
+ collection: null,
+ model: null,
+ definition: null,
+ view: null,
+ disabled: false,
+ vie: null,
+ editableOptions: null,
+ templates: {
+ button: ''
+ }
+ },
+
+ _create: function () {
+ this.addButtons = [];
+ var widget = this;
+ if (!widget.options.collection.localStorage) {
+ try {
+ widget.options.collection.url = widget.options.model.url();
+ } catch (e) {
+ if (window.console) {
+ console.log(e);
+ }
+ }
+ }
+
+ widget.options.collection.bind('add', function (model) {
+ model.primaryCollection = widget.options.collection;
+ widget.options.vie.entities.add(model);
+ model.collection = widget.options.collection;
+ });
+
+ // Re-check collection constraints
+ widget.options.collection.bind('add remove reset', widget.checkCollectionConstraints, widget);
+
+ widget._bindCollectionView(widget.options.view);
+ },
+
+ _bindCollectionView: function (view) {
+ var widget = this;
+ view.bind('add', function (itemView) {
+ itemView.$el.effect('slide', function () {
+ widget._makeEditable(itemView);
+ });
+ });
+ },
+
+ _makeEditable: function (itemView) {
+ this.options.editableOptions.disabled = this.options.disabled;
+ this.options.editableOptions.model = itemView.model;
+ itemView.$el.midgardEditable(this.options.editableOptions);
+ },
+
+ _init: function () {
+ if (this.options.disabled) {
+ this.disable();
+ return;
+ }
+ this.enable();
+ },
+
+ hideButtons: function () {
+ _.each(this.addButtons, function (button) {
+ button.hide();
+ });
+ },
+
+ showButtons: function () {
+ _.each(this.addButtons, function (button) {
+ button.show();
+ });
+ },
+
+ checkCollectionConstraints: function () {
+ if (this.options.disabled) {
+ return;
+ }
+
+ if (!this.options.view.canAdd()) {
+ this.hideButtons();
+ return;
+ }
+
+ if (!this.options.definition) {
+ // We have now information on the constraints applying to this collection
+ this.showButtons();
+ return;
+ }
+
+ if (!this.options.definition.max || this.options.definition.max === -1) {
+ // No maximum constraint
+ this.showButtons();
+ return;
+ }
+
+ if (this.options.collection.length < this.options.definition.max) {
+ this.showButtons();
+ return;
+ }
+ // Collection is already full by its definition
+ this.hideButtons();
+ },
+
+ enable: function () {
+ var widget = this;
+
+ var addButton = jQuery(_.template(this.options.templates.button, {
+ icon: 'plus',
+ label: this.options.editableOptions.localize('Add', this.options.editableOptions.language)
+ })).button();
+ addButton.addClass('midgard-create-add');
+ addButton.click(function () {
+ widget.addItem(addButton);
+ });
+ jQuery(widget.options.view.el).after(addButton);
+
+ widget.addButtons.push(addButton);
+ widget.checkCollectionConstraints();
+ },
+
+ disable: function () {
+ _.each(this.addButtons, function (button) {
+ button.remove();
+ });
+ this.addButtons = [];
+ },
+
+ _getTypeActions: function (options) {
+ var widget = this;
+ var actions = [];
+ _.each(this.options.definition.range, function (type) {
+ var nsType = widget.options.collection.vie.namespaces.uri(type);
+ if (!widget.options.view.canAdd(nsType)) {
+ return;
+ }
+ actions.push({
+ name: type,
+ label: type,
+ cb: function () {
+ widget.options.collection.add({
+ '@type': type
+ }, options);
+ },
+ className: 'create-ui-btn'
+ });
+ });
+ return actions;
+ },
+
+ addItem: function (button, options) {
+ if (options === undefined) {
+ options = {};
+ }
+ var addOptions = _.extend({}, options, { validate: false });
+
+ var itemData = {};
+ if (this.options.definition && this.options.definition.range) {
+ if (this.options.definition.range.length === 1) {
+ // Items can be of single type, add that
+ itemData['@type'] = this.options.definition.range[0];
+ } else {
+ // Ask user which type to add
+ jQuery('body').midgardNotifications('create', {
+ bindTo: button,
+ gravity: 'L',
+ body: this.options.editableOptions.localize('Choose type to add', this.options.editableOptions.language),
+ timeout: 0,
+ actions: this._getTypeActions(addOptions)
+ });
+ return;
+ }
+ } else {
+ // Check the view templates for possible non-Thing type to use
+ var keys = _.keys(this.options.view.templates);
+ if (keys.length == 2) {
+ itemData['@type'] = keys[0];
+ }
+ }
+ this.options.collection.add(itemData, addOptions);
+ }
+ });
+})(jQuery);
+// Create.js - On-site web editing interface
+// (c) 2011-2012 Henri Bergius, IKS Consortium
+// Create may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://createjs.org/
+(function (jQuery, undefined) {
+ // Run JavaScript in strict mode
+ /*global jQuery:false _:false window:false console:false */
+ 'use strict';
+
+ // # Widget for adding items anywhere inside a collection
+ jQuery.widget('Midgard.midgardCollectionAddBetween', jQuery.Midgard.midgardCollectionAdd, {
+ _bindCollectionView: function (view) {
+ var widget = this;
+ view.bind('add', function (itemView) {
+ //itemView.el.effect('slide');
+ widget._makeEditable(itemView);
+ widget._refreshButtons();
+ });
+ view.bind('remove', function () {
+ widget._refreshButtons();
+ });
+ },
+
+ _refreshButtons: function () {
+ var widget = this;
+ window.setTimeout(function () {
+ widget.disable();
+ widget.enable();
+ }, 1);
+ },
+
+ prepareButton: function (index) {
+ var widget = this;
+ var addButton = jQuery(_.template(this.options.templates.button, {
+ icon: 'plus',
+ label: ''
+ })).button();
+ addButton.addClass('midgard-create-add');
+ addButton.click(function () {
+ widget.addItem(addButton, {
+ at: index
+ });
+ });
+ return addButton;
+ },
+
+ enable: function () {
+ var widget = this;
+
+ var firstAddButton = widget.prepareButton(0);
+ jQuery(widget.options.view.el).prepend(firstAddButton);
+ widget.addButtons.push(firstAddButton);
+ jQuery.each(widget.options.view.entityViews, function (cid, view) {
+ var index = widget.options.collection.indexOf(view.model);
+ var addButton = widget.prepareButton(index + 1);
+ jQuery(view.el).append(addButton);
+ widget.addButtons.push(addButton);
+ });
+
+ this.checkCollectionConstraints();
+ },
+
+ disable: function () {
+ var widget = this;
+ jQuery.each(widget.addButtons, function (idx, button) {
+ button.remove();
+ });
+ widget.addButtons = [];
+ }
+ });
+})(jQuery);
+// Create.js - On-site web editing interface
+// (c) 2011-2012 Henri Bergius, IKS Consortium
+// Create may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://createjs.org/
+(function (jQuery, undefined) {
+ // Run JavaScript in strict mode
+ /*global jQuery:false _:false window:false VIE:false */
+ 'use strict';
+
+ // Define Create's EditableEntity widget.
+ jQuery.widget('Midgard.midgardEditable', {
+ options: {
+ propertyEditors: {},
+ collections: [],
+ model: null,
+ // the configuration (mapping and options) of property editor widgets
+ propertyEditorWidgetsConfiguration: {
+ hallo: {
+ widget: 'halloWidget',
+ options: {}
+ }
+ },
+ // the available property editor widgets by data type
+ propertyEditorWidgets: {
+ 'default': 'hallo'
+ },
+ collectionWidgets: {
+ 'default': 'midgardCollectionAdd'
+ },
+ toolbarState: 'full',
+ vie: null,
+ domService: 'rdfa',
+ predicateSelector: '[property]',
+ disabled: false,
+ localize: function (id, language) {
+ return window.midgardCreate.localize(id, language);
+ },
+ language: null,
+ // Current state of the Editable
+ state: null,
+ // Callback function for validating changes between states. Receives the previous state, new state, possibly property, and a callback
+ acceptStateChange: true,
+ // Callback function for listening (and reacting) to state changes.
+ stateChange: null,
+ // Callback function for decorating the full editable. Will be called on instantiation
+ decorateEditableEntity: null,
+ // Callback function for decorating a single property editor widget. Will
+ // be called on editing widget instantiation.
+ decoratePropertyEditor: null,
+
+ // Deprecated.
+ editables: [], // Now `propertyEditors`.
+ editors: {}, // Now `propertyEditorWidgetsConfiguration`.
+ widgets: {} // Now `propertyEditorW
+ },
+
+ // Aids in consistently passing parameters to events and callbacks.
+ _params: function(predicate, extended) {
+ var entityParams = {
+ entity: this.options.model,
+ editableEntity: this,
+ entityElement: this.element,
+
+ // Deprecated.
+ editable: this,
+ element: this.element,
+ instance: this.options.model
+ };
+
+ var propertyParams = (predicate) ? {
+ predicate: predicate,
+ propertyEditor: this.options.propertyEditors[predicate],
+ propertyElement: this.options.propertyEditors[predicate].element,
+
+ // Deprecated.
+ property: predicate,
+ element: this.options.propertyEditors[predicate].element
+ } : {};
+
+ return _.extend(entityParams, propertyParams, extended);
+ },
+
+ _create: function () {
+ // Backwards compatibility:
+ // - this.options.propertyEditorWidgets used to be this.options.widgets
+ // - this.options.propertyEditorWidgetsConfiguration used to be
+ // this.options.editors
+ if (this.options.widgets) {
+ this.options.propertyEditorWidgets = _.extend(this.options.propertyEditorWidgets, this.options.widgets);
+ }
+ if (this.options.editors) {
+ this.options.propertyEditorWidgetsConfiguration = _.extend(this.options.propertyEditorWidgetsConfiguration, this.options.editors);
+ }
+
+ this.vie = this.options.vie;
+ this.domService = this.vie.service(this.options.domService);
+ if (!this.options.model) {
+ var widget = this;
+ this.vie.load({
+ element: this.element
+ }).from(this.options.domService).execute().done(function (entities) {
+ widget.options.model = entities[0];
+ });
+ }
+ if (_.isFunction(this.options.decorateEditableEntity)) {
+ this.options.decorateEditableEntity(this._params());
+ }
+ },
+
+ _init: function () {
+ // Backwards compatibility:
+ // - this.options.propertyEditorWidgets used to be this.options.widgets
+ // - this.options.propertyEditorWidgetsConfiguration used to be
+ // this.options.editors
+ if (this.options.widgets) {
+ this.options.propertyEditorWidgets = _.extend(this.options.propertyEditorWidgets, this.options.widgets);
+ }
+ if (this.options.editors) {
+ this.options.propertyEditorWidgetsConfiguration = _.extend(this.options.propertyEditorWidgetsConfiguration, this.options.editors);
+ }
+
+ // Old way of setting the widget inactive
+ if (this.options.disabled === true) {
+ this.setState('inactive');
+ return;
+ }
+
+ if (this.options.disabled === false && this.options.state === 'inactive') {
+ this.setState('candidate');
+ return;
+ }
+ this.options.disabled = false;
+
+ if (this.options.state) {
+ this.setState(this.options.state);
+ return;
+ }
+ this.setState('candidate');
+ },
+
+ // Method used for cycling between the different states of the Editable widget:
+ //
+ // * Inactive: editable is loaded but disabled
+ // * Candidate: editable is enabled but not activated
+ // * Highlight: user is hovering over the editable (not set by Editable widget directly)
+ // * Activating: an editor widget is being activated for user to edit with it (skipped for editors that activate instantly)
+ // * Active: user is actually editing something inside the editable
+ // * Changed: user has made changes to the editable
+ // * Invalid: the contents of the editable have validation errors
+ //
+ // In situations where state changes are triggered for a particular property editor, the `predicate`
+ // argument will provide the name of that property.
+ //
+ // State changes may carry optional context information in a JavaScript object. The payload of these context objects is not
+ // standardized, and is meant to be set and used by the application controller
+ //
+ // The callback parameter is optional and will be invoked after a state change has been accepted (after the 'statechange'
+ // event) or rejected.
+ setState: function (state, predicate, context, callback) {
+ var previous = this.options.state;
+ var current = state;
+ if (current === previous) {
+ return;
+ }
+
+ if (this.options.acceptStateChange === undefined || !_.isFunction(this.options.acceptStateChange)) {
+ // Skip state transition validation
+ this._doSetState(previous, current, predicate, context);
+ if (_.isFunction(callback)) {
+ callback(true);
+ }
+ return;
+ }
+
+ var widget = this;
+ this.options.acceptStateChange(previous, current, predicate, context, function (accepted) {
+ if (accepted) {
+ widget._doSetState(previous, current, predicate, context);
+ }
+ if (_.isFunction(callback)) {
+ callback(accepted);
+ }
+ return;
+ });
+ },
+
+ getState: function () {
+ return this.options.state;
+ },
+
+ _doSetState: function (previous, current, predicate, context) {
+ this.options.state = current;
+ if (current === 'inactive') {
+ this.disable();
+ } else if ((previous === null || previous === 'inactive') && current !== 'inactive') {
+ this.enable();
+ }
+
+ this._trigger('statechange', null, this._params(predicate, {
+ previous: previous,
+ current: current,
+ context: context
+ }));
+ },
+
+ findEditablePredicateElements: function (callback) {
+ this.domService.findPredicateElements(this.options.model.id, jQuery(this.options.predicateSelector, this.element), false).each(callback);
+ },
+
+ getElementPredicate: function (element) {
+ return this.domService.getElementPredicate(element);
+ },
+
+ enable: function () {
+ var editableEntity = this;
+ if (!this.options.model) {
+ return;
+ }
+
+ this.findEditablePredicateElements(function () {
+ editableEntity._enablePropertyEditor(jQuery(this));
+ });
+
+ this._trigger('enable', null, this._params());
+
+ _.each(this.domService.views, function (view) {
+ if (view instanceof this.vie.view.Collection && this.options.model === view.owner) {
+ var predicate = view.collection.predicate;
+ var editableOptions = _.clone(this.options);
+ editableOptions.state = null;
+ var collection = this.enableCollection({
+ model: this.options.model,
+ collection: view.collection,
+ property: predicate,
+ definition: this.getAttributeDefinition(predicate),
+ view: view,
+ element: view.el,
+ vie: editableEntity.vie,
+ editableOptions: editableOptions
+ });
+ editableEntity.options.collections.push(collection);
+ }
+ }, this);
+ },
+
+ disable: function () {
+ _.each(this.options.propertyEditors, function (editable) {
+ this.disableEditor({
+ widget: this,
+ editable: editable,
+ entity: this.options.model,
+ element: jQuery(editable)
+ });
+ }, this);
+ this.options.propertyEditors = {};
+
+ // Deprecated.
+ this.options.editables = [];
+
+ _.each(this.options.collections, function (collectionWidget) {
+ var editableOptions = _.clone(this.options);
+ editableOptions.state = 'inactive';
+ this.disableCollection({
+ widget: this,
+ model: this.options.model,
+ element: collectionWidget,
+ vie: this.vie,
+ editableOptions: editableOptions
+ });
+ }, this);
+ this.options.collections = [];
+
+ this._trigger('disable', null, this._params());
+ },
+
+ _enablePropertyEditor: function (element) {
+ var widget = this;
+ var predicate = this.getElementPredicate(element);
+ if (!predicate) {
+ return true;
+ }
+ if (this.options.model.get(predicate) instanceof Array) {
+ // For now we don't deal with multivalued properties in the editable
+ return true;
+ }
+
+ var propertyElement = this.enablePropertyEditor({
+ widget: this,
+ element: element,
+ entity: this.options.model,
+ property: predicate,
+ vie: this.vie,
+ decorate: this.options.decoratePropertyEditor,
+ decorateParams: _.bind(this._params, this),
+ changed: function (content) {
+ widget.setState('changed', predicate);
+
+ var changedProperties = {};
+ changedProperties[predicate] = content;
+ widget.options.model.set(changedProperties, {
+ silent: true
+ });
+
+ widget._trigger('changed', null, widget._params(predicate));
+ },
+ activating: function () {
+ widget.setState('activating', predicate);
+ },
+ activated: function () {
+ widget.setState('active', predicate);
+ widget._trigger('activated', null, widget._params(predicate));
+ },
+ deactivated: function () {
+ widget.setState('candidate', predicate);
+ widget._trigger('deactivated', null, widget._params(predicate));
+ }
+ });
+
+ if (!propertyElement) {
+ return;
+ }
+ var widgetType = propertyElement.data('createWidgetName');
+ this.options.propertyEditors[predicate] = propertyElement.data(widgetType);
+
+ // Deprecated.
+ this.options.editables.push(propertyElement);
+
+ this._trigger('enableproperty', null, this._params(predicate));
+ },
+
+ // returns the name of the property editor widget to use for the given property
+ _propertyEditorName: function (data) {
+ if (this.options.propertyEditorWidgets[data.property] !== undefined) {
+ // Property editor widget configuration set for specific RDF predicate
+ return this.options.propertyEditorWidgets[data.property];
+ }
+
+ // Load the property editor widget configuration for the data type
+ var propertyType = 'default';
+ var attributeDefinition = this.getAttributeDefinition(data.property);
+ if (attributeDefinition) {
+ propertyType = attributeDefinition.range[0];
+ }
+ if (this.options.propertyEditorWidgets[propertyType] !== undefined) {
+ return this.options.propertyEditorWidgets[propertyType];
+ }
+ return this.options.propertyEditorWidgets['default'];
+ },
+
+ _propertyEditorWidget: function (editor) {
+ return this.options.propertyEditorWidgetsConfiguration[editor].widget;
+ },
+
+ _propertyEditorOptions: function (editor) {
+ return this.options.propertyEditorWidgetsConfiguration[editor].options;
+ },
+
+ getAttributeDefinition: function (property) {
+ var type = this.options.model.get('@type');
+ if (!type) {
+ return;
+ }
+ if (!type.attributes) {
+ return;
+ }
+ return type.attributes.get(property);
+ },
+
+ // Deprecated.
+ enableEditor: function (data) {
+ return this.enablePropertyEditor(data);
+ },
+
+ enablePropertyEditor: function (data) {
+ var editorName = this._propertyEditorName(data);
+ if (editorName === null) {
+ return;
+ }
+
+ var editorWidget = this._propertyEditorWidget(editorName);
+
+ data.editorOptions = this._propertyEditorOptions(editorName);
+ data.toolbarState = this.options.toolbarState;
+ data.disabled = false;
+ // Pass metadata that could be useful for some implementations.
+ data.editorName = editorName;
+ data.editorWidget = editorWidget;
+
+ if (typeof jQuery(data.element)[editorWidget] !== 'function') {
+ throw new Error(editorWidget + ' widget is not available');
+ }
+
+ jQuery(data.element)[editorWidget](data);
+ jQuery(data.element).data('createWidgetName', editorWidget);
+ return jQuery(data.element);
+ },
+
+ // Deprecated.
+ disableEditor: function (data) {
+ return this.disablePropertyEditor(data);
+ },
+
+ disablePropertyEditor: function (data) {
+ var widgetName = jQuery(data.element).data('createWidgetName');
+
+ data.disabled = true;
+
+ if (widgetName) {
+ // only if there has been an editing widget registered
+ jQuery(data.element)[widgetName](data);
+ jQuery(data.element).removeClass('ui-state-disabled');
+
+ if (data.element.is(':focus')) {
+ data.element.blur();
+ }
+ }
+ },
+
+ collectionWidgetName: function (data) {
+ if (this.options.collectionWidgets[data.property] !== undefined) {
+ // Widget configuration set for specific RDF predicate
+ return this.options.collectionWidgets[data.property];
+ }
+
+ var propertyType = 'default';
+ var attributeDefinition = this.getAttributeDefinition(data.property);
+ if (attributeDefinition) {
+ propertyType = attributeDefinition.range[0];
+ }
+ if (this.options.collectionWidgets[propertyType] !== undefined) {
+ return this.options.collectionWidgets[propertyType];
+ }
+ return this.options.collectionWidgets['default'];
+ },
+
+ enableCollection: function (data) {
+ var widgetName = this.collectionWidgetName(data);
+ if (widgetName === null) {
+ return;
+ }
+ data.disabled = false;
+ if (typeof jQuery(data.element)[widgetName] !== 'function') {
+ throw new Error(widgetName + ' widget is not available');
+ }
+ jQuery(data.element)[widgetName](data);
+ jQuery(data.element).data('createCollectionWidgetName', widgetName);
+ return jQuery(data.element);
+ },
+
+ disableCollection: function (data) {
+ var widgetName = jQuery(data.element).data('createCollectionWidgetName');
+ if (widgetName === null) {
+ return;
+ }
+ data.disabled = true;
+ if (widgetName) {
+ // only if there has been an editing widget registered
+ jQuery(data.element)[widgetName](data);
+ jQuery(data.element).removeClass('ui-state-disabled');
+ }
+ }
+ });
+})(jQuery);
+// Create.js - On-site web editing interface
+// (c) 2012 Tobias Herrmann, IKS Consortium
+// Create may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://createjs.org/
+(function (jQuery, undefined) {
+ // Run JavaScript in strict mode
+ /*global jQuery:false _:false document:false */
+ 'use strict';
+
+ // # Base property editor widget
+ //
+ // This property editor widget provides a very simplistic `contentEditable`
+ // property editor that can be used as standalone, but should more usually be
+ // used as the base class for other property editor widgets.
+ // This property editor widget is only useful for textual properties!
+ //
+ // Subclassing this base property editor widget is easy:
+ //
+ // jQuery.widget('Namespace.MyWidget', jQuery.Create.editWidget, {
+ // // override any properties
+ // });
+ jQuery.widget('Create.editWidget', {
+ options: {
+ disabled: false,
+ vie: null
+ },
+ // override to enable the widget
+ enable: function () {
+ this.element.attr('contenteditable', 'true');
+ },
+ // override to disable the widget
+ disable: function (disable) {
+ this.element.attr('contenteditable', 'false');
+ },
+ // called by the jQuery UI plugin factory when creating the property editor
+ // widget instance
+ _create: function () {
+ this._registerWidget();
+ this._initialize();
+
+ if (_.isFunction(this.options.decorate) && _.isFunction(this.options.decorateParams)) {
+ // TRICKY: we can't use this.options.decorateParams()'s 'propertyName'
+ // parameter just yet, because it will only be available after this
+ // object has been created, but we're currently in the constructor!
+ // Hence we have to duplicate part of its logic here.
+ this.options.decorate(this.options.decorateParams(null, {
+ propertyName: this.options.property,
+ propertyEditor: this,
+ propertyElement: this.element,
+ // Deprecated.
+ editor: this,
+ predicate: this.options.property,
+ element: this.element
+ }));
+ }
+ },
+ // called every time the property editor widget is called
+ _init: function () {
+ if (this.options.disabled) {
+ this.disable();
+ return;
+ }
+ this.enable();
+ },
+ // override this function to initialize the property editor widget functions
+ _initialize: function () {
+ var self = this;
+ this.element.bind('focus', function () {
+ if (self.options.disabled) {
+ return;
+ }
+ self.options.activated();
+ });
+ this.element.bind('blur', function () {
+ if (self.options.disabled) {
+ return;
+ }
+ self.options.deactivated();
+ });
+ var before = this.element.html();
+ this.element.bind('keyup paste', function (event) {
+ if (self.options.disabled) {
+ return;
+ }
+ var current = jQuery(this).html();
+ if (before !== current) {
+ before = current;
+ self.options.changed(current);
+ }
+ });
+ },
+ // used to register the property editor widget name with the DOM element
+ _registerWidget: function () {
+ this.element.data("createWidgetName", this.widgetName);
+ }
+ });
+})(jQuery);
+// Create.js - On-site web editing interface
+// (c) 2012 Tobias Herrmann, IKS Consortium
+// (c) 2011 Rene Kapusta, Evo42
+// Create may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://createjs.org/
+(function (jQuery, undefined) {
+ // Run JavaScript in strict mode
+ /*global jQuery:false _:false document:false Aloha:false */
+ 'use strict';
+
+ // # Aloha editing widget
+ //
+ // This widget allows editing textual contents using the
+ // [Aloha](http://aloha-editor.org) rich text editor.
+ //
+ // Due to licensing incompatibilities, Aloha Editor needs to be installed
+ // and configured separately.
+ jQuery.widget('Create.alohaWidget', jQuery.Create.editWidget, {
+ _initialize: function () {},
+ enable: function () {
+ var options = this.options;
+ var editable;
+ var currentElement = Aloha.jQuery(options.element.get(0)).aloha();
+ _.each(Aloha.editables, function (aloha) {
+ // Find the actual editable instance so we can hook to the events
+ // correctly
+ if (aloha.obj.get(0) === currentElement.get(0)) {
+ editable = aloha;
+ }
+ });
+ if (!editable) {
+ return;
+ }
+ editable.vieEntity = options.entity;
+
+ // Subscribe to activation and deactivation events
+ Aloha.bind('aloha-editable-activated', function (event, data) {
+ if (data.editable !== editable) {
+ return;
+ }
+ options.activated();
+ });
+ Aloha.bind('aloha-editable-deactivated', function (event, data) {
+ if (data.editable !== editable) {
+ return;
+ }
+ options.deactivated();
+ });
+
+ Aloha.bind('aloha-smart-content-changed', function (event, data) {
+ if (data.editable !== editable) {
+ return;
+ }
+ if (!data.editable.isModified()) {
+ return true;
+ }
+ options.changed(data.editable.getContents());
+ data.editable.setUnmodified();
+ });
+ this.options.disabled = false;
+ },
+ disable: function () {
+ Aloha.jQuery(this.options.element.get(0)).mahalo();
+ this.options.disabled = true;
+ }
+ });
+})(jQuery);
+// Create.js - On-site web editing interface
+// (c) 2012 Tobias Herrmann, IKS Consortium
+// Create may be freely distributed under the MIT license.
+// For all details and documentation:
+(function (jQuery, undefined) {
+ // Run JavaScript in strict mode
+ /*global jQuery:false _:false document:false CKEDITOR:false */
+ 'use strict';
+
+ // # CKEditor editing widget
+ //
+ // This widget allows editing textual content areas with the
+ // [CKEditor](http://ckeditor.com/) rich text editor.
+ jQuery.widget('Create.ckeditorWidget', jQuery.Create.editWidget, {
+ enable: function () {
+ this.element.attr('contentEditable', 'true');
+ this.editor = CKEDITOR.inline(this.element.get(0));
+ this.options.disabled = false;
+
+ var widget = this;
+ this.editor.on('focus', function () {
+ widget.options.activated();
+ });
+ this.editor.on('blur', function () {
+ widget.options.activated();
+ });
+ this.editor.on('key', function () {
+ widget.options.changed(widget.editor.getData());
+ });
+ this.editor.on('paste', function () {
+ widget.options.changed(widget.editor.getData());
+ });
+ this.editor.on('afterCommandExec', function () {
+ widget.options.changed(widget.editor.getData());
+ });
+ },
+
+ disable: function () {
+ if (!this.editor) {
+ return;
+ }
+ this.element.attr('contentEditable', 'false');
+ this.editor.destroy();
+ this.editor = null;
+ },
+
+ _initialize: function () {
+ CKEDITOR.disableAutoInline = true;
+ }
+ });
+})(jQuery);
+// Create.js - On-site web editing interface
+// (c) 2012 Tobias Herrmann, IKS Consortium
+// Create may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://createjs.org/
+(function (jQuery, undefined) {
+ // Run JavaScript in strict mode
+ /*global jQuery:false _:false document:false */
+ 'use strict';
+
+ // # Hallo editing widget
+ //
+ // This widget allows editing textual content areas with the
+ // [Hallo](http://hallojs.org) rich text editor.
+ jQuery.widget('Create.halloWidget', jQuery.Create.editWidget, {
+ options: {
+ editorOptions: {},
+ disabled: true,
+ toolbarState: 'full',
+ vie: null,
+ entity: null
+ },
+ enable: function () {
+ jQuery(this.element).hallo({
+ editable: true
+ });
+ this.options.disabled = false;
+ },
+
+ disable: function () {
+ jQuery(this.element).hallo({
+ editable: false
+ });
+ this.options.disabled = true;
+ },
+
+ _initialize: function () {
+ jQuery(this.element).hallo(this.getHalloOptions());
+ var self = this;
+ jQuery(this.element).bind('halloactivated', function (event, data) {
+ self.options.activated();
+ });
+ jQuery(this.element).bind('hallodeactivated', function (event, data) {
+ self.options.deactivated();
+ });
+ jQuery(this.element).bind('hallomodified', function (event, data) {
+ self.options.changed(data.content);
+ data.editable.setUnmodified();
+ });
+
+ jQuery(document).bind('midgardtoolbarstatechange', function(event, data) {
+ // Switch between Hallo configurations when toolbar state changes
+ if (data.display === self.options.toolbarState) {
+ return;
+ }
+ self.options.toolbarState = data.display;
+ var newOptions = self.getHalloOptions();
+ self.element.hallo('changeToolbar', newOptions.parentElement, newOptions.toolbar, true);
+ });
+ },
+
+ getHalloOptions: function() {
+ var defaults = {
+ plugins: {
+ halloformat: {},
+ halloblock: {},
+ hallolists: {},
+ hallolink: {},
+ halloimage: {
+ entity: this.options.entity
+ }
+ },
+ buttonCssClass: 'create-ui-btn-small',
+ placeholder: '[' + this.options.property + ']'
+ };
+
+ if (typeof this.element.annotate === 'function' && this.options.vie.services.stanbol) {
+ // Enable Hallo Annotate plugin by default if user has annotate.js
+ // loaded and VIE has Stanbol enabled
+ defaults.plugins.halloannotate = {
+ vie: this.options.vie
+ };
+ }
+
+ if (this.options.toolbarState === 'full') {
+ // Use fixed toolbar in the Create tools area
+ defaults.parentElement = jQuery('.create-ui-toolbar-dynamictoolarea .create-ui-tool-freearea');
+ defaults.toolbar = 'halloToolbarFixed';
+ } else {
+ // Tools area minimized, use floating toolbar
+ defaults.parentElement = 'body';
+ defaults.toolbar = 'halloToolbarContextual';
+ }
+ return _.extend(defaults, this.options.editorOptions);
+ }
+ });
+})(jQuery);
+// Create.js - On-site web editing interface
+// (c) 2012 Henri Bergius, IKS Consortium
+// Create may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://createjs.org/
+(function (jQuery, undefined) {
+ // Run JavaScript in strict mode
+ /*global jQuery:false _:false document:false */
+ 'use strict';
+
+ // # Redactor editing widget
+ //
+ // This widget allows editing textual content areas with the
+ // [Redactor](http://redactorjs.com/) rich text editor.
+ jQuery.widget('Create.redactorWidget', jQuery.Create.editWidget, {
+ editor: null,
+
+ options: {
+ editorOptions: {},
+ disabled: true
+ },
+
+ enable: function () {
+ jQuery(this.element).redactor(this.getRedactorOptions());
+ this.options.disabled = false;
+ },
+
+ disable: function () {
+ jQuery(this.element).destroyEditor();
+ this.options.disabled = true;
+ },
+
+ _initialize: function () {
+ var self = this;
+ jQuery(this.element).bind('focus', function (event) {
+ self.options.activated();
+ });
+ /*
+ jQuery(this.element).bind('blur', function (event) {
+ self.options.deactivated();
+ });
+ */
+ },
+
+ getRedactorOptions: function () {
+ var self = this;
+ var overrides = {
+ keyupCallback: function (obj, event) {
+ self.options.changed(jQuery(self.element).getCode());
+ },
+ execCommandCallback: function (obj, command) {
+ self.options.changed(jQuery(self.element).getCode());
+ }
+ };
+
+ return _.extend(self.options.editorOptions, overrides);
+ }
+ });
+})(jQuery);
+// Create.js - On-site web editing interface
+// (c) 2011-2012 Henri Bergius, IKS Consortium
+// Create may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://createjs.org/
+(function (jQuery, undefined) {
+ // Run JavaScript in strict mode
+ /*global jQuery:false _:false window:false */
+ 'use strict';
+
+ jQuery.widget('Midgard.midgardStorage', {
+ saveEnabled: true,
+ options: {
+ // Whether to use localstorage
+ localStorage: false,
+ removeLocalstorageOnIgnore: true,
+ // VIE instance to use for storage handling
+ vie: null,
+ // URL callback for Backbone.sync
+ url: '',
+ // Whether to enable automatic saving
+ autoSave: false,
+ // How often to autosave in milliseconds
+ autoSaveInterval: 5000,
+ // Whether to save entities that are referenced by entities
+ // we're saving to the server.
+ saveReferencedNew: false,
+ saveReferencedChanged: false,
+ // Namespace used for events from midgardEditable-derived widget
+ editableNs: 'midgardeditable',
+ // CSS selector for the Edit button, leave to null to not bind
+ // notifications to any element
+ editSelector: '#midgardcreate-edit a',
+ localize: function (id, language) {
+ return window.midgardCreate.localize(id, language);
+ },
+ language: null
+ },
+
+ _create: function () {
+ var widget = this;
+ this.changedModels = [];
+
+ if (window.localStorage) {
+ this.options.localStorage = true;
+ }
+
+ this.vie = this.options.vie;
+
+ this.vie.entities.bind('add', function (model) {
+ // Add the back-end URL used by Backbone.sync
+ model.url = widget.options.url;
+ model.toJSON = model.toJSONLD;
+ });
+
+ widget._bindEditables();
+ if (widget.options.autoSave) {
+ widget._autoSave();
+ }
+ },
+
+ _autoSave: function () {
+ var widget = this;
+ widget.saveEnabled = true;
+
+ var doAutoSave = function () {
+ if (!widget.saveEnabled) {
+ return;
+ }
+
+ if (widget.changedModels.length === 0) {
+ return;
+ }
+
+ widget.saveRemoteAll({
+ // We make autosaves silent so that potential changes from server
+ // don't disrupt user while writing.
+ silent: true
+ });
+ };
+
+ var timeout = window.setInterval(doAutoSave, widget.options.autoSaveInterval);
+
+ this.element.bind('startPreventSave', function () {
+ if (timeout) {
+ window.clearInterval(timeout);
+ timeout = null;
+ }
+ widget.disableAutoSave();
+ });
+ this.element.bind('stopPreventSave', function () {
+ if (!timeout) {
+ timeout = window.setInterval(doAutoSave, widget.options.autoSaveInterval);
+ }
+ widget.enableAutoSave();
+ });
+
+ },
+
+ enableAutoSave: function () {
+ this.saveEnabled = true;
+ },
+
+ disableAutoSave: function () {
+ this.saveEnabled = false;
+ },
+
+ _bindEditables: function () {
+ var widget = this;
+ this.restorables = [];
+ var restorer;
+
+ widget.element.bind(widget.options.editableNs + 'changed', function (event, options) {
+ if (_.indexOf(widget.changedModels, options.instance) === -1) {
+ widget.changedModels.push(options.instance);
+ }
+ widget._saveLocal(options.instance);
+ });
+
+ widget.element.bind(widget.options.editableNs + 'disable', function (event, options) {
+ widget._restoreLocal(options.instance);
+ });
+
+ widget.element.bind(widget.options.editableNs + 'enable', function (event, options) {
+ if (!options.instance._originalAttributes) {
+ options.instance._originalAttributes = _.clone(options.instance.attributes);
+ }
+
+ if (!options.instance.isNew() && widget._checkLocal(options.instance)) {
+ // We have locally-stored modifications, user needs to be asked
+ widget.restorables.push(options.instance);
+ }
+
+ /*_.each(options.instance.attributes, function (attributeValue, property) {
+ if (attributeValue instanceof widget.vie.Collection) {
+ widget._readLocalReferences(options.instance, property, attributeValue);
+ }
+ });*/
+ });
+
+ widget.element.bind('midgardcreatestatechange', function (event, options) {
+ if (options.state === 'browse' || widget.restorables.length === 0) {
+ widget.restorables = [];
+ if (restorer) {
+ restorer.close();
+ }
+ return;
+ }
+ restorer = widget.checkRestore();
+ });
+
+ widget.element.bind('midgardstorageloaded', function (event, options) {
+ if (_.indexOf(widget.changedModels, options.instance) === -1) {
+ widget.changedModels.push(options.instance);
+ }
+ });
+ },
+
+ checkRestore: function () {
+ var widget = this;
+ if (widget.restorables.length === 0) {
+ return;
+ }
+
+ var message;
+ var restorer;
+ if (widget.restorables.length === 1) {
+ message = _.template(widget.options.localize('localModification', widget.options.language), {
+ label: widget.restorables[0].getSubjectUri()
+ });
+ } else {
+ message = _.template(widget.options.localize('localModifications', widget.options.language), {
+ number: widget.restorables.length
+ });
+ }
+
+ var doRestore = function (event, notification) {
+ widget.restoreLocal();
+ restorer.close();
+ };
+
+ var doIgnore = function (event, notification) {
+ widget.ignoreLocal();
+ restorer.close();
+ };
+
+ restorer = jQuery('body').midgardNotifications('create', {
+ bindTo: widget.options.editSelector,
+ gravity: 'TR',
+ body: message,
+ timeout: 0,
+ actions: [
+ {
+ name: 'restore',
+ label: widget.options.localize('Restore', widget.options.language),
+ cb: doRestore,
+ className: 'create-ui-btn'
+ },
+ {
+ name: 'ignore',
+ label: widget.options.localize('Ignore', widget.options.language),
+ cb: doIgnore,
+ className: 'create-ui-btn'
+ }
+ ],
+ callbacks: {
+ beforeShow: function () {
+ if (!window.Mousetrap) {
+ return;
+ }
+ window.Mousetrap.bind(['command+shift+r', 'ctrl+shift+r'], function (event) {
+ event.preventDefault();
+ doRestore();
+ });
+ window.Mousetrap.bind(['command+shift+i', 'ctrl+shift+i'], function (event) {
+ event.preventDefault();
+ doIgnore();
+ });
+ },
+ afterClose: function () {
+ if (!window.Mousetrap) {
+ return;
+ }
+ window.Mousetrap.unbind(['command+shift+r', 'ctrl+shift+r']);
+ window.Mousetrap.unbind(['command+shift+i', 'ctrl+shift+i']);
+ }
+ }
+ });
+ return restorer;
+ },
+
+ restoreLocal: function () {
+ _.each(this.restorables, function (instance) {
+ this.readLocal(instance);
+ }, this);
+ this.restorables = [];
+ },
+
+ ignoreLocal: function () {
+ if (this.options.removeLocalstorageOnIgnore) {
+ _.each(this.restorables, function (instance) {
+ this._removeLocal(instance);
+ }, this);
+ }
+ this.restorables = [];
+ },
+
+ saveReferences: function (model) {
+ _.each(model.attributes, function (value, property) {
+ if (!value || !value.isCollection) {
+ return;
+ }
+
+ value.each(function (referencedModel) {
+ if (this.changedModels.indexOf(referencedModel) !== -1) {
+ // The referenced model is already in the save queue
+ return;
+ }
+
+ if (referencedModel.isNew() && this.options.saveReferencedNew) {
+ return referencedModel.save();
+ }
+
+ if (referencedModel.hasChanged() && this.options.saveReferencedChanged) {
+ return referencedModel.save();
+ }
+ }, this);
+ }, this);
+ },
+
+ saveRemote: function (model, options) {
+ // Optionally handle entities referenced in this model first
+ this.saveReferences(model);
+
+ this._trigger('saveentity', null, {
+ entity: model,
+ options: options
+ });
+
+ var widget = this;
+ model.save(null, _.extend({}, options, {
+ success: function (m, response) {
+ // From now on we're going with the values we have on server
+ model._originalAttributes = _.clone(model.attributes);
+ widget._removeLocal(model);
+ window.setTimeout(function () {
+ // Remove the model from the list of changed models after saving
+ widget.changedModels.splice(widget.changedModels.indexOf(model), 1);
+ }, 0);
+ if (_.isFunction(options.success)) {
+ options.success(m, response);
+ }
+ widget._trigger('savedentity', null, {
+ entity: model,
+ options: options
+ });
+ },
+ error: function (m, response) {
+ if (_.isFunction(options.error)) {
+ options.error(m, response);
+ }
+ }
+ }));
+ },
+
+ saveRemoteAll: function (options) {
+ var widget = this;
+ if (widget.changedModels.length === 0) {
+ return;
+ }
+
+ widget._trigger('save', null, {
+ entities: widget.changedModels,
+ options: options,
+ // Deprecated
+ models: widget.changedModels
+ });
+
+ var notification_msg;
+ var needed = widget.changedModels.length;
+ if (needed > 1) {
+ notification_msg = _.template(widget.options.localize('saveSuccessMultiple', widget.options.language), {
+ number: needed
+ });
+ } else {
+ notification_msg = _.template(widget.options.localize('saveSuccess', widget.options.language), {
+ label: widget.changedModels[0].getSubjectUri()
+ });
+ }
+
+ widget.disableAutoSave();
+ _.each(widget.changedModels, function (model) {
+ this.saveRemote(model, {
+ success: function (m, response) {
+ needed--;
+ if (needed <= 0) {
+ // All models were happily saved
+ widget._trigger('saved', null, {
+ options: options
+ });
+ if (options && _.isFunction(options.success)) {
+ options.success(m, response);
+ }
+ jQuery('body').midgardNotifications('create', {
+ body: notification_msg
+ });
+ widget.enableAutoSave();
+ }
+ },
+ error: function (m, err) {
+ if (options && _.isFunction(options.error)) {
+ options.error(m, err);
+ }
+ jQuery('body').midgardNotifications('create', {
+ body: _.template(widget.options.localize('saveError', widget.options.language), {
+ error: err.responseText || ''
+ }),
+ timeout: 0
+ });
+
+ widget._trigger('error', null, {
+ instance: model
+ });
+ }
+ });
+ }, this);
+ },
+
+ _saveLocal: function (model) {
+ if (!this.options.localStorage) {
+ return;
+ }
+
+ if (model.isNew()) {
+ // Anonymous object, save as refs instead
+ if (!model.primaryCollection) {
+ return;
+ }
+ return this._saveLocalReferences(model.primaryCollection.subject, model.primaryCollection.predicate, model);
+ }
+ window.localStorage.setItem(model.getSubjectUri(), JSON.stringify(model.toJSONLD()));
+ },
+
+ _getReferenceId: function (model, property) {
+ return model.id + ':' + property;
+ },
+
+ _saveLocalReferences: function (subject, predicate, model) {
+ if (!this.options.localStorage) {
+ return;
+ }
+
+ if (!subject || !predicate) {
+ return;
+ }
+
+ var widget = this;
+ var identifier = subject + ':' + predicate;
+ var json = model.toJSONLD();
+ if (window.localStorage.getItem(identifier)) {
+ var referenceList = JSON.parse(window.localStorage.getItem(identifier));
+ var index = _.pluck(referenceList, '@').indexOf(json['@']);
+ if (index !== -1) {
+ referenceList[index] = json;
+ } else {
+ referenceList.push(json);
+ }
+ window.localStorage.setItem(identifier, JSON.stringify(referenceList));
+ return;
+ }
+ window.localStorage.setItem(identifier, JSON.stringify([json]));
+ },
+
+ _checkLocal: function (model) {
+ if (!this.options.localStorage) {
+ return false;
+ }
+
+ var local = window.localStorage.getItem(model.getSubjectUri());
+ if (!local) {
+ return false;
+ }
+
+ return true;
+ },
+
+ hasLocal: function (model) {
+ if (!this.options.localStorage) {
+ return false;
+ }
+
+ if (!window.localStorage.getItem(model.getSubjectUri())) {
+ return false;
+ }
+ return true;
+ },
+
+ readLocal: function (model) {
+ if (!this.options.localStorage) {
+ return;
+ }
+
+ var local = window.localStorage.getItem(model.getSubjectUri());
+ if (!local) {
+ return;
+ }
+ if (!model._originalAttributes) {
+ model._originalAttributes = _.clone(model.attributes);
+ }
+ var parsed = JSON.parse(local);
+ var entity = this.vie.entities.addOrUpdate(parsed, {
+ overrideAttributes: true
+ });
+
+ this._trigger('loaded', null, {
+ instance: entity
+ });
+ },
+
+ _readLocalReferences: function (model, property, collection) {
+ if (!this.options.localStorage) {
+ return;
+ }
+
+ var identifier = this._getReferenceId(model, property);
+ var local = window.localStorage.getItem(identifier);
+ if (!local) {
+ return;
+ }
+ collection.add(JSON.parse(local));
+ },
+
+ _restoreLocal: function (model) {
+ var widget = this;
+
+ // Remove unsaved collection members
+ if (!model) { return; }
+ _.each(model.attributes, function (attributeValue, property) {
+ if (attributeValue instanceof widget.vie.Collection) {
+ var removables = [];
+ attributeValue.forEach(function (model) {
+ if (model.isNew()) {
+ removables.push(model);
+ }
+ });
+ attributeValue.remove(removables);
+ }
+ });
+
+ // Restore original object properties
+ if (!model.changedAttributes()) {
+ if (model._originalAttributes) {
+ model.set(model._originalAttributes);
+ }
+ return;
+ }
+
+ model.set(model.previousAttributes());
+ },
+
+ _removeLocal: function (model) {
+ if (!this.options.localStorage) {
+ return;
+ }
+
+ window.localStorage.removeItem(model.getSubjectUri());
+ }
+ });
+})(jQuery);
+if (window.midgardCreate === undefined) {
+ window.midgardCreate = {};
+}
+if (window.midgardCreate.locale === undefined) {
+ window.midgardCreate.locale = {};
+}
+
+window.midgardCreate.locale.en = {
+ // Session-state buttons for the main toolbar
+ 'Save': 'Save',
+ 'Saving': 'Saving',
+ 'Cancel': 'Cancel',
+ 'Edit': 'Edit',
+ // Storage status messages
+ 'localModification': 'Item "<%= label %>" has local modifications',
+ 'localModifications': '<%= number %> items on this page have local modifications',
+ 'Restore': 'Restore',
+ 'Ignore': 'Ignore',
+ 'saveSuccess': 'Item "<%= label %>" saved successfully',
+ 'saveSuccessMultiple': '<%= number %> items saved successfully',
+ 'saveError': 'Error occurred while saving <%= error %>',
+ // Tagging
+ 'Item tags': 'Item tags',
+ 'Suggested tags': 'Suggested tags',
+ 'Tags': 'Tags',
+ 'add a tag': 'add a tag',
+ // Collection widgets
+ 'Add': 'Add',
+ 'Choose type to add': 'Choose type to add'
+};
diff --git a/core/misc/vie/vie-core.js b/core/misc/vie/vie-core.js
new file mode 100644
index 0000000..da0ae5e
--- /dev/null
+++ b/core/misc/vie/vie-core.js
@@ -0,0 +1,3682 @@
+/*Copyright (c) 2011 Henri Bergius, IKS Consortium
+Copyright (c) 2011 Sebastian Germesin, IKS Consortium
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+*/(function(){// VIE - Vienna IKS Editables
+// (c) 2011 Henri Bergius, IKS Consortium
+// (c) 2011 Sebastian Germesin, IKS Consortium
+// (c) 2011 Szaby Grünwald, IKS Consortium
+// VIE may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://viejs.org/
+
+/*global console:false exports:false require:false */
+
+var root = this,
+ jQuery = root.jQuery,
+ Backbone = root.Backbone,
+ _ = root._;
+
+
+// ## VIE constructor
+//
+// The VIE constructor is the way to initialize VIE for your
+// application. The instance of VIE handles all management of
+// semantic interaction, including keeping track of entities,
+// changes to them, the possible RDFa views on the page where
+// the entities are displayed, and connections to external
+// services like Stanbol and DBPedia.
+//
+// To get a VIE instance, simply run:
+//
+// var vie = new VIE();
+//
+// You can also pass configurations to the VIE instance through
+// the constructor. For example, to set a different default
+// namespace to be used for names that don't have a namespace
+// specified, do:
+//
+// var vie = new VIE({
+// baseNamespace: 'http://example.net'
+// });
+//
+// ### Differences with VIE 1.x
+//
+// VIE 1.x used singletons for managing entities and views loaded
+// from a page. This has been changed with VIE 2.x, and now all
+// data managed by VIE is tied to the instance of VIE being used.
+//
+// This means that VIE needs to be instantiated before using. So,
+// when previously you could get entities from page with:
+//
+// VIE.RDFaEntities.getInstances();
+//
+// Now you need to instantiate VIE first. This example uses the
+// Classic API compatibility layer instead of the `load` method:
+//
+// var vie = new VIE();
+// vie.RDFaEntities.getInstances();
+//
+// Currently the Classic API is enabled by default, but it is
+// recommended to ensure it is enabled before using it. So:
+//
+// var vie = new VIE({classic: true});
+// vie.RDFaEntities.getInstances();
+var VIE = root.VIE = function(config) {
+ this.config = (config) ? config : {};
+ this.services = {};
+ this.jQuery = jQuery;
+ this.entities = new this.Collection([], {
+ vie: this
+ });
+
+ this.Entity.prototype.entities = this.entities;
+ this.Entity.prototype.entityCollection = this.Collection;
+ this.Entity.prototype.vie = this;
+
+ this.Namespaces.prototype.vie = this;
+// ### Namespaces in VIE
+// VIE supports different ontologies and an easy use of them.
+// Namespace prefixes reduce the amount of code you have to
+// write. In VIE, it does not matter if you access an entitie's
+// property with
+// `entity.get('')` or
+// `entity.get('dbprop:capitalOf')` or even
+// `entity.get('capitalOf')` once the corresponding namespace
+// is registered as *baseNamespace*.
+// By default `"http://viejs.org/ns/"`is set as base namespace.
+// For more information about how to set, get and list all
+// registered namespaces, refer to the
+// Namespaces documentation.
+ this.namespaces = new this.Namespaces(
+ (this.config.baseNamespace) ? this.config.baseNamespace : "http://viejs.org/ns/",
+
+// By default, VIE is shipped with common namespace prefixes:
+
+// + owl : "http://www.w3.org/2002/07/owl#"
+// + rdfs : "http://www.w3.org/2000/01/rdf-schema#"
+// + rdf : "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+// + schema : 'http://schema.org/'
+// + foaf : 'http://xmlns.com/foaf/0.1/'
+// + geo : 'http://www.w3.org/2003/01/geo/wgs84_pos#'
+// + dbpedia: "http://dbpedia.org/ontology/"
+// + dbprop : "http://dbpedia.org/property/"
+// + skos : "http://www.w3.org/2004/02/skos/core#"
+// + xsd : "http://www.w3.org/2001/XMLSchema#"
+// + sioc : "http://rdfs.org/sioc/ns#"
+// + dcterms: "http://purl.org/dc/terms/"
+ {
+ owl : "http://www.w3.org/2002/07/owl#",
+ rdfs : "http://www.w3.org/2000/01/rdf-schema#",
+ rdf : "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+ schema : 'http://schema.org/',
+ foaf : 'http://xmlns.com/foaf/0.1/',
+ geo : 'http://www.w3.org/2003/01/geo/wgs84_pos#',
+ dbpedia: "http://dbpedia.org/ontology/",
+ dbprop : "http://dbpedia.org/property/",
+ skos : "http://www.w3.org/2004/02/skos/core#",
+ xsd : "http://www.w3.org/2001/XMLSchema#",
+ sioc : "http://rdfs.org/sioc/ns#",
+ dcterms: "http://purl.org/dc/terms/"
+ }
+ );
+
+
+ this.Type.prototype.vie = this;
+ this.Types.prototype.vie = this;
+ this.Attribute.prototype.vie = this;
+ this.Attributes.prototype.vie = this;
+// ### Type hierarchy in VIE
+// VIE takes care about type hierarchy of entities
+// (aka. *schema* or *ontology*).
+// Once a type hierarchy is known to VIE, we can leverage
+// this information, to easily ask, whether an entity
+// is of type, e.g., *foaf:Person* or *schema:Place*.
+// For more information about how to generate such a type
+// hierarchy, refer to the
+// Types documentation.
+ this.types = new this.Types();
+// By default, there is a parent type in VIE, called
+// *owl:Thing*. All types automatically inherit from this
+// type and all registered entities, are of this type.
+ this.types.add("owl:Thing");
+
+// As described above, the Classic API of VIE 1.x is loaded
+// by default. As this might change in the future, it is
+// recommended to ensure it is enabled before using it. So:
+//
+// var vie = new VIE({classic: true});
+// vie.RDFaEntities.getInstances();
+ if (this.config.classic === true) {
+ /* Load Classic API as well */
+ this.RDFa = new this.ClassicRDFa(this);
+ this.RDFaEntities = new this.ClassicRDFaEntities(this);
+ this.EntityManager = new this.ClassicEntityManager(this);
+
+ this.cleanup = function() {
+ this.entities.reset();
+ };
+ }
+};
+
+// ### use(service, name)
+// This method registers services within VIE.
+// **Parameters**:
+// *{string|object}* **service** The service to be registered.
+// *{string}* **name** An optional name to register the service with. If this
+// is not set, the default name that comes with the service is taken.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE}* : The current VIE instance.
+// **Example usage**:
+//
+// var vie = new VIE();
+// var conf1 = {...};
+// var conf2 = {...};
+// vie.use(new vie.StanbolService());
+// vie.use(new vie.StanbolService(conf1), "stanbol_1");
+// vie.use(new vie.StanbolService(conf2), "stanbol_2");
+// // <-- this means that there are now 3 services registered!
+VIE.prototype.use = function(service, name) {
+ if (!name && !service.name) {
+ throw new Error("Please provide a name for the service!");
+ }
+ service.vie = this;
+ service.name = (name)? name : service.name;
+ if (service.init) {
+ service.init();
+ }
+ this.services[service.name] = service;
+
+ return this;
+};
+
+// ### service(name)
+// This method returns the service object that is
+// registered under the given name.
+// **Parameters**:
+// *{string}* **name** ...
+// **Throws**:
+// *{Error}* if no service could be found.
+// **Returns**:
+// *{object}* : The service to be queried.
+// **Example usage**:
+//
+// var vie = new VIE();
+// vie.use(new vie.StanbolService(), "stanbol");
+// var service = vie.service("stanbol");
+VIE.prototype.service = function(name) {
+ if (!this.hasService(name)) {
+ throw "Undefined service " + name;
+ }
+ return this.services[name];
+};
+
+// ### hasService(name)
+// This method returns a boolean telling whether VIE has a particular
+// service loaded.
+// **Parameters**:
+// *{string}* **name**
+// **Returns**:
+// *{boolean}* whether service is available
+VIE.prototype.hasService = function(name) {
+ if (!this.services[name]) {
+ return false;
+ }
+ return true;
+};
+
+// ### getServicesArray()
+// This method returns an array of all registered services.
+// **Parameters**:
+// *nothing*
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{array}* : An array of service instances.
+// **Example usage**:
+//
+// var vie = new VIE();
+// vie.use(new vie.StanbolService(), "stanbol");
+// var services = vie.getServicesArray();
+// services.length; // <-- 1
+VIE.prototype.getServicesArray = function() {
+ return _.map(this.services, function (v) {return v;});
+};
+
+// ### load(options)
+// This method instantiates a new VIE.Loadable in order to
+// perform queries on the services.
+// **Parameters**:
+// *{object}* **options** Options to be set.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Loadable}* : A new instance of VIE.Loadable.
+// **Example usage**:
+//
+// var vie = new VIE();
+// vie.use(new vie.StanbolService(), "stanbol");
+// var loader = vie.load({...});
+VIE.prototype.load = function(options) {
+ if (!options) { options = {}; }
+ options.vie = this;
+ return new this.Loadable(options);
+};
+
+// ### save(options)
+// This method instantiates a new VIE.Savable in order to
+// perform queries on the services.
+// **Parameters**:
+// *{object}* **options** Options to be set.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Savable}* : A new instance of VIE.Savable.
+// **Example usage**:
+//
+// var vie = new VIE();
+// vie.use(new vie.StanbolService(), "stanbol");
+// var saver = vie.save({...});
+VIE.prototype.save = function(options) {
+ if (!options) { options = {}; }
+ options.vie = this;
+ return new this.Savable(options);
+};
+
+// ### remove(options)
+// This method instantiates a new VIE.Removable in order to
+// perform queries on the services.
+// **Parameters**:
+// *{object}* **options** Options to be set.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Removable}* : A new instance of VIE.Removable.
+// **Example usage**:
+//
+// var vie = new VIE();
+// vie.use(new vie.StanbolService(), "stanbol");
+// var remover = vie.remove({...});
+VIE.prototype.remove = function(options) {
+ if (!options) { options = {}; }
+ options.vie = this;
+ return new this.Removable(options);
+};
+
+// ### analyze(options)
+// This method instantiates a new VIE.Analyzable in order to
+// perform queries on the services.
+// **Parameters**:
+// *{object}* **options** Options to be set.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Analyzable}* : A new instance of VIE.Analyzable.
+// **Example usage**:
+//
+// var vie = new VIE();
+// vie.use(new vie.StanbolService(), "stanbol");
+// var analyzer = vie.analyze({...});
+VIE.prototype.analyze = function(options) {
+ if (!options) { options = {}; }
+ options.vie = this;
+ return new this.Analyzable(options);
+};
+
+// ### find(options)
+// This method instantiates a new VIE.Findable in order to
+// perform queries on the services.
+// **Parameters**:
+// *{object}* **options** Options to be set.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Findable}* : A new instance of VIE.Findable.
+// **Example usage**:
+//
+// var vie = new VIE();
+// vie.use(new vie.StanbolService(), "stanbol");
+// var finder = vie.find({...});
+VIE.prototype.find = function(options) {
+ if (!options) { options = {}; }
+ options.vie = this;
+ return new this.Findable(options);
+};
+
+// ### loadSchema(url, options)
+// VIE only knows the *owl:Thing* type by default.
+// You can use this method to import another
+// schema (ontology) from an external resource.
+// (Currently, this supports only the JSON format!!)
+// As this method works asynchronously, you might want
+// to register `success` and `error` callbacks via the
+// options.
+// **Parameters**:
+// *{string}* **url** The url, pointing to the schema to import.
+// *{object}* **options** Options to be set.
+// (Set ```success``` and ```error``` as callbacks.).
+// **Throws**:
+// *{Error}* if the url is not set.
+// **Returns**:
+// *{VIE}* : The VIE instance itself.
+// **Example usage**:
+//
+// var vie = new VIE();
+// vie.loadSchema("http://schema.rdfs.org/all.json",
+// {
+// baseNS : "http://schema.org/",
+// success : function () {console.log("success");},
+// error : function (msg) {console.warn(msg);}
+// });
+VIE.prototype.loadSchema = function(url, options) {
+ options = (!options)? {} : options;
+
+ if (!url) {
+ throw new Error("Please provide a proper URL");
+ }
+ else {
+ var vie = this;
+ jQuery.getJSON(url)
+ .success(function(data) {
+ try {
+ VIE.Util.loadSchemaOrg(vie, data, options.baseNS);
+ if (options.success) {
+ options.success.call(vie);
+ }
+ } catch (e) {
+ options.error.call(vie, e);
+ return;
+ }
+ })
+ .error(function(data, textStatus, jqXHR) {
+ if (options.error) {
+ console.warn(data, textStatus, jqXHR);
+ options.error.call(vie, "Could not load schema from URL (" + url + ")");
+ }
+ });
+ }
+
+ return this;
+};
+
+// ### getTypedEntityClass(type)
+// This method generates a special type of `Entity` based on the given type.
+// **Parameters**:
+// *{string}* **type** The type.
+// **Throws**:
+// *{Error}* if the type is unknown to VIE.
+// **Returns**:
+// *{VIE.Entity}* : A subclass of `VIE.Entity`.
+// **Example usage**:
+//
+// var vie = new VIE();
+// vie.types.add("Person");
+// var PersonClass = vie.getTypedEntityClass("Person");
+// var Person = new PersonClass({"name", "Sebastian"});
+VIE.prototype.getTypedEntityClass = function (type) {
+ var typeType = this.types.get(type);
+ if (!typeType) {
+ throw new Error("Unknown type " + type);
+ }
+ var TypedEntityClass = function (attrs, opts) {
+ if (!attrs) {
+ attrs = {};
+ }
+ attrs["@type"] = type;
+ this.set(attrs, opts);
+ };
+ TypedEntityClass.prototype = new this.Entity();
+ TypedEntityClass.prototype.schema = function () {
+ return VIE.Util.getFormSchemaForType(typeType);
+ };
+ return TypedEntityClass;
+};
+
+// ## Running VIE on Node.js
+//
+// When VIE is running under Node.js we can use the CommonJS
+// require interface to load our dependencies automatically.
+//
+// This means Node.js users don't need to care about dependencies
+// and can just run VIE with:
+//
+// var VIE = require('vie');
+//
+// In browser environments the dependencies have to be included
+// before including VIE itself.
+if (typeof exports === 'object') {
+ exports.VIE = VIE;
+
+ if (!jQuery) {
+ jQuery = require('jquery');
+ }
+ if (!Backbone) {
+ Backbone = require('backbone');
+ Backbone.setDomLibrary(jQuery);
+ }
+ if (!_) {
+ _ = require('underscore')._;
+ }
+}
+// VIE - Vienna IKS Editables
+// (c) 2011 Henri Bergius, IKS Consortium
+// (c) 2011 Sebastian Germesin, IKS Consortium
+// (c) 2011 Szaby Grünwald, IKS Consortium
+// VIE may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://viejs.org/
+
+// ## VIE.Able
+// VIE implements asynchronius service methods through
+// [jQuery.Deferred](http://api.jquery.com/category/deferred-object/) objects.
+// Loadable, Analysable, Savable, etc. are part of the VIE service API and
+// are implemented with the generic VIE.Able class.
+// Example:
+//
+// VIE.prototype.Loadable = function (options) {
+// this.init(options,"load");
+// };
+// VIE.prototype.Loadable.prototype = new VIE.prototype.Able();
+//
+// This defines
+//
+// someVIEService.load(options)
+// .using(...)
+// .execute()
+// .success(...)
+// .fail(...)
+// which will run the asynchronius `load` function of the service with the created Loadable
+// object.
+
+// ### VIE.Able()
+// This is the constructor of a VIE.Able. This should not be called
+// globally but using the inherited classes below.
+// **Parameters**:
+// *nothing*
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Able}* : A **new** VIE.Able object.
+// Example:
+//
+// VIE.prototype.Loadable = function (options) {
+// this.init(options,"load");
+// };
+// VIE.prototype.Loadable.prototype = new VIE.prototype.Able();
+VIE.prototype.Able = function(){
+
+// ### init(options, methodName)
+// Internal method, called during initialization.
+// **Parameters**:
+// *{object}* **options** the *able* options coming from the API call
+// *{string}* **methodName** the service method called on `.execute`.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Able}* : The current instance.
+// **Example usage**:
+//
+// VIE.prototype.Loadable = function (options) {
+// this.init(options,"load");
+// };
+// VIE.prototype.Loadable.prototype = new VIE.prototype.Able();
+ this.init = function(options, methodName) {
+ this.options = options;
+ this.services = options.from || options.using || options.to || [];
+ this.vie = options.vie;
+
+ this.methodName = methodName;
+
+ // Instantiate the deferred object
+ this.deferred = jQuery.Deferred();
+
+// In order to get more information and documentation about the passed-through
+// deferred methods and their synonyms, please see the documentation of
+// the [jQuery.Deferred object](http://api.jquery.com/category/deferred-object/)
+ /* Public deferred-methods */
+ this.resolve = this.deferred.resolve;
+ this.resolveWith = this.deferred.resolveWith;
+ this.reject = this.deferred.reject;
+ this.rejectWith = this.deferred.rejectWith;
+ this.success = this.done = this.deferred.done;
+ this.fail = this.deferred.fail;
+ this.then = this.deferred.then;
+ this.always = this.deferred.always;
+ this.from = this.using;
+ this.to = this.using;
+
+ return this;
+ };
+
+
+// ### using(services)
+// This method registers services with the current able instance.
+// **Parameters**:
+// *{string|array}* **services** An id of a service or an array of strings.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Able}* : The current instance.
+// **Example usage**:
+//
+// var loadable = vie.load({id: "http://example.com/entity/1234"});
+// able.using("myService");
+ this.using = function(services) {
+ var self = this;
+ services = (_.isArray(services))? services : [ services ];
+ _.each (services, function (s) {
+ var obj = (typeof s === "string")? self.vie.service(s) : s;
+ self.services.push(obj);
+ });
+ return this;
+ };
+
+// ### execute()
+// This method runs the actual method on all registered services.
+// **Parameters**:
+// *nothing*
+// **Throws**:
+// *nothing* ...
+// **Returns**:
+// *{VIE.Able}* : The current instance.
+// **Example usage**:
+//
+// var able = new vie.Able().init();
+// able.using("stanbol")
+// .done(function () {alert("finished");})
+// .execute();
+ this.execute = function() {
+ /* call service[methodName] */
+ var able = this;
+ _(this.services).each(function(service){
+ service[able.methodName](able);
+ });
+ return this;
+ };
+};
+
+// ## VIE.Loadable
+// A ```VIE.Loadable``` is a wrapper around the deferred object
+// to **load** semantic data from a semantic web service.
+VIE.prototype.Loadable = function (options) {
+ this.init(options,"load");
+};
+VIE.prototype.Loadable.prototype = new VIE.prototype.Able();
+
+// ## VIE.Savable
+// A ```VIE.Savable``` is a wrapper around the deferred object
+// to **save** entities by a VIE service. The RDFaService would write the data
+// in the HTML as RDFa, the StanbolService stores the data in its Entityhub, etc.
+VIE.prototype.Savable = function(options){
+ this.init(options, "save");
+};
+VIE.prototype.Savable.prototype = new VIE.prototype.Able();
+
+// ## VIE.Removable
+// A ```VIE.Removable``` is a wrapper around the deferred object
+// to **remove** semantic data from a semantic web service.
+VIE.prototype.Removable = function(options){
+ this.init(options, "remove");
+};
+VIE.prototype.Removable.prototype = new VIE.prototype.Able();
+
+// ## VIE.Analyzable
+// A ```VIE.Analyzable``` is a wrapper around the deferred object
+// to **analyze** data and extract semantic information with the
+// help of a semantic web service.
+VIE.prototype.Analyzable = function (options) {
+ this.init(options, "analyze");
+};
+VIE.prototype.Analyzable.prototype = new VIE.prototype.Able();
+
+// ## VIE.Findable
+// A ```VIE.Findable``` is a wrapper around the deferred object
+// to **find** semantic data on a semantic storage.
+VIE.prototype.Findable = function (options) {
+ this.init(options, "find");
+};
+VIE.prototype.Findable.prototype = new VIE.prototype.Able();
+
+// VIE - Vienna IKS Editables
+// (c) 2011 Henri Bergius, IKS Consortium
+// (c) 2011 Sebastian Germesin, IKS Consortium
+// (c) 2011 Szaby Grünwald, IKS Consortium
+// VIE may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://viejs.org/
+
+// ## VIE Utils
+//
+// The here-listed methods are utility methods for the day-to-day
+// VIE.js usage. All methods are within the static namespace ```VIE.Util```.
+VIE.Util = {
+
+// ### VIE.Util.toCurie(uri, safe, namespaces)
+// This method converts a given
+// URI into a CURIE (or SCURIE), based on the given ```VIE.Namespaces``` object.
+// If the given uri is already a URI, it is left untouched and directly returned.
+// If no prefix could be found, an ```Error``` is thrown.
+// **Parameters**:
+// *{string}* **uri** The URI to be transformed.
+// *{boolean}* **safe** A flag whether to generate CURIEs or SCURIEs.
+// *{VIE.Namespaces}* **namespaces** The namespaces to be used for the prefixes.
+// **Throws**:
+// *{Error}* If no prefix could be found in the passed namespaces.
+// **Returns**:
+// *{string}* The CURIE or SCURIE.
+// **Example usage**:
+//
+// var ns = new myVIE.Namespaces(
+// "http://viejs.org/ns/",
+// { "dbp": "http://dbpedia.org/ontology/" }
+// );
+// var uri = "";
+// VIE.Util.toCurie(uri, false, ns); // --> dbp:Person
+// VIE.Util.toCurie(uri, true, ns); // --> [dbp:Person]
+ toCurie : function (uri, safe, namespaces) {
+ if (VIE.Util.isCurie(uri, namespaces)) {
+ return uri;
+ }
+ var delim = ":";
+ for (var k in namespaces.toObj()) {
+ if (uri.indexOf(namespaces.get(k)) === 1) {
+ var pattern = new RegExp("^" + "" + namespaces.get(k));
+ if (k === '') {
+ delim = '';
+ }
+ return ((safe)? "[" : "") +
+ uri.replace(pattern, k + delim).replace(/>$/, '') +
+ ((safe)? "]" : "");
+ }
+ }
+ throw new Error("No prefix found for URI '" + uri + "'!");
+ },
+
+// ### VIE.Util.isCurie(curie, namespaces)
+// This method checks, whether
+// the given string is a CURIE and returns ```true``` if so and ```false```otherwise.
+// **Parameters**:
+// *{string}* **curie** The CURIE (or SCURIE) to be checked.
+// *{VIE.Namespaces}* **namespaces** The namespaces to be used for the prefixes.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{boolean}* ```true``` if the given curie is a CURIE or SCURIE and ```false``` otherwise.
+// **Example usage**:
+//
+// var ns = new myVIE.Namespaces(
+// "http://viejs.org/ns/",
+// { "dbp": "http://dbpedia.org/ontology/" }
+// );
+// var uri = "";
+// var curie = "dbp:Person";
+// var scurie = "[dbp:Person]";
+// var text = "This is some text.";
+// VIE.Util.isCurie(uri, ns); // --> false
+// VIE.Util.isCurie(curie, ns); // --> true
+// VIE.Util.isCurie(scurie, ns); // --> true
+// VIE.Util.isCurie(text, ns); // --> false
+ isCurie : function (curie, namespaces) {
+ if (VIE.Util.isUri(curie)) {
+ return false;
+ } else {
+ try {
+ VIE.Util.toUri(curie, namespaces);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+ },
+
+// ### VIE.Util.toUri(curie, namespaces)
+// This method converts a
+// given CURIE (or save CURIE) into a URI, based on the given ```VIE.Namespaces``` object.
+// **Parameters**:
+// *{string}* **curie** The CURIE to be transformed.
+// *{VIE.Namespaces}* **namespaces** The namespaces object
+// **Throws**:
+// *{Error}* If no URI could be assembled.
+// **Returns**:
+// *{string}* : A string, representing the URI.
+// **Example usage**:
+//
+// var ns = new myVIE.Namespaces(
+// "http://viejs.org/ns/",
+// { "dbp": "http://dbpedia.org/ontology/" }
+// );
+// var curie = "dbp:Person";
+// var scurie = "[dbp:Person]";
+// VIE.Util.toUri(curie, ns);
+// -->
+// VIE.Util.toUri(scurie, ns);
+// -->
+ toUri : function (curie, namespaces) {
+ if (VIE.Util.isUri(curie)) {
+ return curie;
+ }
+ var delim = ":";
+ for (var prefix in namespaces.toObj()) {
+ if (prefix !== "" && (curie.indexOf(prefix + ":") === 0 || curie.indexOf("[" + prefix + ":") === 0)) {
+ var pattern = new RegExp("^" + "\\[{0,1}" + prefix + delim);
+ return "<" + curie.replace(pattern, namespaces.get(prefix)).replace(/\]{0,1}$/, '') + ">";
+ }
+ }
+ /* check for the default namespace */
+ if (curie.indexOf(delim) === -1) {
+ return "<" + namespaces.base() + curie + ">";
+ }
+ throw new Error("No prefix found for CURIE '" + curie + "'!");
+ },
+
+// ### VIE.Util.isUri(something)
+// This method checks, whether the given string is a URI.
+// **Parameters**:
+// *{string}* **something** : The string to be checked.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{boolean}* : ```true``` if the string is a URI, ```false``` otherwise.
+// **Example usage**:
+//
+// var uri = "";
+// var curie = "dbp:Person";
+// VIE.Util.isUri(uri); // --> true
+// VIE.Util.isUri(curie); // --> false
+ isUri : function (something) {
+ return (typeof something === "string" && something.search(/^<.+>$/) === 0);
+ },
+
+// ### VIE.Util.mapAttributeNS(attr, ns)
+// This method maps an attribute of an entity into namespaces if they have CURIEs.
+// **Parameters**:
+// *{string}* **attr** : The attribute to be transformed.
+// *{VIE.Namespaces}* **ns** : The namespaces.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{string}* : The transformed attribute's name.
+// **Example usage**:
+//
+// var attr = "name";
+// var ns = myVIE.namespaces;
+// VIE.Util.mapAttributeNS(attr, ns); // '<' + ns.base() + attr + '>';
+ mapAttributeNS : function (attr, ns) {
+ var a = attr;
+ if (ns.isUri (attr) || attr.indexOf('@') === 0) {
+ //ignore
+ } else if (ns.isCurie(attr)) {
+ a = ns.uri(attr);
+ } else if (!ns.isUri(attr)) {
+ if (attr.indexOf(":") === -1) {
+ a = '<' + ns.base() + attr + '>';
+ } else {
+ a = '<' + attr + '>';
+ }
+ }
+ return a;
+ },
+
+// ### VIE.Util.rdf2Entities(service, results)
+// This method converts *rdf/json* data from an external service
+// into VIE.Entities.
+// **Parameters**:
+// *{object}* **service** The service that retrieved the data.
+// *{object}* **results** The data to be transformed.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{[VIE.Entity]}* : An array, containing VIE.Entity instances which have been transformed from the given data.
+ rdf2Entities: function (service, results) {
+ if (typeof jQuery.rdf !== 'function') {
+ /* fallback if no rdfQuery has been loaded */
+ return VIE.Util._rdf2EntitiesNoRdfQuery(service, results);
+ }
+ try {
+ var rdf = (results instanceof jQuery.rdf)?
+ results.base(service.vie.namespaces.base()) :
+ jQuery.rdf().base(service.vie.namespaces.base()).load(results, {});
+
+ /* if the service contains rules to apply special transformation, they are executed here.*/
+ if (service.rules) {
+ var rules = jQuery.rdf.ruleset();
+ for (var prefix in service.vie.namespaces.toObj()) {
+ if (prefix !== "") {
+ rules.prefix(prefix, service.vie.namespaces.get(prefix));
+ }
+ }
+ for (var i = 0; i < service.rules.length; i++)if(service.rules.hasOwnProperty(i)) {
+ var rule = service.rules[i];
+ rules.add(rule.left, rule.right);
+ }
+ rdf = rdf.reason(rules, 10); /* execute the rules only 10 times to avoid looping */
+ }
+ var entities = {};
+ rdf.where('?subject ?property ?object').each(function() {
+ var subject = this.subject.toString();
+ if (!entities[subject]) {
+ entities[subject] = {
+ '@subject': subject,
+ '@context': service.vie.namespaces.toObj(true),
+ '@type': []
+ };
+ }
+ var propertyUri = this.property.toString();
+ var propertyCurie;
+
+ try {
+ propertyCurie = service.vie.namespaces.curie(propertyUri);
+ //jQuery.createCurie(propertyUri, {namespaces: service.vie.namespaces.toObj(true)});
+ } catch (e) {
+ propertyCurie = propertyUri;
+ // console.warn(propertyUri + " doesn't have a namespace definition in '", service.vie.namespaces.toObj());
+ }
+ entities[subject][propertyCurie] = entities[subject][propertyCurie] || [];
+
+ function getValue(rdfQueryLiteral){
+ if(typeof rdfQueryLiteral.value === "string"){
+ if (rdfQueryLiteral.lang){
+ var literal = {
+ toString: function(){
+ return this["@value"];
+ },
+ "@value": rdfQueryLiteral.value.replace(/^"|"$/g, ''),
+ "@language": rdfQueryLiteral.lang
+ };
+ return literal;
+ }
+ else
+ return rdfQueryLiteral.value;
+ return rdfQueryLiteral.value.toString();
+ } else if (rdfQueryLiteral.type === "uri"){
+ return rdfQueryLiteral.toString();
+ } else {
+ return rdfQueryLiteral.value;
+ }
+ }
+ entities[subject][propertyCurie].push(getValue(this.object));
+ });
+
+ _(entities).each(function(ent){
+ ent["@type"] = ent["@type"].concat(ent["rdf:type"]);
+ delete ent["rdf:type"];
+ _(ent).each(function(value, property){
+ if(value.length === 1){
+ ent[property] = value[0];
+ }
+ });
+ });
+
+ var vieEntities = [];
+ jQuery.each(entities, function() {
+ var entityInstance = new service.vie.Entity(this);
+ entityInstance = service.vie.entities.addOrUpdate(entityInstance);
+ vieEntities.push(entityInstance);
+ });
+ return vieEntities;
+ } catch (e) {
+ console.warn("Something went wrong while parsing the returned results!", e);
+ return [];
+ }
+ },
+
+ /*
+ VIE.Util.getPreferredLangForPreferredProperty(entity, preferredFields, preferredLanguages)
+ looks for specific ranking fields and languages. It calculates all possibilities and gives them
+ a score. It returns the value with the best score.
+ */
+ getPreferredLangForPreferredProperty: function(entity, preferredFields, preferredLanguages) {
+ var l, labelArr, lang, p, property, resArr, valueArr, _len, _len2,
+ _this = this;
+ resArr = [];
+ /* Try to find a label in the preferred language
+ */
+ _.each(preferredLanguages, function (lang) {
+ _.each(preferredFields, function (property) {
+ labelArr = null;
+ /* property can be a string e.g. "skos:prefLabel"
+ */
+ if (typeof property === "string" && entity.get(property)) {
+ labelArr = _.flatten([entity.get(property)]);
+ _(labelArr).each(function(label) {
+ /*
+ The score is a natural number with 0 for the
+ best candidate with the first preferred language
+ and first preferred property
+ */
+ var labelLang, score, value;
+ score = p;
+ labelLang = label["@language"];
+ /*
+ legacy code for compatibility with uotdated stanbol,
+ to be removed after may 2012
+ */
+ if (typeof label === "string" && (label.indexOf("@") === label.length - 3 || label.indexOf("@") === label.length - 5)) {
+ labelLang = label.replace(/(^\"*|\"*@)..(..)?$/g, "");
+ }
+ /* end of legacy code
+ */
+ if (labelLang) {
+ if (labelLang === lang) {
+ score += l;
+ } else {
+ score += 20;
+ }
+ } else {
+ score += 10;
+ }
+ value = label.toString();
+ /* legacy code for compatibility with uotdated stanbol, to be removed after may 2012
+ */
+ value = value.replace(/(^\"*|\"*@..$)/g, "");
+ /* end of legacy code
+ */
+ return resArr.push({
+ score: score,
+ value: value
+ });
+ });
+ /*
+ property can be an object like
+ {
+ property: "skos:broader",
+ makeLabel: function(propertyValueArr) { return "..."; }
+ }
+ */
+ } else if (typeof property === "object" && entity.get(property.property)) {
+ valueArr = _.flatten([entity.get(property.property)]);
+ valueArr = _(valueArr).map(function(termUri) {
+ if (termUri.isEntity) {
+ return termUri.getSubject();
+ } else {
+ return termUri;
+ }
+ });
+ resArr.push({
+ score: p,
+ value: property.makeLabel(valueArr)
+ });
+ }
+ });
+ });
+ /*
+ take the result with the best score
+ */
+ resArr = _(resArr).sortBy(function(a) {
+ return a.score;
+ });
+ if(resArr.length) {
+ return resArr[0].value;
+ } else {
+ return "n/a";
+ }
+ },
+
+
+// ### VIE.Util._rdf2EntitiesNoRdfQuery(service, results)
+// This is a **private** method which should
+// only be accessed through ```VIE.Util._rdf2Entities()``` and is a helper method in case there is no
+// rdfQuery loaded (*not recommended*).
+// **Parameters**:
+// *{object}* **service** The service that retrieved the data.
+// *{object}* **results** The data to be transformed.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{[VIE.Entity]}* : An array, containing VIE.Entity instances which have been transformed from the given data.
+ _rdf2EntitiesNoRdfQuery: function (service, results) {
+ var jsonLD = [];
+ _.forEach(results, function(value, key) {
+ var entity = {};
+ entity['@subject'] = '<' + key + '>';
+ _.forEach(value, function(triples, predicate) {
+ predicate = '<' + predicate + '>';
+ _.forEach(triples, function(triple) {
+ if (triple.type === 'uri') {
+ triple.value = '<' + triple.value + '>';
+ }
+
+ if (entity[predicate] && !_.isArray(entity[predicate])) {
+ entity[predicate] = [entity[predicate]];
+ }
+
+ if (_.isArray(entity[predicate])) {
+ entity[predicate].push(triple.value);
+ return;
+ }
+ entity[predicate] = triple.value;
+ });
+ });
+ jsonLD.push(entity);
+ });
+ return jsonLD;
+ },
+
+// ### VIE.Util.loadSchemaOrg(vie, SchemaOrg, baseNS)
+// This method is a wrapper around
+// the schema.org ontology. It adds all the
+// given types and properties as ```VIE.Type``` instances to the given VIE instance.
+// If the paramenter **baseNS** is set, the method automatically sets the namespace
+// to the provided one. If it is not set, it will keep the base namespace of VIE untouched.
+// **Parameters**:
+// *{VIE}* **vie** The instance of ```VIE```.
+// *{object}* **SchemaOrg** The data imported from schema.org.
+// *{string|undefined}* **baseNS** If set, this will become the new baseNamespace within the given ```VIE``` instance.
+// **Throws**:
+// *{Error}* If the parameter was not given.
+// **Returns**:
+// *nothing*
+ loadSchemaOrg : function (vie, SchemaOrg, baseNS) {
+
+ if (!SchemaOrg) {
+ throw new Error("Please load the schema.json file.");
+ }
+ vie.types.remove("");
+
+ var baseNSBefore = (baseNS)? baseNS : vie.namespaces.base();
+ vie.namespaces.base(baseNS);
+
+ var datatypeMapping = {
+ 'DataType': 'xsd:anyType',
+ 'Boolean' : 'xsd:boolean',
+ 'Date' : 'xsd:date',
+ 'DateTime': 'xsd:dateTime',
+ 'Time' : 'xsd:time',
+ 'Float' : 'xsd:float',
+ 'Integer' : 'xsd:integer',
+ 'Number' : 'xsd:anySimpleType',
+ 'Text' : 'xsd:string',
+ 'URL' : 'xsd:anyURI'
+ };
+
+ var dataTypeHelper = function (ancestors, id) {
+ var type = vie.types.add(id, [{'id' : 'value', 'range' : datatypeMapping[id]}]);
+
+ for (var i = 0; i < ancestors.length; i++) {
+ var supertype = (vie.types.get(ancestors[i]))? vie.types.get(ancestors[i]) :
+ dataTypeHelper.call(vie, SchemaOrg.datatypes[ancestors[i]].supertypes, ancestors[i]);
+ type.inherit(supertype);
+ }
+ return type;
+ };
+
+ for (var dt in SchemaOrg.datatypes) {
+ if (!vie.types.get(dt)) {
+ var ancestors = SchemaOrg.datatypes[dt].supertypes;
+ dataTypeHelper.call(vie, ancestors, dt);
+ }
+ }
+
+ var metadataHelper = function (definition) {
+ var metadata = {};
+
+ if (definition.label) {
+ metadata.label = definition.label;
+ }
+
+ if (definition.url) {
+ metadata.url = definition.url;
+ }
+
+ if (definition.comment) {
+ metadata.comment = definition.comment;
+ }
+
+ if (definition.metadata) {
+ metadata = _.extend(metadata, definition.metadata);
+ }
+ return metadata;
+ };
+
+ var typeProps = function (id) {
+ var props = [];
+ _.each(SchemaOrg.types[id].specific_properties, function (pId) {
+ var property = SchemaOrg.properties[pId];
+ props.push({
+ 'id' : property.id,
+ 'range' : property.ranges,
+ 'min' : property.min,
+ 'max' : property.max,
+ 'metadata': metadataHelper(property)
+ });
+ });
+ return props;
+ };
+
+ var typeHelper = function (ancestors, id, props, metadata) {
+ var type = vie.types.add(id, props, metadata);
+
+ for (var i = 0; i < ancestors.length; i++) {
+ var supertype = (vie.types.get(ancestors[i]))? vie.types.get(ancestors[i]) :
+ typeHelper.call(vie, SchemaOrg.types[ancestors[i]].supertypes, ancestors[i], typeProps.call(vie, ancestors[i]));
+ type.inherit(supertype);
+ }
+ if (id === "Thing" && !type.isof("owl:Thing")) {
+ type.inherit("owl:Thing");
+ }
+ return type;
+ };
+
+ _.each(SchemaOrg.types, function (typeDef) {
+ if (vie.types.get(typeDef.id)) {
+ return;
+ }
+ var ancestors = typeDef.supertypes;
+ var metadata = metadataHelper(typeDef);
+ typeHelper.call(vie, ancestors, typeDef.id, typeProps.call(vie, typeDef.id), metadata);
+ });
+
+ /* set the namespace to either the old value or the provided baseNS value */
+ vie.namespaces.base(baseNSBefore);
+ },
+
+// ### VIE.Util.getEntityTypeUnion(entity)
+// This generates a entity-specific VIE type that is a subtype of all the
+// types of the entity. This makes it easier to deal with attribute definitions
+// specific to an entity because they're merged to a single list. This custom
+// type is transient, meaning that it won't be automatilly added to the entity
+// or the VIE type registry.
+ getEntityTypeUnion : function(entity) {
+ var vie = entity.vie;
+ return new vie.Type('Union').inherit(entity.get('@type'));
+ },
+
+// ### VIE.Util.getFormSchemaForType(type)
+// This creates a [Backbone Forms](https://github.com/powmedia/backbone-forms)
+// -compatible form schema for any VIE Type.
+ getFormSchemaForType : function(type, allowNested) {
+ var schema = {};
+
+ // Generate a schema
+ _.each(type.attributes.toArray(), function (attribute) {
+ var key = VIE.Util.toCurie(attribute.id, false, attribute.vie.namespaces);
+ schema[key] = VIE.Util.getFormSchemaForAttribute(attribute);
+ });
+
+ // Clean up unknown attribute types
+ _.each(schema, function (field, id) {
+ if (!field.type) {
+ delete schema[id];
+ }
+
+ if (field.type === 'URL') {
+ field.type = 'Text';
+ field.dataType = 'url';
+ }
+
+ if (field.type === 'List' && !field.listType) {
+ delete schema[id];
+ }
+
+ if (!allowNested) {
+ if (field.type === 'NestedModel' || field.listType === 'NestedModel') {
+ delete schema[id];
+ }
+ }
+ });
+
+ return schema;
+ },
+
+/// ### VIE.Util.getFormSchemaForAttribute(attribute)
+ getFormSchemaForAttribute : function(attribute) {
+ var primaryType = attribute.range[0];
+ var schema = {};
+
+ var getWidgetForType = function (type) {
+ switch (type) {
+ case 'xsd:anySimpleType':
+ case 'xsd:float':
+ case 'xsd:integer':
+ return 'Number';
+ case 'xsd:string':
+ return 'Text';
+ case 'xsd:date':
+ return 'Date';
+ case 'xsd:dateTime':
+ return 'DateTime';
+ case 'xsd:boolean':
+ return 'Checkbox';
+ case 'xsd:anyURI':
+ return 'URL';
+ default:
+ var typeType = attribute.vie.types.get(type);
+ if (!typeType) {
+ return null;
+ }
+ if (typeType.attributes.get('value')) {
+ // Convert to proper xsd type
+ return getWidgetForType(typeType.attributes.get('value').range[0]);
+ }
+ return 'NestedModel';
+ }
+ };
+
+ // TODO: Generate a nicer label
+ schema.title = VIE.Util.toCurie(attribute.id, false, attribute.vie.namespaces);
+
+ // TODO: Handle attributes linking to other VIE entities
+
+ if (attribute.min > 0) {
+ schema.validators = ['required'];
+ }
+
+ if (attribute.max > 1) {
+ schema.type = 'List';
+ schema.listType = getWidgetForType(primaryType);
+ if (schema.listType === 'NestedModel') {
+ schema.nestedModelType = primaryType;
+ }
+ return schema;
+ }
+
+ schema.type = getWidgetForType(primaryType);
+ if (schema.type === 'NestedModel') {
+ schema.nestedModelType = primaryType;
+ }
+ return schema;
+ },
+
+// ### VIE.Util.getFormSchema(entity)
+// This creates a [Backbone Forms](https://github.com/powmedia/backbone-forms)
+// -compatible form schema for any VIE Entity. The form schema creation
+// utilizes type information attached to the entity.
+// **Parameters**:
+// *{```Entity```}* **entity** An instance of VIE ```Entity```.
+// **Throws**:
+// *nothing*..
+// **Returns**:
+// *{object}* a JavaScript object representation of the form schema
+ getFormSchema : function(entity) {
+ if (!entity || !entity.isEntity) {
+ return {};
+ }
+
+ var unionType = VIE.Util.getEntityTypeUnion(entity);
+ var schema = VIE.Util.getFormSchemaForType(unionType, true);
+
+ // Handle nested models
+ _.each(schema, function (property, id) {
+ if (property.type !== 'NestedModel' && property.listType !== 'NestedModel') {
+ return;
+ }
+ schema[id].model = entity.vie.getTypedEntityClass(property.nestedModelType);
+ });
+
+ return schema;
+ },
+
+// ### VIE.Util.xsdDateTime(date)
+// This transforms a ```Date``` instance into an xsd:DateTime format.
+// **Parameters**:
+// *{```Date```}* **date** An instance of a javascript ```Date```.
+// **Throws**:
+// *nothing*..
+// **Returns**:
+// *{string}* A string representation of the dateTime in the xsd:dateTime format.
+ xsdDateTime : function(date) {
+ function pad(n) {
+ var s = n.toString();
+ return s.length < 2 ? '0'+s : s;
+ }
+
+ var yyyy = date.getFullYear();
+ var mm1 = pad(date.getMonth()+1);
+ var dd = pad(date.getDate());
+ var hh = pad(date.getHours());
+ var mm2 = pad(date.getMinutes());
+ var ss = pad(date.getSeconds());
+
+ return yyyy +'-' +mm1 +'-' +dd +'T' +hh +':' +mm2 +':' +ss;
+ },
+
+// ### VIE.Util.extractLanguageString(entity, attrs, langs)
+// This method extracts a literal string from an entity, searching through the given attributes and languages.
+// **Parameters**:
+// *{```VIE.Entity```}* **entity** An instance of a VIE.Entity.
+// *{```array|string```}* **attrs** Either a string or an array of possible attributes.
+// *{```array|string```}* **langs** Either a string or an array of possible languages.
+// **Throws**:
+// *nothing*..
+// **Returns**:
+// *{string|undefined}* The string that was found at the attribute with the wanted language, undefined if nothing could be found.
+// **Example usage**:
+//
+// var attrs = ["name", "rdfs:label"];
+// var langs = ["en", "de"];
+// VIE.Util.extractLanguageString(someEntity, attrs, langs); // "Barack Obama";
+ extractLanguageString : function(entity, attrs, langs) {
+ var p, attr, name, i, n;
+ if (entity && typeof entity !== "string") {
+ attrs = (_.isArray(attrs))? attrs : [ attrs ];
+ langs = (_.isArray(langs))? langs : [ langs ];
+ for (p = 0; p < attrs.length; p++) {
+ for (var l = 0; l < langs.length; l++) {
+ var lang = langs[l];
+ attr = attrs[p];
+ if (entity.has(attr)) {
+ name = entity.get(attr);
+ name = (_.isArray(name))? name : [ name ];
+ for (i = 0; i < name.length; i++) {
+ n = name[i];
+ if (n.isEntity) {
+ n = VIE.Util.extractLanguageString(n, attrs, lang);
+ } else if (typeof n === "string") {
+ n = n;
+ } else {
+ n = "";
+ }
+ if (n && n.indexOf('@' + lang) > -1) {
+ return n.replace(/"/g, "").replace(/@[a-z]+/, '').trim();
+ }
+ }
+ }
+ }
+ }
+ /* let's do this again in case we haven't found a name but are dealing with
+ broken data where no language is given */
+ for (p = 0; p < attrs.length; p++) {
+ attr = attrs[p];
+ if (entity.has(attr)) {
+ name = entity.get(attr);
+ name = (_.isArray(name))? name : [ name ];
+ for (i = 0; i < name.length; i++) {
+ n = name[i];
+ if (n.isEntity) {
+ n = VIE.Util.extractLanguageString(n, attrs, []);
+ }
+ if (n && (typeof n === "string") && n.indexOf('@') === -1) {
+ return n.replace(/"/g, "").replace(/@[a-z]+/, '').trim();
+ }
+ }
+ }
+ }
+ }
+ return undefined;
+ },
+
+// ### VIE.Util.transformationRules(service)
+// This returns a default set of rdfQuery rules that transform semantic data into the
+// VIE entity types.
+// **Parameters**:
+// *{object}* **service** An instance of a vie.service.
+// **Throws**:
+// *nothing*..
+// **Returns**:
+// *{array}* An array of rules with 'left' and 'right' side.
+ transformationRules : function (service) {
+ var res = [
+ // rule(s) to transform a dbpedia:Person into a VIE:Person
+ {
+ 'left' : [
+ '?subject a dbpedia:Person',
+ '?subject rdfs:label ?label'
+ ],
+ 'right': function(ns){
+ return function(){
+ return [
+ jQuery.rdf.triple(this.subject.toString(),
+ 'a',
+ '<' + ns.base() + 'Person>', {
+ namespaces: ns.toObj()
+ }),
+ jQuery.rdf.triple(this.subject.toString(),
+ '<' + ns.base() + 'name>',
+ this.label, {
+ namespaces: ns.toObj()
+ })
+ ];
+ };
+ }(service.vie.namespaces)
+ },
+ // rule(s) to transform a foaf:Person into a VIE:Person
+ {
+ 'left' : [
+ '?subject a foaf:Person',
+ '?subject rdfs:label ?label'
+ ],
+ 'right': function(ns){
+ return function(){
+ return [
+ jQuery.rdf.triple(this.subject.toString(),
+ 'a',
+ '<' + ns.base() + 'Person>', {
+ namespaces: ns.toObj()
+ }),
+ jQuery.rdf.triple(this.subject.toString(),
+ '<' + ns.base() + 'name>',
+ this.label, {
+ namespaces: ns.toObj()
+ })
+ ];
+ };
+ }(service.vie.namespaces)
+ },
+ // rule(s) to transform a dbpedia:Place into a VIE:Place
+ {
+ 'left' : [
+ '?subject a dbpedia:Place',
+ '?subject rdfs:label ?label'
+ ],
+ 'right': function(ns) {
+ return function() {
+ return [
+ jQuery.rdf.triple(this.subject.toString(),
+ 'a',
+ '<' + ns.base() + 'Place>', {
+ namespaces: ns.toObj()
+ }),
+ jQuery.rdf.triple(this.subject.toString(),
+ '<' + ns.base() + 'name>',
+ this.label.toString(), {
+ namespaces: ns.toObj()
+ })
+ ];
+ };
+ }(service.vie.namespaces)
+ },
+ // rule(s) to transform a dbpedia:City into a VIE:City
+ {
+ 'left' : [
+ '?subject a dbpedia:City',
+ '?subject rdfs:label ?label',
+ '?subject dbpedia:abstract ?abs',
+ '?subject dbpedia:country ?country'
+ ],
+ 'right': function(ns) {
+ return function() {
+ return [
+ jQuery.rdf.triple(this.subject.toString(),
+ 'a',
+ '<' + ns.base() + 'City>', {
+ namespaces: ns.toObj()
+ }),
+ jQuery.rdf.triple(this.subject.toString(),
+ '<' + ns.base() + 'name>',
+ this.label.toString(), {
+ namespaces: ns.toObj()
+ }),
+ jQuery.rdf.triple(this.subject.toString(),
+ '<' + ns.base() + 'description>',
+ this.abs.toString(), {
+ namespaces: ns.toObj()
+ }),
+ jQuery.rdf.triple(this.subject.toString(),
+ '<' + ns.base() + 'containedIn>',
+ this.country.toString(), {
+ namespaces: ns.toObj()
+ })
+ ];
+ };
+ }(service.vie.namespaces)
+ }
+ ];
+ return res;
+ },
+
+ getAdditionalRules : function (service) {
+
+ var mapping = {
+ Work : "CreativeWork",
+ Film : "Movie",
+ TelevisionEpisode : "TVEpisode",
+ TelevisionShow : "TVSeries", // not listed as equivalent class on dbpedia.org
+ Website : "WebPage",
+ Painting : "Painting",
+ Sculpture : "Sculpture",
+
+ Event : "Event",
+ SportsEvent : "SportsEvent",
+ MusicFestival : "Festival",
+ FilmFestival : "Festival",
+
+ Place : "Place",
+ Continent : "Continent",
+ Country : "Country",
+ City : "City",
+ Airport : "Airport",
+ Station : "TrainStation", // not listed as equivalent class on dbpedia.org
+ Hospital : "GovernmentBuilding",
+ Mountain : "Mountain",
+ BodyOfWater : "BodyOfWater",
+
+ Company : "Organization",
+ Person : "Person"
+ };
+
+ var additionalRules = [];
+ _.each(mapping, function (map, key) {
+ var tripple = {
+ 'left' : [ '?subject a dbpedia:' + key, '?subject rdfs:label ?label' ],
+ 'right' : function(ns) {
+ return function() {
+ return [ jQuery.rdf.triple(this.subject.toString(), 'a', '<' + ns.base() + map + '>', {
+ namespaces : ns.toObj()
+ }), jQuery.rdf.triple(this.subject.toString(), '<' + ns.base() + 'name>', this.label.toString(), {
+ namespaces : ns.toObj()
+ }) ];
+ };
+ }(service.vie.namespaces)
+ };
+ additionalRules.push(tripple);
+ });
+ return additionalRules;
+ }
+};
+// VIE - Vienna IKS Editables
+// (c) 2011 Henri Bergius, IKS Consortium
+// (c) 2011 Sebastian Germesin, IKS Consortium
+// (c) 2011 Szaby Grünwald, IKS Consortium
+// VIE may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://viejs.org/
+
+// ## VIE Entities
+//
+// In VIE there are two low-level model types for storing data.
+// **Collections** and **Entities**. Considering `var v = new VIE();` a VIE instance,
+// `v.entities` is a Collection with `VIE Entity` objects in it.
+// VIE internally uses JSON-LD to store entities.
+//
+// Each Entity has a few special attributes starting with an `@`. VIE has an API
+// for correctly using these attributes, so in order to stay compatible with later
+// versions of the library, possibly using a later version of JSON-LD, use the API
+// to interact with your entities.
+//
+// * `@subject` stands for the identifier of the entity. Use `e.getSubject()`
+// * `@type` stores the explicit entity types. VIE internally handles Type hierarchy,
+// which basically enables to define subtypes and supertypes. Every entity has
+// the type 'owl:Thing'. Read more about Types in VIE.Type.
+// * `@context` stores namespace definitions used in the entity. Read more about
+// Namespaces in VIE Namespaces.
+VIE.prototype.Entity = function(attrs, opts) {
+
+ attrs = (attrs)? attrs : {};
+ opts = (opts)? opts : {};
+
+ var self = this;
+
+ if (attrs['@type'] !== undefined) {
+ attrs['@type'] = (_.isArray(attrs['@type']))? attrs['@type'] : [ attrs['@type'] ];
+ attrs['@type'] = _.map(attrs['@type'], function(val){
+ if (!self.vie.types.get(val)) {
+ //if there is no such type -> add it and let it inherit from "owl:Thing"
+ self.vie.types.add(val).inherit("owl:Thing");
+ }
+ return self.vie.types.get(val).id;
+ });
+ attrs['@type'] = (attrs['@type'].length === 1)? attrs['@type'][0] : attrs['@type'];
+ } else {
+ // provide "owl:Thing" as the default type if none was given
+ attrs['@type'] = self.vie.types.get("owl:Thing").id;
+ }
+
+ //the following provides full seamless namespace support
+ //for attributes. It should not matter, if you
+ //query for `model.get('name')` or `model.get('foaf:name')`
+ //or even `model.get('http://xmlns.com/foaf/0.1/name');`
+ //However, if we just overwrite `set()` and `get()`, this
+ //raises a lot of side effects, so we need to expand
+ //the attributes before we create the model.
+ _.each (attrs, function (value, key) {
+ var newKey = VIE.Util.mapAttributeNS(key, this.namespaces);
+ if (key !== newKey) {
+ delete attrs[key];
+ attrs[newKey] = value;
+ }
+ }, self.vie);
+
+ var Model = Backbone.Model.extend({
+ idAttribute: '@subject',
+
+ initialize: function(attributes, options) {
+ if (attributes['@subject']) {
+ this.id = this['@subject'] = this.toReference(attributes['@subject']);
+ } else {
+ this.id = this['@subject'] = attributes['@subject'] = this.cid.replace('c', '_:bnode');
+ }
+ return this;
+ },
+
+ schema: function() {
+ return VIE.Util.getFormSchema(this);
+ },
+
+ // ### Getter, Has, Setter
+ // #### `.get(attr)`
+ // To be able to communicate to a VIE Entity you can use a simple get(property)
+ // command as in `entity.get('rdfs:label')` which will give you one or more literals.
+ // If the property points to a collection, its entities can be browsed further.
+ get: function (attr) {
+ attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
+ var value = Backbone.Model.prototype.get.call(this, attr);
+ value = (_.isArray(value))? value : [ value ];
+
+ value = _.map(value, function(v) {
+ if (v !== undefined && attr === '@type' && self.vie.types.get(v)) {
+ return self.vie.types.get(v);
+ } else if (v !== undefined && self.vie.entities.get(v)) {
+ return self.vie.entities.get(v);
+ } else {
+ return v;
+ }
+ }, this);
+ if(value.length === 0) {
+ return undefined;
+ }
+ // if there is only one element, just return that one
+ value = (value.length === 1)? value[0] : value;
+ return value;
+ },
+
+ // #### `.has(attr)`
+ // Sometimes you'd like to determine if a specific attribute is set
+ // in an entity. For this reason you can call for example `person.has('friend')`
+ // to determine if a person entity has friends.
+ has: function(attr) {
+ attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
+ return Backbone.Model.prototype.has.call(this, attr);
+ },
+
+ // #### `.set(attrName, value, opts)`,
+ // The `options` parameter always refers to a `Backbone.Model.set` `options` object.
+ //
+ // **`.set(attributes, options)`** is the most universal way of calling the
+ // `.set` method. In this case the `attributes` object is a map of all
+ // attributes to be changed.
+ set : function(attrs, options, opts) {
+ if (!attrs) {
+ return this;
+ }
+
+ if (attrs['@subject']) {
+ attrs['@subject'] = this.toReference(attrs['@subject']);
+ }
+
+ // Use **`.set(attrName, value, options)`** for setting or changing exactly one
+ // entity attribute.
+ if (typeof attrs === "string") {
+ var obj = {};
+ obj[attrs] = options;
+ return this.set(obj, opts);
+ }
+ // **`.set(entity)`**: In case you'd pass a VIE entity,
+ // the passed entities attributes are being set for the entity.
+ if (attrs.attributes) {
+ attrs = attrs.attributes;
+ }
+ var self = this;
+ var coll;
+ // resolve shortened URIs like rdfs:label..
+ _.each (attrs, function (value, key) {
+ var newKey = VIE.Util.mapAttributeNS(key, self.vie.namespaces);
+ if (key !== newKey) {
+ delete attrs[key];
+ attrs[newKey] = value;
+ }
+ }, this);
+ // Finally iterate through the *attributes* to be set and prepare
+ // them for the Backbone.Model.set method.
+ _.each (attrs, function (value, key) {
+ if (!value) { return; }
+ if (key.indexOf('@') === -1) {
+ if (value.isCollection) {
+ // ignore
+ value.each(function (child) {
+ self.vie.entities.addOrUpdate(child);
+ });
+ } else if (value.isEntity) {
+ self.vie.entities.addOrUpdate(value);
+ coll = new self.vie.Collection(value, {
+ vie: self.vie,
+ predicate: key
+ });
+ attrs[key] = coll;
+ } else if (_.isArray(value)) {
+ if (this.attributes[key] && this.attributes[key].isCollection) {
+ var newEntities = this.attributes[key].addOrUpdate(value);
+ attrs[key] = this.attributes[key];
+ attrs[key].reset(newEntities);
+ }
+ } else if (value["@value"]) {
+ // The value is a literal object, ignore
+ } else if (_.isObject(value) && !_.isDate(value)) {
+ // The value is another VIE Entity
+ var child = new self.vie.Entity(value, options);
+ // which is being stored in `v.entities`
+ self.vie.entities.addOrUpdate(child);
+ // and set as VIE Collection attribute on the original entity
+ coll = new self.vie.Collection(value, {
+ vie: self.vie,
+ predicate: key
+ });
+ attrs[key] = coll;
+ } else {
+ // ignore
+ }
+ }
+ }, this);
+ return Backbone.Model.prototype.set.call(this, attrs, options);
+ },
+
+ // **`.unset(attr, opts)` ** removes an attribute from the entity.
+ unset: function (attr, opts) {
+ attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
+ return Backbone.Model.prototype.unset.call(this, attr, opts);
+ },
+
+ // Validation based on type rules.
+ //
+ // There are two ways to skip validation for entity operations:
+ //
+ // * `options.silent = true`
+ // * `options.validate = false`
+ validate: function (attrs, opts) {
+ if (opts && opts.validate === false) {
+ return;
+ }
+ var types = this.get('@type');
+ if (_.isArray(types)) {
+ var results = [];
+ _.each(types, function (type) {
+ var res = this.validateByType(type, attrs, opts);
+ if (res) {
+ results.push(res);
+ }
+ }, this);
+ if (_.isEmpty(results)) {
+ return;
+ }
+ return _.flatten(results);
+ }
+
+ return this.validateByType(types, attrs, opts);
+ },
+
+ validateByType: function (type, attrs, opts) {
+ var messages = {
+ max: '<%= property %> cannot contain more than <%= num %> items',
+ min: '<%= property %> must contain at least <%= num %> items',
+ required: '<%= property %> is required'
+ };
+
+ if (!type.attributes) {
+ return;
+ }
+
+ var toError = function (definition, constraint, messageValues) {
+ return {
+ property: definition.id,
+ constraint: constraint,
+ message: _.template(messages[constraint], _.extend({
+ property: definition.id
+ }, messageValues))
+ };
+ };
+
+ var checkMin = function (definition, attrs) {
+ if (!attrs[definition.id] || _.isEmpty(attrs[definition.id])) {
+ return toError(definition, 'required', {});
+ }
+ };
+
+ // Check the number of items in attr against max
+ var checkMax = function (definition, attrs) {
+ if (!attrs[definition.id]) {
+ return;
+ }
+
+ if (!attrs[definition.id].isCollection && !_.isArray(attrs[definition.id])) {
+ return;
+ }
+
+ if (attrs[definition.id].length > definition.max) {
+ return toError(definition, 'max', {
+ num: definition.max
+ });
+ }
+ };
+
+ var results = [];
+ _.each(type.attributes.list(), function (definition) {
+ var res;
+ if (definition.max && definition.max != -1) {
+ res = checkMax(definition, attrs);
+ if (res) {
+ results.push(res);
+ }
+ }
+
+ if (definition.min && definition.min > 0) {
+ res = checkMin(definition, attrs);
+ if (res) {
+ results.push(res);
+ }
+ }
+ });
+
+ if (_.isEmpty(results)) {
+ return;
+ }
+ return results;
+ },
+
+ isNew: function() {
+ if (this.getSubjectUri().substr(0, 7) === '_:bnode') {
+ return true;
+ }
+ return false;
+ },
+
+ hasChanged: function(attr) {
+ if (this.markedChanged) {
+ return true;
+ }
+
+ return Backbone.Model.prototype.hasChanged.call(this, attr);
+ },
+
+ // Force hasChanged to return true
+ forceChanged: function(changed) {
+ this.markedChanged = changed ? true : false;
+ },
+
+ // **`getSubject()`** is the getter for the entity identifier.
+ getSubject: function(){
+ if (typeof this.id === "undefined") {
+ this.id = this.attributes[this.idAttribute];
+ }
+ if (typeof this.id === 'string') {
+ if (this.id.substr(0, 7) === 'http://' || this.id.substr(0, 4) === 'urn:') {
+ return this.toReference(this.id);
+ }
+ return this.id;
+ }
+ return this.cid.replace('c', '_:bnode');
+ },
+
+ // TODO describe
+ getSubjectUri: function(){
+ return this.fromReference(this.getSubject());
+ },
+
+ isReference: function(uri){
+ var matcher = new RegExp("^\\<([^\\>]*)\\>$");
+ if (matcher.exec(uri)) {
+ return true;
+ }
+ return false;
+ },
+
+ toReference: function(uri){
+ if (_.isArray(uri)) {
+ var self = this;
+ return _.map(uri, function(part) {
+ return self.toReference(part);
+ });
+ }
+ var ns = this.vie.namespaces;
+ var ret = uri;
+ if (uri.substring(0, 2) === "_:") {
+ ret = uri;
+ }
+ else if (ns.isCurie(uri)) {
+ ret = ns.uri(uri);
+ if (ret === "<" + ns.base() + uri + ">") {
+ /* no base namespace extension with IDs */
+ ret = '<' + uri + '>';
+ }
+ } else if (!ns.isUri(uri)) {
+ ret = '<' + uri + '>';
+ }
+ return ret;
+ },
+
+ fromReference: function(uri){
+ var ns = this.vie.namespaces;
+ if (!ns.isUri(uri)) {
+ return uri;
+ }
+ return uri.substring(1, uri.length - 1);
+ },
+
+ as: function(encoding){
+ if (encoding === "JSON") {
+ return this.toJSON();
+ }
+ if (encoding === "JSONLD") {
+ return this.toJSONLD();
+ }
+ throw new Error("Unknown encoding " + encoding);
+ },
+
+ toJSONLD: function(){
+ var instanceLD = {};
+ var instance = this;
+ _.each(instance.attributes, function(value, name){
+ var entityValue = value; //instance.get(name);
+
+ if (value instanceof instance.vie.Collection) {
+ entityValue = value.map(function(instance) {
+ return instance.getSubject();
+ });
+ }
+
+ // TODO: Handle collections separately
+ instanceLD[name] = entityValue;
+ });
+
+ instanceLD['@subject'] = instance.getSubject();
+
+ return instanceLD;
+ },
+
+ // **`.setOrAdd(arg1, arg2)`** similar to `.set(..)`, `.setOrAdd(..)` can
+ // be used for setting one or more attributes of an entity, but in
+ // this case it's a collection of values, not just one. That means, if the
+ // entity already has the attribute set, make the value to a VIE Collection
+ // and use the collection as value. The collection can contain entities
+ // or literals, but not both at the same time.
+ setOrAdd: function (arg1, arg2, option) {
+ var entity = this;
+ if (typeof arg1 === "string" && arg2) {
+ // calling entity.setOrAdd("rdfs:type", "example:Musician")
+ entity._setOrAddOne(arg1, arg2, option);
+ }
+ else
+ if (typeof arg1 === "object") {
+ // calling entity.setOrAdd({"rdfs:type": "example:Musician", ...})
+ _(arg1).each(function(val, key){
+ entity._setOrAddOne(key, val, arg2);
+ });
+ }
+ return this;
+ },
+
+
+ /* attr is always of type string */
+ /* value can be of type: string,int,double,object,VIE.Entity,VIE.Collection */
+ /* val can be of type: undefined,string,int,double,array,VIE.Collection */
+
+ /* depending on the type of value and the type of val, different actions need to be made */
+ _setOrAddOne: function (attr, value, options) {
+ if (!attr || !value)
+ return;
+ options = (options)? options : {};
+ var v;
+
+ attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
+
+ if (_.isArray(value)) {
+ for (v = 0; v < value.length; v++) {
+ this._setOrAddOne(attr, value[v], options);
+ }
+ return;
+ }
+
+ if (attr === "@type" && value instanceof self.vie.Type) {
+ value = value.id;
+ }
+
+ var obj = {};
+ var existing = Backbone.Model.prototype.get.call(this, attr);
+
+ if (!existing) {
+ obj[attr] = value;
+ this.set(obj, options);
+ } else if (existing.isCollection) {
+ if (value.isCollection) {
+ value.each(function (model) {
+ existing.add(model);
+ });
+ } else if (value.isEntity) {
+ existing.add(value);
+ } else if (typeof value === "object") {
+ value = new this.vie.Entity(value);
+ existing.add(value);
+ } else {
+ throw new Error("you cannot add a literal to a collection of entities!");
+ }
+ this.trigger('change:' + attr, this, value, {});
+ this.change({});
+ } else if (_.isArray(existing)) {
+ if (value.isCollection) {
+ for (v = 0; v < value.size(); v++) {
+ this._setOrAddOne(attr, value.at(v).getSubject(), options);
+ }
+ } else if (value.isEntity) {
+ this._setOrAddOne(attr, value.getSubject(), options);
+ } else if (typeof value === "object") {
+ value = new this.vie.Entity(value);
+ this._setOrAddOne(attr, value, options);
+ } else {
+ /* yes, we (have to) allow multiple equal values */
+ existing.push(value);
+ obj[attr] = existing;
+ this.set(obj);
+ }
+ } else {
+ var arr = [ existing ];
+ arr.push(value);
+ obj[attr] = arr;
+ return this.set(obj, options);
+ }
+ },
+
+ // **`.hasType(type)`** determines if the entity has the explicit `type` set.
+ hasType: function(type){
+ type = self.vie.types.get(type);
+ return this.hasPropertyValue("@type", type);
+ },
+
+ // TODO describe
+ hasPropertyValue: function(property, value) {
+ var t = this.get(property);
+ if (!(value instanceof Object)) {
+ value = self.vie.entities.get(value);
+ }
+ if (t instanceof Array) {
+ return t.indexOf(value) !== -1;
+ }
+ else {
+ return t === value;
+ }
+ },
+
+ // **`.isof(type)`** determines if the entity is of `type` by explicit or implicit
+ // declaration. E.g. if Employee is a subtype of Person and e Entity has
+ // explicitly set type Employee, e.isof(Person) will evaluate to true.
+ isof: function (type) {
+ var types = this.get('@type');
+
+ if (types === undefined) {
+ return false;
+ }
+ types = (_.isArray(types))? types : [ types ];
+
+ type = (self.vie.types.get(type))? self.vie.types.get(type) : new self.vie.Type(type);
+ for (var t = 0; t < types.length; t++) {
+ if (self.vie.types.get(types[t])) {
+ if (self.vie.types.get(types[t]).isof(type)) {
+ return true;
+ }
+ } else {
+ var typeTmp = new self.vie.Type(types[t]);
+ if (typeTmp.id === type.id) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ // TODO describe
+ addTo : function (collection, update) {
+ var self = this;
+ if (collection instanceof self.vie.Collection) {
+ if (update) {
+ collection.addOrUpdate(self);
+ } else {
+ collection.add(self);
+ }
+ return this;
+ }
+ throw new Error("Please provide a proper collection of type VIE.Collection as argument!");
+ },
+
+ isEntity: true,
+
+ vie: self.vie
+ });
+
+ return new Model(attrs, opts);
+};
+// VIE - Vienna IKS Editables
+// (c) 2011 Henri Bergius, IKS Consortium
+// (c) 2011 Sebastian Germesin, IKS Consortium
+// (c) 2011 Szaby Grünwald, IKS Consortium
+// VIE may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://viejs.org/
+VIE.prototype.Collection = Backbone.Collection.extend({
+ model: VIE.prototype.Entity,
+
+ initialize: function (models, options) {
+ if (!options || !options.vie) {
+ throw new Error('Each collection needs a VIE reference');
+ }
+ this.vie = options.vie;
+ this.predicate = options.predicate;
+ },
+
+ canAdd: function (type) {
+ return true;
+ },
+
+ get: function(id) {
+ if (id === null) {
+ return null;
+ }
+
+ id = (id.getSubject)? id.getSubject() : id;
+ if (typeof id === "string" && id.indexOf("_:") === 0) {
+ if (id.indexOf("bnode") === 2) {
+ //bnode!
+ id = id.replace("_:bnode", 'c');
+ return this._byCid[id];
+ } else {
+ return this._byId["<" + id + ">"];
+ }
+ } else {
+ id = this.toReference(id);
+ return this._byId[id];
+ }
+ },
+
+ addOrUpdate: function(model, options) {
+ options = options || {};
+
+ var collection = this;
+ var existing;
+ if (_.isArray(model)) {
+ var entities = [];
+ _.each(model, function(item) {
+ entities.push(collection.addOrUpdate(item, options));
+ });
+ return entities;
+ }
+
+ if (model === undefined) {
+ throw new Error("No model given");
+ }
+
+ if (_.isString(model)) {
+ model = {
+ '@subject': model,
+ id: model
+ };
+ }
+
+ if (!model.isEntity) {
+ model = new this.model(model);
+ }
+
+ if (model.id && this.get(model.id)) {
+ existing = this.get(model.id);
+ }
+ if (this.getByCid(model.cid)) {
+ existing = this.getByCid(model.cid);
+ }
+ if (existing) {
+ var newAttribs = {};
+ _.each(model.attributes, function(value, attribute) {
+ if (!existing.has(attribute)) {
+ newAttribs[attribute] = value;
+ return true;
+ }
+
+ if (attribute === '@subject') {
+ if (model.isNew() && !existing.isNew()) {
+ // Save order issue, skip
+ return true;
+ }
+ }
+
+ if (existing.get(attribute) === value) {
+ return true;
+ }
+ //merge existing attribute values with new ones!
+ //not just overwrite 'em!!
+ var oldVals = existing.attributes[attribute];
+ var newVals = value;
+ if (oldVals instanceof collection.vie.Collection) {
+ // TODO: Merge collections
+ return true;
+ }
+ if (options.overrideAttributes) {
+ newAttribs[attribute] = value;
+ return true;
+ }
+ if (attribute === '@context') {
+ newAttribs[attribute] = jQuery.extend(true, {}, oldVals, newVals);
+ } else {
+ oldVals = (jQuery.isArray(oldVals))? oldVals : [ oldVals ];
+ newVals = (jQuery.isArray(newVals))? newVals : [ newVals ];
+ newAttribs[attribute] = _.uniq(oldVals.concat(newVals));
+ newAttribs[attribute] = (newAttribs[attribute].length === 1)? newAttribs[attribute][0] : newAttribs[attribute];
+ }
+ });
+
+ if (!_.isEmpty(newAttribs)) {
+ existing.set(newAttribs, options.updateOptions);
+ }
+ return existing;
+ }
+ this.add(model, options.addOptions);
+ return model;
+ },
+
+ isReference: function(uri){
+ var matcher = new RegExp("^\\<([^\\>]*)\\>$");
+ if (matcher.exec(uri)) {
+ return true;
+ }
+ return false;
+ },
+
+ toReference: function(uri){
+ if (this.isReference(uri)) {
+ return uri;
+ }
+ return '<' + uri + '>';
+ },
+
+ fromReference: function(uri){
+ if (!this.isReference(uri)) {
+ return uri;
+ }
+ return uri.substring(1, uri.length - 1);
+ },
+
+ isCollection: true
+});
+// VIE - Vienna IKS Editables
+// (c) 2011 Henri Bergius, IKS Consortium
+// (c) 2011 Sebastian Germesin, IKS Consortium
+// (c) 2011 Szaby Grünwald, IKS Consortium
+// VIE may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://viejs.org/
+//
+
+// ## VIE.Types
+// Within VIE, we provide special capabilities of handling types of entites. This helps
+// for example to query easily for certain entities (e.g., you only need to query for *Person*s
+// and not for all subtypes).
+if (VIE.prototype.Type) {
+ throw new Error("ERROR: VIE.Type is already defined. Please check your installation!");
+}
+if (VIE.prototype.Types) {
+ throw new Error("ERROR: VIE.Types is already defined. Please check your installation!");
+}
+
+// ### VIE.Type(id, attrs, metadata)
+// This is the constructor of a VIE.Type.
+// **Parameters**:
+// *{string}* **id** The id of the type.
+// *{string|array|VIE.Attribute}* **attrs** A string, proper ```VIE.Attribute``` or an array of these which
+// *{object}* **metadata** Possible metadata about the type
+// are the possible attributes of the type
+// **Throws**:
+// *{Error}* if one of the given paramenters is missing.
+// **Returns**:
+// *{VIE.Type}* : A **new** VIE.Type object.
+// **Example usage**:
+//
+// var person = new vie.Type("Person", ["name", "knows"]);
+VIE.prototype.Type = function (id, attrs, metadata) {
+ if (id === undefined || typeof id !== 'string') {
+ throw "The type constructor needs an 'id' of type string! E.g., 'Person'";
+ }
+
+// ### id
+// This field stores the id of the type's instance.
+// **Parameters**:
+// nothing
+// **Throws**:
+// nothing
+// **Returns**:
+// *{string}* : The id of the type as a URI.
+// **Example usage**:
+//
+// console.log(person.id);
+// // --> ""
+ this.id = this.vie.namespaces.isUri(id) ? id : this.vie.namespaces.uri(id);
+
+ /* checks whether such a type is already defined. */
+ if (this.vie.types.get(this.id)) {
+ throw new Error("The type " + this.id + " is already defined!");
+ }
+
+// ### supertypes
+// This field stores all parent types of the type's instance. This
+// is set if the current type inherits from another type.
+// **Parameters**:
+// nothing
+// **Throws**:
+// nothing
+// **Returns**:
+// *{VIE.Types}* : The supertypes (parents) of the type.
+// **Example usage**:
+//
+// console.log(person.supertypes);
+ this.supertypes = new this.vie.Types();
+
+// ### subtypes
+// This field stores all children types of the type's instance. This
+// will be set if another type inherits from the current type.
+// **Parameters**:
+// nothing
+// **Throws**:
+// nothing
+// **Returns**:
+// *{VIE.Types}* : The subtypes (parents) of the type.
+// **Example usage**:
+//
+// console.log(person.subtypes);
+ this.subtypes = new this.vie.Types();
+
+// ### attributes
+// This field stores all attributes of the type's instance as
+// a proper ```VIE.Attributes``` class. (see also VIE.Attributes)
+// **Parameters**:
+// nothing
+// **Throws**:
+// nothing
+// **Returns**:
+// *{VIE.Attributes}* : The attributes of the type.
+// **Example usage**:
+//
+// console.log(person.attributes);
+ this.attributes = new this.vie.Attributes(this, (attrs)? attrs : []);
+
+// ### metadata
+// This field stores possible additional information about the type, like
+// a human-readable label.
+ this.metadata = metadata ? metadata : {};
+
+// ### isof(type)
+// This method checks whether the current type is a child of the given type.
+// **Parameters**:
+// *{string|VIE.Type}* **type** The type (or the id of that type) to be checked.
+// **Throws**:
+// *{Error}* If the type is not valid.
+// **Returns**:
+// *{boolean}* : ```true``` if the current type inherits from the type, ```false``` otherwise.
+// **Example usage**:
+//
+// console.log(person.isof("owl:Thing"));
+// // <-- true
+ this.isof = function (type) {
+ type = this.vie.types.get(type);
+ if (type) {
+ return type.subsumes(this.id);
+ } else {
+ throw new Error("No valid type given");
+ }
+ };
+
+// ### subsumes(type)
+// This method checks whether the current type is a parent of the given type.
+// **Parameters**:
+// *{string|VIE.Type}* **type** The type (or the id of that type) to be checked.
+// **Throws**:
+// *{Error}* If the type is not valid.
+// **Returns**:
+// *{boolean}* : ```true``` if the current type is a parent of the type, ```false``` otherwise.
+// **Example usage**:
+//
+// var x = new vie.Type(...);
+// var y = new vie.Type(...).inherit(x);
+// y.isof(x) === x.subsumes(y);
+ this.subsumes = function (type) {
+ type = this.vie.types.get(type);
+ if (type) {
+ if (this.id === type.id) {
+ return true;
+ }
+ var subtypes = this.subtypes.list();
+ for (var c = 0; c < subtypes.length; c++) {
+ var childObj = subtypes[c];
+ if (childObj) {
+ if (childObj.id === type.id || childObj.subsumes(type)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ } else {
+ throw new Error("No valid type given");
+ }
+ };
+
+// ### inherit(supertype)
+// This method invokes inheritance throught the types. This adds the current type to the
+// subtypes of the supertype and vice versa.
+// **Parameters**:
+// *{string|VIE.Type|array}* **supertype** The type to be inherited from. If this is an array
+// the inherit method is called sequentially on all types.
+// **Throws**:
+// *{Error}* If the type is not valid.
+// **Returns**:
+// *{VIE.Type}* : The instance itself.
+// **Example usage**:
+//
+// var x = new vie.Type(...);
+// var y = new vie.Type(...).inherit(x);
+// y.isof(x) // <-- true
+ this.inherit = function (supertype) {
+ if (typeof supertype === "string") {
+ this.inherit(this.vie.types.get(supertype));
+ }
+ else if (supertype instanceof this.vie.Type) {
+ supertype.subtypes.addOrOverwrite(this);
+ this.supertypes.addOrOverwrite(supertype);
+ try {
+ /* only for validation of attribute-inheritance!
+ if this throws an error (inheriting two attributes
+ that cannot be combined) we reverse all changes. */
+ this.attributes.list();
+ } catch (e) {
+ supertype.subtypes.remove(this);
+ this.supertypes.remove(supertype);
+ throw e;
+ }
+ } else if (jQuery.isArray(supertype)) {
+ for (var i = 0, slen = supertype.length; i < slen; i++) {
+ this.inherit(supertype[i]);
+ }
+ } else {
+ throw new Error("Wrong argument in VIE.Type.inherit()");
+ }
+ return this;
+ };
+
+// ### hierarchy()
+// This method serializes the hierarchy of child types into an object.
+// **Parameters**:
+// *nothing*
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{object}* : The hierachy of child types as an object.
+// **Example usage**:
+//
+// var x = new vie.Type(...);
+// var y = new vie.Type(...).inherit(x);
+// x.hierarchy();
+ this.hierarchy = function () {
+ var obj = {id : this.id, subtypes: []};
+ var list = this.subtypes.list();
+ for (var c = 0, llen = list.length; c < llen; c++) {
+ var childObj = this.vie.types.get(list[c]);
+ obj.subtypes.push(childObj.hierarchy());
+ }
+ return obj;
+ };
+
+// ### instance()
+// This method creates a ```VIE.Entity``` instance from this type.
+// **Parameters**:
+// *{object}* **attrs** see constructor of VIE.Entity
+// *{object}* **opts** see constructor of VIE.Entity
+// **Throws**:
+// *{Error}* if the instance could not be built
+// **Returns**:
+// *{VIE.Entity}* : A **new** instance of a ```VIE.Entity``` with the current type.
+// **Example usage**:
+//
+// var person = new vie.Type("person");
+// var sebastian = person.instance(
+// {"@subject" : "#me",
+// "name" : "Sebastian"});
+// console.log(sebastian.get("name")); // <-- "Sebastian"
+ this.instance = function (attrs, opts) {
+ attrs = (attrs)? attrs : {};
+ opts = (opts)? opts : {};
+
+ /* turn type/attribute checking on by default! */
+ if (opts.typeChecking !== false) {
+ for (var a in attrs) {
+ if (a.indexOf('@') !== 0 && !this.attributes.get(a)) {
+ throw new Error("Cannot create an instance of " + this.id + " as the type does not allow an attribute '" + a + "'!");
+ }
+ }
+ }
+
+ if (attrs['@type']) {
+ attrs['@type'].push(this.id);
+ } else {
+ attrs['@type'] = this.id;
+ }
+
+ return new this.vie.Entity(attrs, opts);
+ };
+
+// ### toString()
+// This method returns the id of the type.
+// **Parameters**:
+// *nothing*
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{string}* : The id of the type.
+// **Example usage**:
+//
+// var x = new vie.Type(...);
+// x.toString() === x.id;
+ this.toString = function () {
+ return this.id;
+ };
+};
+
+// ### VIE.Types()
+// This is the constructor of a VIE.Types. This is a convenience class
+// to store ```VIE.Type``` instances properly.
+// **Parameters**:
+// *nothing*
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Types}* : A **new** VIE.Types object.
+// **Example usage**:
+//
+// var types = new vie.Types();
+VIE.prototype.Types = function () {
+
+ this._types = {};
+
+// ### add(id, attrs, metadata)
+// This method adds a `VIE.Type` to the types.
+// **Parameters**:
+// *{string|VIE.Type}* **id** If this is a string, the type is created and directly added.
+// *{string|object}* **attrs** Only used if ```id``` is a string.
+// *{object}* **metadata** potential additional metadata about the type.
+// **Throws**:
+// *{Error}* if a type with the given id already exists a ```VIE.Entity``` instance from this type.
+// **Returns**:
+// *{VIE.Types}* : The instance itself.
+// **Example usage**:
+//
+// var types = new vie.Types();
+// types.add("Person", ["name", "knows"]);
+ this.add = function (id, attrs, metadata) {
+ if (_.isArray(id)) {
+ _.each(id, function (type) {
+ this.add(type);
+ }, this);
+ return this;
+ }
+
+ if (this.get(id)) {
+ throw new Error("Type '" + id + "' already registered.");
+ } else {
+ if (typeof id === "string") {
+ var t = new this.vie.Type(id, attrs, metadata);
+ this._types[t.id] = t;
+ return t;
+ } else if (id instanceof this.vie.Type) {
+ this._types[id.id] = id;
+ return id;
+ } else {
+ throw new Error("Wrong argument to VIE.Types.add()!");
+ }
+ }
+ return this;
+ };
+
+// ### addOrOverwrite(id, attrs)
+// This method adds or overwrites a `VIE.Type` to the types. This is the same as
+// ``this.remove(id); this.add(id, attrs);``
+// **Parameters**:
+// *{string|VIE.Type}* **id** If this is a string, the type is created and directly added.
+// *{string|object}* **attrs** Only used if ```id``` is a string.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Types}* : The instance itself.
+// **Example usage**:
+//
+// var types = new vie.Types();
+// types.addOrOverwrite("Person", ["name", "knows"]);
+ this.addOrOverwrite = function(id, attrs){
+ if (this.get(id)) {
+ this.remove(id);
+ }
+ return this.add(id, attrs);
+ };
+
+// ### get(id)
+// This method retrieves a `VIE.Type` from the types by it's id.
+// **Parameters**:
+// *{string|VIE.Type}* **id** The id or the type itself.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Type}* : The instance of the type or ```undefined```.
+// **Example usage**:
+//
+// var types = new vie.Types();
+// types.addOrOverwrite("Person", ["name", "knows"]);
+// types.get("Person");
+ this.get = function (id) {
+ if (!id) {
+ return undefined;
+ }
+ if (typeof id === 'string') {
+ var lid = this.vie.namespaces.isUri(id) ? id : this.vie.namespaces.uri(id);
+ return this._types[lid];
+ } else if (id instanceof this.vie.Type) {
+ return this.get(id.id);
+ }
+ return undefined;
+ };
+
+// ### remove(id)
+// This method removes a type of given id from the type. This also
+// removes all children if their only parent were this
+// type. Furthermore, this removes the link from the
+// super- and subtypes.
+// **Parameters**:
+// *{string|VIE.Type}* **id** The id or the type itself.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Type}* : The removed type.
+// **Example usage**:
+//
+// var types = new vie.Types();
+// types.addOrOverwrite("Person", ["name", "knows"]);
+// types.remove("Person");
+ this.remove = function (id) {
+ var t = this.get(id);
+ /* test whether the type actually exists in VIE
+ * and prevents removing *owl:Thing*.
+ */
+ if (!t) {
+ return this;
+ }
+ if (!t || t.subsumes("owl:Thing")) {
+ console.warn("You are not allowed to remove 'owl:Thing'.");
+ return this;
+ }
+ delete this._types[t.id];
+
+ var subtypes = t.subtypes.list();
+ for (var c = 0; c < subtypes.length; c++) {
+ var childObj = subtypes[c];
+ if (childObj.supertypes.list().length === 1) {
+ /* recursively remove all children
+ that inherit only from this type */
+ this.remove(childObj);
+ } else {
+ childObj.supertypes.remove(t.id);
+ }
+ }
+ return t;
+ };
+
+// ### toArray() === list()
+// This method returns an array of all types.
+// **Parameters**:
+// *nothing*
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{array}* : An array of ```VIE.Type``` instances.
+// **Example usage**:
+//
+// var types = new vie.Types();
+// types.addOrOverwrite("Person", ["name", "knows"]);
+// types.list();
+ this.toArray = this.list = function () {
+ var ret = [];
+ for (var i in this._types) {
+ ret.push(this._types[i]);
+ }
+ return ret;
+ };
+
+// ### sort(types, desc)
+// This method sorts an array of types in their order, given by the
+// inheritance. This returns a copy and leaves the original array untouched.
+// **Parameters**:
+// *{array|VIE.Type}* **types** The array of ```VIE.Type``` instances or ids of types to be sorted.
+// *{boolean}* **desc** If 'desc' is given and 'true', the array will be sorted
+// in descendant order.
+// *nothing*
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{array}* : A sorted copy of the array.
+// **Example usage**:
+//
+// var types = new vie.Types();
+// types.addOrOverwrite("Person", ["name", "knows"]);
+// types.sort(types.list(), true);
+ this.sort = function (types, desc) {
+ var self = this;
+ types = (jQuery.isArray(types))? types : [ types ];
+ desc = (desc)? true : false;
+
+ if (types.length === 0) return [];
+ var copy = [ types[0] ];
+ var x, tlen;
+ for (x = 1, tlen = types.length; x < tlen; x++) {
+ var insert = types[x];
+ var insType = self.get(insert);
+ if (insType) {
+ for (var y = 0; y < copy.length; y++) {
+ if (insType.subsumes(copy[y])) {
+ copy.splice(y,0,insert);
+ break;
+ } else if (y === copy.length - 1) {
+ copy.push(insert);
+ }
+ }
+ }
+ }
+
+ //unduplicate
+ for (x = 0; x < copy.length; x++) {
+ if (copy.lastIndexOf(copy[x]) !== x) {
+ copy.splice(x, 1);
+ x--;
+ }
+ }
+
+ if (!desc) {
+ copy.reverse();
+ }
+ return copy;
+ };
+};
+// VIE - Vienna IKS Editables
+// (c) 2011 Henri Bergius, IKS Consortium
+// (c) 2011 Sebastian Germesin, IKS Consortium
+// (c) 2011 Szaby Grünwald, IKS Consortium
+// VIE may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://viejs.org/
+//
+
+// ## VIE.Attributes
+// Within VIE, we provide special capabilities of handling attributes of types of entites. This
+// helps first of all to list all attributes of an entity type, but furthermore fully supports
+// inheritance of attributes from the type-class to inherit from.
+if (VIE.prototype.Attribute) {
+ throw new Error("ERROR: VIE.Attribute is already defined. Please check your VIE installation!");
+}
+if (VIE.prototype.Attributes) {
+ throw new Error("ERROR: VIE.Attributes is already defined. Please check your VIE installation!");
+}
+
+// ### VIE.Attribute(id, range, domain, minCount, maxCount, metadata)
+// This is the constructor of a VIE.Attribute.
+// **Parameters**:
+// *{string}* **id** The id of the attribute.
+// *{string|array}* **range** A string or an array of strings of the target range of
+// the attribute.
+// *{string}* **domain** The domain of the attribute.
+// *{number}* **minCount** The minimal number this attribute can occur. (needs to be >= 0)
+// *{number}* **maxCount** The maximal number this attribute can occur. (needs to be >= minCount, use `-1` for unlimited)
+// *{object}* **metadata** Possible metadata about the attribute
+// **Throws**:
+// *{Error}* if one of the given paramenters is missing.
+// **Returns**:
+// *{VIE.Attribute}* : A **new** VIE.Attribute object.
+// **Example usage**:
+//
+// var knowsAttr = new vie.Attribute("knows", ["Person"], "Person", 0, 10);
+// // Creates an attribute to describe a *knows*-relationship
+// // between persons. Each person can only have
+VIE.prototype.Attribute = function (id, range, domain, minCount, maxCount, metadata) {
+ if (id === undefined || typeof id !== 'string') {
+ throw new Error("The attribute constructor needs an 'id' of type string! E.g., 'Person'");
+ }
+ if (range === undefined) {
+ throw new Error("The attribute constructor of " + id + " needs 'range'.");
+ }
+ if (domain === undefined) {
+ throw new Error("The attribute constructor of " + id + " needs a 'domain'.");
+ }
+
+ this._domain = domain;
+
+// ### id
+// This field stores the id of the attribute's instance.
+// **Parameters**:
+// nothing
+// **Throws**:
+// nothing
+// **Returns**:
+// *{string}* : A URI, representing the id of the attribute.
+// **Example usage**:
+//
+// var knowsAttr = new vie.Attribute("knows", ["Person"], "Person");
+// console.log(knowsAttr.id);
+// // -->
+ this.id = this.vie.namespaces.isUri(id) ? id : this.vie.namespaces.uri(id);
+
+// ### range
+// This field stores the ranges of the attribute's instance.
+// **Parameters**:
+// nothing
+// **Throws**:
+// nothing
+// **Returns**:
+// *{array}* : An array of strings which represent the types.
+// **Example usage**:
+//
+// var knowsAttr = new vie.Attribute("knows", ["Person"], "Person");
+// console.log(knowsAttr.range);
+// // --> ["Person"]
+ this.range = (_.isArray(range))? range : [ range ];
+
+// ### min
+// This field stores the minimal amount this attribute can occur in the type's instance. The number
+// needs to be greater or equal to zero.
+// **Parameters**:
+// nothing
+// **Throws**:
+// nothing
+// **Returns**:
+// *{int}* : The minimal amount this attribute can occur.
+// **Example usage**:
+//
+// console.log(person.min);
+// // --> 0
+ minCount = minCount ? minCount : 0;
+ this.min = (minCount > 0) ? minCount : 0;
+
+// ### max
+// This field stores the maximal amount this attribute can occur in the type's instance.
+// This number cannot be smaller than min
+// **Parameters**:
+// nothing
+// **Throws**:
+// nothing
+// **Returns**:
+// *{int}* : The maximal amount this attribute can occur.
+// **Example usage**:
+//
+// console.log(person.max);
+// // --> 1.7976931348623157e+308
+ maxCount = maxCount ? maxCount : 1;
+ if (maxCount === -1) {
+ maxCount = Number.MAX_VALUE;
+ }
+ this.max = (maxCount >= this.min)? maxCount : this.min;
+
+// ### metadata
+// This field holds potential metadata about the attribute.
+ this.metadata = metadata ? metadata : {};
+
+// ### applies(range)
+// This method checks, whether the current attribute applies in the given range.
+// If ```range``` is a string and cannot be transformed into a ```VIE.Type```,
+// this performs only string comparison, if it is a VIE.Type
+// or an ID of a VIE.Type, then inheritance is checked as well.
+// **Parameters**:
+// *{string|VIE.Type}* **range** The ```VIE.Type``` (or it's string representation) to be checked.
+// **Throws**:
+// nothing
+// **Returns**:
+// *{boolean}* : ```true``` if the given type applies to this attribute and ```false``` otherwise.
+// **Example usage**:
+//
+// var knowsAttr = new vie.Attribute("knows", ["Person"], "Person");
+// console.log(knowsAttr.applies("Person")); // --> true
+// console.log(knowsAttr.applies("Place")); // --> false
+ this.applies = function (range) {
+ if (this.vie.types.get(range)) {
+ range = this.vie.types.get(range);
+ }
+ for (var r = 0, len = this.range.length; r < len; r++) {
+ var x = this.vie.types.get(this.range[r]);
+ if (x === undefined && typeof range === "string") {
+ if (range === this.range[r]) {
+ return true;
+ }
+ }
+ else {
+ if (range.isof(this.range[r])) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+};
+
+// ## VIE.Attributes(domain, attrs)
+// This is the constructor of a VIE.Attributes. Basically a convenience class
+// that represents a list of ```VIE.Attribute```. As attributes are part of a
+// certain ```VIE.Type```, it needs to be passed for inheritance checks.
+// **Parameters**:
+// *{string}* **domain** The domain of the attributes (the type they will be part of).
+// *{string|VIE.Attribute|array}* **attrs** Either a string representation of an attribute,
+// a proper instance of ```VIE.Attribute``` or an array of both.
+// *{string}* **domain** The domain of the attribute.
+// **Throws**:
+// *{Error}* if one of the given paramenters is missing.
+// **Returns**:
+// *{VIE.Attribute}* : A **new** VIE.Attribute instance.
+// **Example usage**:
+//
+// var knowsAttr = new vie.Attribute("knows", ["Person"], "Person");
+// var personAttrs = new vie.Attributes("Person", knowsAttr);
+VIE.prototype.Attributes = function (domain, attrs) {
+
+ this._local = {};
+ this._attributes = {};
+
+// ### domain
+// This field stores the domain of the attributes' instance.
+// **Parameters**:
+// nothing
+// **Throws**:
+// nothing
+// **Returns**:
+// *{string}* : The string representation of the domain.
+// **Example usage**:
+//
+// console.log(personAttrs.domain);
+// // --> ["Person"]
+ this.domain = domain;
+
+// ### add(id, range, min, max, metadata)
+// This method adds a ```VIE.Attribute``` to the attributes instance.
+// **Parameters**:
+// *{string|VIE.Attribute}* **id** The string representation of an attribute, or a proper
+// instance of a ```VIE.Attribute```.
+// *{string|array}* **range** An array representing the target range of the attribute.
+// *{number}* **min** The minimal amount this attribute can appear.
+// instance of a ```VIE.Attribute```.
+// *{number}* **max** The maximal amount this attribute can appear.
+// *{object}* **metadata** Additional metadata for the attribute.
+// **Throws**:
+// *{Error}* If an atribute with the given id is already registered.
+// *{Error}* If the ```id``` parameter is not a string, nor a ```VIE.Type``` instance.
+// **Returns**:
+// *{VIE.Attribute}* : The generated or passed attribute.
+// **Example usage**:
+//
+// personAttrs.add("name", "Text", 0, 1);
+ this.add = function (id, range, min, max, metadata) {
+ if (_.isArray(id)) {
+ _.each(id, function (attribute) {
+ this.add(attribute);
+ }, this);
+ return this;
+ }
+
+ if (this.get(id)) {
+ throw new Error("Attribute '" + id + "' already registered for domain " + this.domain.id + "!");
+ } else {
+ if (typeof id === "string") {
+ var a = new this.vie.Attribute(id, range, this.domain, min, max, metadata);
+ this._local[a.id] = a;
+ return a;
+ } else if (id instanceof this.vie.Attribute) {
+ id.domain = this.domain;
+ id.vie = this.vie;
+ this._local[id.id] = id;
+ return id;
+ } else {
+ throw new Error("Wrong argument to VIE.Types.add()!");
+ }
+ }
+ };
+
+// ### remove(id)
+// This method removes a ```VIE.Attribute``` from the attributes instance.
+// **Parameters**:
+// *{string|VIE.Attribute}* **id** The string representation of an attribute, or a proper
+// instance of a ```VIE.Attribute```.
+// **Throws**:
+// *{Error}* When the attribute is inherited from a parent ```VIE.Type``` and thus cannot be removed.
+// **Returns**:
+// *{VIE.Attribute}* : The removed attribute.
+// **Example usage**:
+//
+// personAttrs.remove("knows");
+ this.remove = function (id) {
+ var a = this.get(id);
+ if (a.id in this._local) {
+ delete this._local[a.id];
+ return a;
+ }
+ throw new Error("The attribute " + id + " is inherited and cannot be removed from the domain " + this.domain.id + "!");
+ };
+
+// ### get(id)
+// This method returns a ```VIE.Attribute``` from the attributes instance by it's id.
+// **Parameters**:
+// *{string|VIE.Attribute}* **id** The string representation of an attribute, or a proper
+// instance of a ```VIE.Attribute```.
+// **Throws**:
+// *{Error}* When the method is called with an unknown datatype.
+// **Returns**:
+// *{VIE.Attribute}* : The attribute.
+// **Example usage**:
+//
+// personAttrs.get("knows");
+ this.get = function (id) {
+ if (typeof id === 'string') {
+ var lid = this.vie.namespaces.isUri(id) ? id : this.vie.namespaces.uri(id);
+ return this._inherit()._attributes[lid];
+ } else if (id instanceof this.vie.Attribute) {
+ return this.get(id.id);
+ } else {
+ throw new Error("Wrong argument in VIE.Attributes.get()");
+ }
+ };
+
+// ### _inherit()
+// The private method ```_inherit``` creates a full list of all attributes. This includes
+// local attributes as well as inherited attributes from the parents. The ranges of attributes
+// with the same id will be merged. This method is called everytime an attribute is requested or
+// the list of all attributes. Usually this method should not be invoked outside of the class.
+// **Parameters**:
+// *nothing*
+// instance of a ```VIE.Attribute```.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *nothing*
+// **Example usage**:
+//
+// personAttrs._inherit();
+ this._inherit = function () {
+ var a, x, id;
+ var attributes = jQuery.extend(true, {}, this._local);
+
+ var inherited = _.map(this.domain.supertypes.list(),
+ function (x) {
+ return x.attributes;
+ }
+ );
+
+ var add = {};
+ var merge = {};
+ var ilen, alen;
+ for (a = 0, ilen = inherited.length; a < ilen; a++) {
+ var attrs = inherited[a].list();
+ for (x = 0, alen = attrs.length; x < alen; x++) {
+ id = attrs[x].id;
+ if (!(id in attributes)) {
+ if (!(id in add) && !(id in merge)) {
+ add[id] = attrs[x];
+ }
+ else {
+ if (!merge[id]) {
+ merge[id] = {range : [], mins : [], maxs: [], metadatas: []};
+ }
+ if (id in add) {
+ merge[id].range = jQuery.merge(merge[id].range, add[id].range);
+ merge[id].mins = jQuery.merge(merge[id].mins, [ add[id].min ]);
+ merge[id].maxs = jQuery.merge(merge[id].maxs, [ add[id].max ]);
+ merge[id].metadatas = jQuery.merge(merge[id].metadatas, [ add[id].metadata ]);
+ delete add[id];
+ }
+ merge[id].range = jQuery.merge(merge[id].range, attrs[x].range);
+ merge[id].mins = jQuery.merge(merge[id].mins, [ attrs[x].min ]);
+ merge[id].maxs = jQuery.merge(merge[id].maxs, [ attrs[x].max ]);
+ merge[id].metadatas = jQuery.merge(merge[id].metadatas, [ attrs[x].metadata ]);
+ merge[id].range = _.uniq(merge[id].range);
+ merge[id].mins = _.uniq(merge[id].mins);
+ merge[id].maxs = _.uniq(merge[id].maxs);
+ merge[id].metadatas = _.uniq(merge[id].metadatas);
+ }
+ }
+ }
+ }
+
+ /* adds inherited attributes that do not need to be merged */
+ jQuery.extend(attributes, add);
+
+ /* merges inherited attributes */
+ for (id in merge) {
+ var mranges = merge[id].range;
+ var mins = merge[id].mins;
+ var maxs = merge[id].maxs;
+ var metadatas = merge[id].metadatas;
+ var ranges = [];
+ //merging ranges
+ for (var r = 0, mlen = mranges.length; r < mlen; r++) {
+ var p = this.vie.types.get(mranges[r]);
+ var isAncestorOf = false;
+ if (p) {
+ for (x = 0; x < mlen; x++) {
+ if (x === r) {
+ continue;
+ }
+ var c = this.vie.types.get(mranges[x]);
+ if (c && c.isof(p)) {
+ isAncestorOf = true;
+ break;
+ }
+ }
+ }
+ if (!isAncestorOf) {
+ ranges.push(mranges[r]);
+ }
+ }
+
+ var maxMin = _.max(mins);
+ var minMax = _.min(maxs);
+ if (maxMin <= minMax && minMax >= 0 && maxMin >= 0) {
+ attributes[id] = new this.vie.Attribute(id, ranges, this, maxMin, minMax, metadatas[0]);
+ } else {
+ throw new Error("This inheritance is not allowed because of an invalid minCount/maxCount pair!");
+ }
+ }
+
+ this._attributes = attributes;
+ return this;
+ };
+
+// ### toArray() === list()
+// This method return an array of ```VIE.Attribute```s from the attributes instance.
+// **Parameters**:
+// *nothing.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{array}* : An array of ```VIE.Attribute```.
+// **Example usage**:
+//
+// personAttrs.list();
+ this.toArray = this.list = function (range) {
+ var ret = [];
+ var attributes = this._inherit()._attributes;
+ for (var a in attributes) {
+ if (!range || attributes[a].applies(range)) {
+ ret.push(attributes[a]);
+ }
+ }
+ return ret;
+ };
+
+ attrs = _.isArray(attrs) ? attrs : [ attrs ];
+ _.each(attrs, function (attr) {
+ this.add(attr.id, attr.range, attr.min, attr.max, attr.metadata);
+ }, this);
+};
+// VIE - Vienna IKS Editables
+// (c) 2011 Henri Bergius, IKS Consortium
+// (c) 2011 Sebastian Germesin, IKS Consortium
+// (c) 2011 Szaby Grünwald, IKS Consortium
+// VIE may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://viejs.org/
+if (VIE.prototype.Namespaces) {
+ throw new Error("ERROR: VIE.Namespaces is already defined. " +
+ "Please check your VIE installation!");
+}
+
+// ## VIE Namespaces
+//
+// In general, a namespace is a container that provides context for the identifiers.
+// Within VIE, namespaces are used to distinguish different ontolgies or vocabularies
+// of identifiers, types and attributes. However, because of their verbosity, namespaces
+// tend to make their usage pretty circuitous. The ``VIE.Namespaces(...)`` class provides VIE
+// with methods to maintain abbreviations (akak **prefixes**) for namespaces in order to
+// alleviate their usage. By default, every VIE instance is equipped with a main instance
+// of the namespaces in ``myVIE.namespaces``. Furthermore, VIE uses a **base namespace**,
+// which is used if no prefix is given (has an empty prefix).
+// In the upcoming sections, we will explain the
+// methods to add, access and remove prefixes.
+
+
+
+// ## VIE.Namespaces(base, namespaces)
+// This is the constructor of a VIE.Namespaces. The constructor initially
+// needs a *base namespace* and can optionally be initialised with an
+// associative array of prefixes and namespaces. The base namespace is used in a way
+// that every non-prefixed, non-expanded attribute or type is assumed to be of that
+// namespace. This helps, e.g., in an environment where only one namespace is given.
+// **Parameters**:
+// *{string}* **base** The base namespace.
+// *{object}* **namespaces** Initial namespaces to bootstrap the namespaces. (optional)
+// **Throws**:
+// *{Error}* if the base namespace is missing.
+// **Returns**:
+// *{VIE.Attribute}* : A **new** VIE.Attribute object.
+// **Example usage**:
+//
+// var ns = new myVIE.Namespaces("http://viejs.org/ns/",
+// {
+// "foaf": "http://xmlns.com/foaf/0.1/"
+// });
+VIE.prototype.Namespaces = function (base, namespaces) {
+
+ if (!base) {
+ throw new Error("Please provide a base namespace!");
+ }
+ this._base = base;
+
+ this._namespaces = (namespaces)? namespaces : {};
+ if (typeof this._namespaces !== "object" || _.isArray(this._namespaces)) {
+ throw new Error("If you want to initialise VIE namespace prefixes, " +
+ "please provide a proper object!");
+ }
+};
+
+
+// ### base(ns)
+// This is a **getter** and **setter** for the base
+// namespace. If called like ``base();`` it
+// returns the actual base namespace as a string. If provided
+// with a string, e.g., ``base("http://viejs.org/ns/");``
+// it sets the current base namespace and retuns the namespace object
+// for the purpose of chaining. If provided with anything except a string,
+// it throws an Error.
+// **Parameters**:
+// *{string}* **ns** The namespace to be set. (optional)
+// **Throws**:
+// *{Error}* if the namespace is not of type string.
+// **Returns**:
+// *{string}* : The current base namespace.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// console.log(namespaces.base()); // <-- "http://base.ns/"
+// namespaces.base("http://viejs.org/ns/");
+// console.log(namespaces.base()); // <-- "http://viejs.org/ns/"
+VIE.prototype.Namespaces.prototype.base = function (ns) {
+ if (!ns) {
+ return this._base;
+ }
+ else if (typeof ns === "string") {
+ /* remove another mapping */
+ this.removeNamespace(ns);
+ this._base = ns;
+ return this._base;
+ } else {
+ throw new Error("Please provide a valid namespace!");
+ }
+};
+
+// ### add(prefix, namespace)
+// This method adds new prefix mappings to the
+// current instance. If a prefix or a namespace is already
+// present (in order to avoid ambiguities), an Error is thrown.
+// ``prefix`` can also be an object in which case, the method
+// is called sequentially on all elements.
+// **Parameters**:
+// *{string|object}* **prefix** The prefix to be set. If it is an object, the
+// method will be applied to all key,value pairs sequentially.
+// *{string}* **namespace** The namespace to be set.
+// **Throws**:
+// *{Error}* If a prefix or a namespace is already
+// present (in order to avoid ambiguities).
+// **Returns**:
+// *{VIE.Namespaces}* : The current namespaces instance.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// namespaces.add("", "http://...");
+// // is always equal to
+// namespaces.base("http://..."); // <-- setter of base namespace
+VIE.prototype.Namespaces.prototype.add = function (prefix, namespace) {
+ if (typeof prefix === "object") {
+ for (var k1 in prefix) {
+ this.add(k1, prefix[k1]);
+ }
+ return this;
+ }
+ if (prefix === "") {
+ this.base(namespace);
+ return this;
+ }
+ /* checking if we overwrite existing mappings */
+ else if (this.contains(prefix) && namespace !== this._namespaces[prefix]) {
+ throw new Error("ERROR: Trying to register namespace prefix mapping (" + prefix + "," + namespace + ")!" +
+ "There is already a mapping existing: '(" + prefix + "," + this.get(prefix) + ")'!");
+ } else {
+ jQuery.each(this._namespaces, function (k1,v1) {
+ if (v1 === namespace && k1 !== prefix) {
+ throw new Error("ERROR: Trying to register namespace prefix mapping (" + prefix + "," + namespace + ")!" +
+ "There is already a mapping existing: '(" + k1 + "," + namespace + ")'!");
+ }
+ });
+ }
+ /* if not, just add them */
+ this._namespaces[prefix] = namespace;
+ return this;
+};
+
+// ### addOrReplace(prefix, namespace)
+// This method adds new prefix mappings to the
+// current instance. This will overwrite existing mappings.
+// **Parameters**:
+// *{string|object}* **prefix** The prefix to be set. If it is an object, the
+// method will be applied to all key,value pairs sequentially.
+// *{string}* **namespace** The namespace to be set.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Namespaces}* : The current namespaces instance.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// namespaces.addOrReplace("", "http://...");
+// // is always equal to
+// namespaces.base("http://..."); // <-- setter of base namespace
+VIE.prototype.Namespaces.prototype.addOrReplace = function (prefix, namespace) {
+ if (typeof prefix === "object") {
+ for (var k1 in prefix) {
+ this.addOrReplace(k1, prefix[k1]);
+ }
+ return this;
+ }
+ this.remove(prefix);
+ this.removeNamespace(namespace);
+ return this.add(prefix, namespace);
+};
+
+// ### get(prefix)
+// This method retrieves a namespaces, given a prefix. If the
+// prefix is the empty string, the base namespace is returned.
+// **Parameters**:
+// *{string}* **prefix** The prefix to be retrieved.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{string|undefined}* : The namespace or ```undefined``` if no namespace could be found.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// namespaces.addOrReplace("test", "http://test.ns");
+// console.log(namespaces.get("test")); // <-- "http://test.ns"
+VIE.prototype.Namespaces.prototype.get = function (prefix) {
+ if (prefix === "") {
+ return this.base();
+ }
+ return this._namespaces[prefix];
+};
+
+// ### getPrefix(namespace)
+// This method retrieves a prefix, given a namespace.
+// **Parameters**:
+// *{string}* **namespace** The namespace to be retrieved.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{string|undefined}* : The prefix or ```undefined``` if no prefix could be found.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// namespaces.addOrReplace("test", "http://test.ns");
+// console.log(namespaces.getPrefix("http://test.ns")); // <-- "test"
+VIE.prototype.Namespaces.prototype.getPrefix = function (namespace) {
+ var prefix;
+ if (namespace.indexOf('<') === 0) {
+ namespace = namespace.substring(1, namespace.length - 1);
+ }
+ jQuery.each(this._namespaces, function (k1,v1) {
+ if (namespace.indexOf(v1) === 0) {
+ prefix = k1;
+ }
+
+ if (namespace.indexOf(k1 + ':') === 0) {
+ prefix = k1;
+ }
+ });
+ return prefix;
+};
+
+// ### contains(prefix)
+// This method checks, whether a prefix is stored in the instance.
+// **Parameters**:
+// *{string}* **prefix** The prefix to be checked.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{boolean}* : ```true``` if the prefix could be found, ```false``` otherwise.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// namespaces.addOrReplace("test", "http://test.ns");
+// console.log(namespaces.contains("test")); // <-- true
+VIE.prototype.Namespaces.prototype.contains = function (prefix) {
+ return (prefix in this._namespaces);
+};
+
+// ### containsNamespace(namespace)
+// This method checks, whether a namespace is stored in the instance.
+// **Parameters**:
+// *{string}* **namespace** The namespace to be checked.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{boolean}* : ```true``` if the namespace could be found, ```false``` otherwise.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// namespaces.addOrReplace("test", "http://test.ns");
+// console.log(namespaces.containsNamespace("http://test.ns")); // <-- true
+VIE.prototype.Namespaces.prototype.containsNamespace = function (namespace) {
+ return this.getPrefix(namespace) !== undefined;
+};
+
+// ### update(prefix, namespace)
+// This method overwrites the namespace that is stored under the
+// prefix ``prefix`` with the new namespace ``namespace``.
+// If a namespace is already bound to another prefix, an Error is thrown.
+// **Parameters**:
+// *{string}* **prefix** The prefix.
+// *{string}* **namespace** The namespace.
+// **Throws**:
+// *{Error}* If a namespace is already bound to another prefix.
+// **Returns**:
+// *{VIE.Namespaces}* : The namespace instance.
+// **Example usage**:
+//
+// ...
+VIE.prototype.Namespaces.prototype.update = function (prefix, namespace) {
+ this.remove(prefix);
+ return this.add(prefix, namespace);
+};
+
+// ### updateNamespace(prefix, namespace)
+// This method overwrites the prefix that is bound to the
+// namespace ``namespace`` with the new prefix ``prefix``. If another namespace is
+// already registered with the given ``prefix``, an Error is thrown.
+// **Parameters**:
+// *{string}* **prefix** The prefix.
+// *{string}* **namespace** The namespace.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Namespaces}* : The namespace instance.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// namespaces.add("test", "http://test.ns");
+// namespaces.updateNamespace("test2", "http://test.ns");
+// namespaces.get("test2"); // <-- "http://test.ns"
+VIE.prototype.Namespaces.prototype.updateNamespace = function (prefix, namespace) {
+ this.removeNamespace(prefix);
+ return this.add(prefix, namespace);
+};
+
+// ### remove(prefix)
+// This method removes the namespace that is stored under the prefix ``prefix``.
+// **Parameters**:
+// *{string}* **prefix** The prefix to be removed.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Namespaces}* : The namespace instance.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// namespaces.add("test", "http://test.ns");
+// namespaces.get("test"); // <-- "http://test.ns"
+// namespaces.remove("test");
+// namespaces.get("test"); // <-- undefined
+VIE.prototype.Namespaces.prototype.remove = function (prefix) {
+ if (prefix) {
+ delete this._namespaces[prefix];
+ }
+ return this;
+};
+
+// ### removeNamespace(namespace)
+// This method removes removes the namespace ``namespace`` from the instance.
+// **Parameters**:
+// *{string}* **namespace** The namespace to be removed.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{VIE.Namespaces}* : The namespace instance.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// namespaces.add("test", "http://test.ns");
+// namespaces.get("test"); // <-- "http://test.ns"
+// namespaces.removeNamespace("http://test.ns");
+// namespaces.get("test"); // <-- undefined
+VIE.prototype.Namespaces.prototype.removeNamespace = function (namespace) {
+ var prefix = this.getPrefix(namespace);
+ if (prefix) {
+ delete this._namespaces[prefix];
+ }
+ return this;
+};
+
+// ### toObj()
+// This method serializes the namespace instance into an associative
+// array representation. The base namespace is given an empty
+// string as key.
+// **Parameters**:
+// *{boolean}* **omitBase** If set to ```true``` this omits the baseNamespace.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{object}* : A serialization of the namespaces as an object.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// namespaces.add("test", "http://test.ns");
+// console.log(namespaces.toObj());
+// // <-- {"" : "http://base.ns/",
+// "test": "http://test.ns"}
+// console.log(namespaces.toObj(true));
+// // <-- {"test": "http://test.ns"}
+VIE.prototype.Namespaces.prototype.toObj = function (omitBase) {
+ if (omitBase) {
+ return jQuery.extend({}, this._namespaces);
+ }
+ return jQuery.extend({'' : this._base}, this._namespaces);
+};
+
+// ### curie(uri, safe)
+// This method converts a given
+// URI into a CURIE (or SCURIE), based on the given ```VIE.Namespaces``` object.
+// If the given uri is already a URI, it is left untouched and directly returned.
+// If no prefix could be found, an ```Error``` is thrown.
+// **Parameters**:
+// *{string}* **uri** The URI to be transformed.
+// *{boolean}* **safe** A flag whether to generate CURIEs or SCURIEs.
+// **Throws**:
+// *{Error}* If no prefix could be found in the passed namespaces.
+// **Returns**:
+// *{string}* The CURIE or SCURIE.
+// **Example usage**:
+//
+// var ns = new myVIE.Namespaces(
+// "http://viejs.org/ns/",
+// { "dbp": "http://dbpedia.org/ontology/" }
+// );
+// var uri = "";
+// ns.curie(uri, false); // --> dbp:Person
+// ns.curie(uri, true); // --> [dbp:Person]
+VIE.prototype.Namespaces.prototype.curie = function(uri, safe){
+ return VIE.Util.toCurie(uri, safe, this);
+};
+
+// ### isCurie(curie)
+// This method checks, whether
+// the given string is a CURIE and returns ```true``` if so and ```false```otherwise.
+// **Parameters**:
+// *{string}* **curie** The CURIE (or SCURIE) to be checked.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{boolean}* ```true``` if the given curie is a CURIE or SCURIE and ```false``` otherwise.
+// **Example usage**:
+//
+// var ns = new myVIE.Namespaces(
+// "http://viejs.org/ns/",
+// { "dbp": "http://dbpedia.org/ontology/" }
+// );
+// var uri = "";
+// var curie = "dbp:Person";
+// var scurie = "[dbp:Person]";
+// var text = "This is some text.";
+// ns.isCurie(uri); // --> false
+// ns.isCurie(curie); // --> true
+// ns.isCurie(scurie); // --> true
+// ns.isCurie(text); // --> false
+VIE.prototype.Namespaces.prototype.isCurie = function (something) {
+ return VIE.Util.isCurie(something, this);
+};
+
+// ### uri(curie)
+// This method converts a
+// given CURIE (or save CURIE) into a URI, based on the given ```VIE.Namespaces``` object.
+// **Parameters**:
+// *{string}* **curie** The CURIE to be transformed.
+// **Throws**:
+// *{Error}* If no URI could be assembled.
+// **Returns**:
+// *{string}* : A string, representing the URI.
+// **Example usage**:
+//
+// var ns = new myVIE.Namespaces(
+// "http://viejs.org/ns/",
+// { "dbp": "http://dbpedia.org/ontology/" }
+// );
+// var curie = "dbp:Person";
+// var scurie = "[dbp:Person]";
+// ns.uri(curie);
+// -->
+// ns.uri(scurie);
+// -->
+VIE.prototype.Namespaces.prototype.uri = function (curie) {
+ return VIE.Util.toUri(curie, this);
+};
+
+// ### isUri(something)
+// This method checks, whether the given string is a URI.
+// **Parameters**:
+// *{string}* **something** : The string to be checked.
+// **Throws**:
+// *nothing*
+// **Returns**:
+// *{boolean}* : ```true``` if the string is a URI, ```false``` otherwise.
+// **Example usage**:
+//
+// var namespaces = new vie.Namespaces("http://base.ns/");
+// namespaces.addOrReplace("test", "http://test.ns");
+// var uri = "";
+// var curie = "test:Person";
+// namespaces.isUri(uri); // --> true
+// namespaces.isUri(curie); // --> false
+VIE.prototype.Namespaces.prototype.isUri = VIE.Util.isUri;
+})();
\ No newline at end of file
diff --git a/core/modules/edit/css/edit.css b/core/modules/edit/css/edit.css
new file mode 100644
index 0000000..b270165
--- /dev/null
+++ b/core/modules/edit/css/edit.css
@@ -0,0 +1,405 @@
+/**
+ * Animations.
+ */
+.edit-animate-invisible {
+ opacity: 0;
+}
+
+.edit-animate-fast {
+-webkit-transition: all .2s ease;
+ -moz-transition: all .2s ease;
+ -ms-transition: all .2s ease;
+ -o-transition: all .2s ease;
+ transition: all .2s ease;
+}
+
+.edit-animate-default {
+ -webkit-transition: all .4s ease;
+ -moz-transition: all .4s ease;
+ -ms-transition: all .4s ease;
+ -o-transition: all .4s ease;
+ transition: all .4s ease;
+}
+
+.edit-animate-slow {
+-webkit-transition: all .6s ease;
+ -moz-transition: all .6s ease;
+ -ms-transition: all .6s ease;
+ -o-transition: all .6s ease;
+ transition: all .6s ease;
+}
+
+.edit-animate-delay-veryfast {
+ -webkit-transition-delay: .05s;
+ -moz-transition-delay: .05s;
+ -ms-transition-delay: .05s;
+ -o-transition-delay: .05s;
+ transition-delay: .05s;
+}
+
+.edit-animate-delay-fast {
+ -webkit-transition-delay: .2s;
+ -moz-transition-delay: .2s;
+ -ms-transition-delay: .2s;
+ -o-transition-delay: .2s;
+ transition-delay: .2s;
+}
+
+.edit-animate-disable-width {
+ -webkit-transition: width 0s;
+ -moz-transition: width 0s;
+ -ms-transition: width 0s;
+ -o-transition: width 0s;
+ transition: width 0s;
+}
+
+.edit-animate-only-visibility {
+ -webkit-transition: opacity .2s ease;
+ -moz-transition: opacity .2s ease;
+ -ms-transition: opacity .2s ease;
+ -o-transition: opacity .2s ease;
+ transition: opacity .2s ease;
+}
+
+
+
+
+/**
+ * Toolbar.
+ */
+.icon-edit:before {
+ background-image: url("../images/icon-edit.png");
+}
+.icon-edit:active:before,
+.active .icon-edit:before {
+ background-image: url("../images/icon-edit-active.png");
+}
+.toolbar .tray.edit.active {
+ z-index: 340;
+}
+.toolbar .icon-edit.edit-nothing-editable-hidden {
+ display: none;
+}
+
+
+
+
+/**
+ * Edit mode: overlay + candidate editables + editables being edited.
+ *
+ * Note: every class is prefixed with "edit-" to prevent collisions with modules
+ * or themes. In IPE-specific DOM subtrees, this is not necessary.
+ */
+
+#edit_overlay {
+ position: fixed;
+ z-index: 250;
+ width: 100%;
+ height: 100%;
+ background-color: #fff;
+ background-color: rgba(255,255,255,.5);
+ top: 0;
+ left: 0;
+}
+
+/* Editable. */
+.edit-editable {
+ z-index: 300;
+ position: relative;
+}
+.edit-editable:focus {
+ outline: none;
+}
+.edit-field.edit-editable,
+.edit-field.edit-type-direct .edit-editable {
+ box-shadow: 0 0 1px 1px #4d9de9;
+}
+
+/* Highlighted (hovered) editable. */
+.edit-editable.edit-highlighted {
+ min-width: 200px;
+}
+.edit-field.edit-editable.edit-highlighted,
+.edit-form.edit-editable.edit-highlighted,
+.edit-field.edit-type-direct .edit-editable.edit-highlighted {
+ box-shadow: 0 0 1px 1px #0199ff, 0 0 3px 3px rgba(153, 153, 153, .5);
+}
+.edit-field.edit-editable.edit-highlighted.edit-validation-error,
+.edit-form.edit-editable.edit-highlighted.edit-validation-error,
+.edit-field.edit-type-direct .edit-editable.edit-highlighted.edit-validation-error {
+ box-shadow: 0 0 1px 1px red, 0 0 3px 3px rgba(153, 153, 153, .5);
+}
+.edit-form.edit-editable .form-item .error {
+ border: 1px solid #eea0a0;
+}
+
+
+/* Editing (focused) editable. */
+.edit-form.edit-editable.edit-editing,
+.edit-field.edit-type-direct .edit-editable.edit-editing {
+ /* In the latest design, there's no special styling when editing as opposed to
+ * just hovering.
+ * This will be necessary again for http://drupal.org/node/1844220.
+ */
+}
+
+
+
+
+/**
+ * Edit mode: modal.
+ */
+#edit_modal {
+ z-index: 350;
+ position: fixed;
+ top: 40%;
+ left: 40%;
+ box-shadow: 3px 3px 5px #333;
+ background-color: white;
+ border: 1px solid #0199ff;
+ font-family: 'Droid sans', 'Lucida Grande', sans-serif;
+}
+
+#edit_modal .main {
+ font-size: 130%;
+ margin: 25px;
+ padding-left: 40px;
+ background: transparent url('../images/attention.png') no-repeat;
+}
+
+#edit_modal .actions {
+ border-top: 1px solid #ddd;
+ padding: 3px inherit;
+ text-align: right;
+ background: #f5f5f5;
+}
+
+/* Modal active: prevent user from interacting with toolbar & editables. */
+.edit-form-container.edit-belowoverlay,
+.edit-toolbar-container.edit-belowoverlay,
+.edit-validation-errors.edit-belowoverlay {
+ z-index: 210;
+}
+.edit-editable.edit-belowoverlay {
+ z-index: 200;
+}
+
+
+
+
+/**
+ * Edit mode: type=direct.
+ */
+.edit-validation-errors {
+ z-index: 300;
+ position: relative;
+}
+
+.edit-validation-errors .messages.error {
+ position: absolute;
+ top: 6px;
+ left: -5px;
+ margin: 0;
+ border: none;
+ box-shadow: 0 0 1px 1px red, 0 0 3px 3px rgba(153, 153, 153, .5);
+ background-color: white;
+}
+
+
+
+
+/**
+ * Edit mode: type=form.
+ */
+#edit_backstage {
+ display: none;
+}
+
+.edit-form {
+ position: absolute;
+ z-index: 300;
+ box-shadow: 0 0 30px 4px #4f4f4f;
+ max-width: 35em;
+}
+
+.edit-form .placeholder {
+ min-height: 22px;
+}
+
+/* Default form styling overrides. */
+.edit-form form { padding: 1em; }
+.edit-form .form-item { margin: 0; }
+.edit-form .form-wrapper { margin: .5em; }
+.edit-form .form-actions { display: none; }
+.edit-form input { max-width: 100%; }
+
+
+
+
+/**
+ * Edit mode: toolbars
+ */
+
+/* Trick: wrap statically positioned elements in relatively positioned element
+ without changing its location. This allows us to absolutely position the
+ toolbar.
+*/
+.edit-toolbar-container,
+.edit-form-container {
+ position: relative;
+ padding: 0;
+ border: 0;
+ margin: 0;
+ vertical-align: baseline;
+ z-index: 310;
+}
+.edit-toolbar-container {
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+
+.edit-toolbar-heightfaker {
+ height: auto;
+ position: absolute;
+ bottom: 1px;
+ box-shadow: 0 0 1px 1px #0199ff, 0 0 3px 3px rgba(153, 153, 153, .5);
+ background: #fff;
+}
+
+/* The toolbar; these are not necessarily visible. */
+.edit-toolbar {
+ position: relative;
+ height: 100%;
+ font-family: 'Droid sans', 'Lucida Grande', sans-serif;
+}
+.edit-toolbar-heightfaker {
+ clip: rect(-1000px, 1000px, auto, -1000px); /* Remove bottom box-shadow. */
+}
+/* Exception: when used for a directly WYSIWYG editable field that is actively
+ being edited. */
+.edit-type-direct-with-wysiwyg .edit-editing .edit-toolbar-heightfaker {
+ width: 100%;
+ clip: auto;
+}
+
+
+/* The toolbar contains toolgroups; these are visible. */
+.edit-toolgroup {
+ float: left; /* LTR */
+}
+
+/* Info toolgroup. */
+.edit-toolgroup.info {
+ float: left; /* LTR */
+ font-weight: bolder;
+ padding: 0 5px;
+ background: #fff url('../images/throbber.gif') no-repeat -60px 60px;
+}
+.edit-toolgroup.info.loading {
+ padding-right: 35px;
+ background-position: 90% 50%;
+}
+
+/* Operations toolgroup. */
+.edit-toolgroup.ops {
+ float: right; /* LTR */
+ margin-left: 5px;
+}
+
+.edit-toolgroup.wysiwyg-tabs {
+ float: right;
+}
+.edit-toolgroup.wysiwyg {
+ clear: left;
+ width: 100%;
+ padding-left: 0;
+}
+
+
+
+/**
+ * Edit mode: buttons (in both modal and toolbar).
+ */
+#edit_modal a,
+.edit-toolbar a {
+ float: left; /* LTR */
+ display: block;
+ height: 21px;
+ min-width: 21px;
+ padding: 3px 6px 3px 6px;
+ margin: 4px 5px 1px 0;
+ border: 1px solid #fff;
+ border-radius: 3px;
+ color: white;
+ text-decoration: none;
+ font-size: 13px;
+}
+#edit_modal a {
+ float: none;
+ display: inline-block;
+}
+
+#edit_modal a:link,
+#edit_modal a:visited,
+#edit_modal a:hover,
+#edit_modal a:active,
+.edit-toolbar a:link,
+.edit-toolbar a:visited,
+.edit-toolbar a:hover,
+.edit-toolbar a:active {
+ text-decoration: none;
+}
+
+/* Button with icons. */
+#edit_modal a span,
+.edit-toolbar a span {
+ width: 22px;
+ height: 19px;
+ display: block;
+ float: left;
+}
+.edit-toolbar a span.close {
+ background: url('../images/close.png') no-repeat 3px 2px;
+}
+
+.edit-toolbar a.blank-button {
+ color: black;
+}
+
+#edit_modal a.blue-button,
+.edit-toolbar a.blue-button {
+ color: white;
+ background-image: -webkit-linear-gradient(top, #6fc2f2 0%, #4e97c0 100%);
+ background-image: -moz-linear-gradient(top, #6fc2f2 0%, #4e97c0 100%);
+ background-image: linear-gradient(top, #6fc2f2 0%, #4e97c0 100%);
+ border-radius: 5px;
+}
+
+#edit_modal a.gray-button,
+.edit-toolbar a.gray-button {
+ color: #666;
+ background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #ccc 100%);
+ background-image: -moz-linear-gradient(top, #f5f5f5 0%, #ccc 100%);
+ background-image: linear-gradient(top, #f5f5f5 0%, #ccc 100%);
+ border-radius: 5px;
+}
+
+#edit_modal a.blue-button:hover,
+.edit-toolbar a.blue-button:hover,
+#edit_modal a.blue-button:active,
+.edit-toolbar a.blue-button:active {
+ border: 1px solid #55a5d3;
+ box-shadow: 0 2px 1px rgba(0,0,0,0.2);
+}
+
+#edit_modal a.gray-button:hover,
+.edit-toolbar a.gray-button:hover,
+#edit_modal a.gray-button:active,
+.edit-toolbar a.gray-button:active {
+ border: 1px solid #cdcdcd;
+ box-shadow: 0 2px 1px rgba(0,0,0,0.1);
+}
diff --git a/core/modules/edit/edit.info b/core/modules/edit/edit.info
new file mode 100644
index 0000000..3328601
--- /dev/null
+++ b/core/modules/edit/edit.info
@@ -0,0 +1,7 @@
+name = Edit
+description = In-place content editing.
+package = Core
+core = 8.x
+
+dependencies[] = field
+dependencies[] = toolbar
diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
new file mode 100644
index 0000000..8740388
--- /dev/null
+++ b/core/modules/edit/edit.module
@@ -0,0 +1,381 @@
+ array(TRUE),
+ 'access callback' => TRUE,
+ 'page callback' => 'edit_field_edit',
+ 'page arguments' => array(3, 4, 5, 6, 7),
+ 'theme callback' => 'ajax_base_page_theme',
+ 'file' => 'includes/pages.inc',
+ );
+ $items['admin/render-without-transformations/field/%/%/%/%/%'] = array(
+ // Access is controlled after we have inspected the entity, which can't
+ // easily happen until after the callback.
+ 'access arguments' => array(TRUE),
+ 'access callback' => TRUE,
+ 'page callback' => 'edit_text_field_render_without_transformation_filters',
+ 'page arguments' => array(3, 4, 5, 6, 7),
+ 'theme callback' => 'ajax_base_page_theme',
+ 'file' => 'includes/pages.inc',
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_toolbar().
+ */
+function edit_toolbar() {
+ if (path_is_admin(current_path())) {
+ return;
+ }
+
+ $tab['edit'] = array(
+ 'tab' => array(
+ 'title' => t('Edit'),
+ 'href' => '',
+ 'html' => FALSE,
+ 'attributes' => array(
+ 'class' => array('icon', 'icon-edit', 'edit-nothing-editable-hidden'),
+ ),
+ ),
+ 'tray' => array(
+ '#heading' => t('In-place editing operations'),
+ 'view_edit_toggle' => array(
+ '#theme' => 'links__toolbar_edit',
+ '#attributes' => array(
+ 'id' => 'edit_view-edit-toggles',
+ 'class' => 'menu',
+ ),
+ '#links' => array(
+ 'view' => array(
+ 'title' => t('View'),
+ 'href' => request_path(),
+ 'fragment' => 'view',
+ 'attributes' => array(
+ 'class' => array('edit_view-edit-toggle', 'edit-view'),
+ ),
+ ),
+ 'edit' => array(
+ 'title' => t('Quick edit'),
+ 'href' => request_path(),
+ 'fragment' => 'quick-edit',
+ 'attributes' => array(
+ 'class' => array('edit_view-edit-toggle', 'edit-edit'),
+ ),
+ ),
+ ),
+ '#attached' => array(
+ 'library' => array(
+ array('edit', 'edit'),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ return $tab;
+}
+
+/**
+ * Implements hook_library().
+ */
+function edit_library_info() {
+ $path = drupal_get_path('module', 'edit');
+ $libraries['edit'] = array(
+ 'title' => 'Edit: in-place editing',
+ 'website' => 'http://drupal.org/project/edit',
+ 'version' => VERSION,
+ 'js' => array(
+ // Core.
+ $path . '/js/edit.js' => array('defer' => TRUE),
+ $path . '/js/app.js' => array('defer' => TRUE),
+ // Routers.
+ $path . '/js/routers/edit-router.js' => array('defer' => TRUE),
+ // Models.
+ $path . '/js/models/edit-app-model.js' => array('defer' => TRUE),
+ // Views.
+ $path . '/js/views/propertyeditordecoration-view.js' => array('defer' => TRUE),
+ $path . '/js/views/menu-view.js' => array('defer' => TRUE),
+ $path . '/js/views/modal-view.js' => array('defer' => TRUE),
+ $path . '/js/views/overlay-view.js' => array('defer' => TRUE),
+ $path . '/js/views/toolbar-view.js' => array('defer' => TRUE),
+ // Backbone.sync implementation on top of Drupal forms.
+ $path . '/js/backbone.drupalform.js' => array('defer' => TRUE),
+ // VIE service.
+ $path . '/js/viejs/EditService.js' => array('defer' => TRUE),
+ // Create.js subclasses.
+ $path . '/js/createjs/editable.js' => array('defer' => TRUE),
+ $path . '/js/createjs/storage.js' => array('defer' => TRUE),
+ $path . '/js/createjs/editingWidgets/formwidget.js' => array('defer' => TRUE),
+ $path . '/js/createjs/editingWidgets/drupalcontenteditablewidget.js' => array('defer' => TRUE),
+ // Other.
+ $path . '/js/util.js' => array('defer' => TRUE),
+ $path . '/js/theme.js' => array('defer' => TRUE),
+ // Basic settings.
+ array(
+ 'data' => array('edit' => array(
+ 'fieldFormURL' => url('admin/edit/field/!entity_type/!id/!field_name/!langcode/!view_mode'),
+ 'rerenderProcessedTextURL' => url('admin/render-without-transformations/field/!entity_type/!id/!field_name/!langcode/!view_mode'),
+ 'context' => 'body',
+ )),
+ 'type' => 'setting',
+ ),
+ ),
+ 'css' => array(
+ $path . '/css/edit.css',
+ ),
+ 'dependencies' => array(
+ array('system', 'jquery'),
+ array('system', 'underscore'),
+ array('system', 'backbone'),
+ array('system', 'vie.core'),
+ array('system', 'create.editonly'),
+ array('system', 'jquery.form'),
+ array('system', 'drupal.form'),
+ array('system', 'drupal.ajax'),
+ array('system', 'drupalSettings'),
+ ),
+ );
+
+ return $libraries;
+}
+
+/**
+ * Implements hook_field_attach_view_alter().
+ */
+function edit_field_attach_view_alter(&$output, $context) {
+ // Special case for this special mode.
+ if ($context['display'] == 'edit-render-without-transformation-filters') {
+ $children = element_children($output);
+ $field_name = reset($children);
+ $langcode = $output[$field_name]['#language'];
+ foreach (array_keys($output[$field_name]['#items']) as $item) {
+ $text = $output[$field_name]['#items'][$item]['value'];
+ $format_id = $output[$field_name]['#items'][$item]['format'];
+ $untransformed = check_markup($text, $format_id, $langcode, FALSE, array(FILTER_TYPE_TRANSFORM_REVERSIBLE, FILTER_TYPE_TRANSFORM_IRREVERSIBLE));
+ $output[$field_name][$item]['#markup'] = $untransformed;
+ }
+ }
+}
+
+/**
+ * Implements hook_preprocess_HOOK() for field.tpl.php.
+ */
+function edit_preprocess_field(&$variables) {
+ $entity = $variables['element']['#object'];
+ $field_name = $variables['element']['#field_name'];
+ $langcode = $variables['element']['#language'];
+ $view_mode = $variables['element']['#view_mode'];
+ $formatter_type = $variables['element']['#formatter'];
+ $items = $entity->{$field_name}[$langcode];;
+ $instance = field_info_instance($entity->entityType(), $field_name, $entity->bundle());
+
+ $entity_access = edit_entity_access('update', $entity->entityType(), $entity);
+ $field_access = field_access('edit', $field_name, $entity->entityType(), $entity);
+ $editor = _edit_get_field_editor($items, $instance, $formatter_type);
+ if ($entity_access && $field_access && $editor != 'disabled') {
+ // Mark this field as editable and provide metadata through data- attributes.
+ $variables['attributes']['data-edit-field-label'] = $instance->definition['label'];
+ $variables['attributes']['data-edit-id'] = $entity->entityType() . ':' . $entity->id() . ':' . $field_name . ':' . $langcode . ':' . $view_mode;
+ $variables['attributes']['class'][] = 'edit-field';
+ $variables['attributes']['class'][] = 'edit-allowed';
+ $variables['attributes']['class'][] = 'edit-type-' . $editor;
+ if ($editor == 'direct-with-wysiwyg') {
+ $variables['attributes']['class'][] = 'edit-type-direct';
+ $format_id = $entity->{$field_name}[$langcode][0]['format'];
+ _edit_preprocess_field_wysiwyg($variables, $format_id);
+ }
+ }
+}
+
+/**
+ * Sets attributes on a field that have 'direct-with-wysiwyg' editor.
+ *
+ * @param array $variables
+ * An associative array containing: the key 'attributes'. See the
+ * theme_field() function for information about these variables.
+ * @param string $format_id
+ * A text format id.
+ *
+ * @see theme_field()
+ */
+function _edit_preprocess_field_wysiwyg(&$variables, $format_id) {
+ // Let the WYSIWYG editor know the text format.
+ $variables['attributes']['data-edit-text-format'] = $format_id;
+
+ // Let the JavaScript logic know whether transformation filters are used
+ // in this format, so it can decide whether to re-render the text or not.
+ $filter_types = filter_get_filter_types_by_format($format_id);
+ $transformation_filter_types = array(
+ FILTER_TYPE_TRANSFORM_REVERSIBLE,
+ FILTER_TYPE_TRANSFORM_IRREVERSIBLE
+ );
+ if (count(array_intersect($transformation_filter_types, $filter_types))) {
+ $variables['attributes']['class'][] = 'edit-text-with-transformation-filters';
+ }
+ else {
+ $variables['attributes']['class'][] = 'edit-text-without-transformation-filters';
+ }
+}
+
+/**
+ * Determines editor given a field, its instance info and its formatter.
+ *
+ * @param array $field
+ * The field's field array.
+ * @param FieldInstance $instance
+ * The field's instance info.
+ * @param string $formatter_type
+ * The field's formatter type name.
+ *
+ * @return string
+ * The editor: 'disabled', 'form', 'direct' or 'direct-with-wysiwyg'.
+ */
+function _edit_get_field_editor($items, FieldInstance $instance, $formatter_type) {
+ $field_name = $instance['field_name'];
+
+ // If the formatter doesn't contain the edit property, default it to 'form'
+ // editor, which should always work.
+ $formatter_info = field_info_formatter_types($formatter_type);
+ if (empty($formatter_info['edit']['editor'])) {
+ $formatter_info['edit']['editor'] = 'form';
+ }
+
+ $editor = $formatter_info['edit']['editor'];
+
+ // If editing is explicitly disabled for this field, return early to avoid
+ // any further processing.
+ if ($editor == 'disabled') {
+ return;
+ }
+
+ // If directly editable, check the cardinality. If the cardinality is greater
+ // than 1, use a form to edit the field.
+ if ($editor == 'direct') {
+ $field = field_info_field($field_name);
+ if ($field['cardinality'] != 1) {
+ $editor = 'form';
+ }
+ }
+
+ // If still directly editable, check whether "regular" direct editing (almost
+ // bare contentEditable) editing should be used or WYSIWYG-based direct
+ // editing should be used. In the latter case
+ if ($editor == 'direct') {
+ // If this field is configured to not use text processing; it is plain text
+ // "regular" direct editing should be used, which is already set.
+ // On the other hand, if it is configured to use text processing; then we
+ // must check whether 'direct-with-wysiwyg' or 'form' editor should be
+ // used.
+ if (!empty($instance['settings']['text_processing'])) {
+ $format_id = $items[0]['format'];
+ $editor = _edit_wysiwyg_get_field_editor($format_id);
+ }
+ }
+
+ return $editor;
+}
+
+/**
+ * Determines editor given a directly editable field with text processing.
+ *
+ * Given a text field (with cardinality 1) that defaults to 'direct' editor
+ * and has text processing enabled, check whether the text format allows it to
+ * use WYSIWYG-powered direct editing or whether 'form' based editing needs to
+ * be used.
+ *
+ * @param string|NULL $format_id
+ * The field's current text format.
+ *
+ * @return string
+ * The editor: 'direct-with-wysiwyg' or 'form'.
+ */
+function _edit_wysiwyg_get_field_editor($format_id = NULL) {
+ $wysiwyg_plugin = &drupal_static(__FUNCTION__);
+
+ // If no format is assigned yet, (e.g. when the field is still empty (NULL)),
+ // then provide form-based editing, so that the user is able to select a text
+ // format. (Direct editing doesn't allow the user to change the format.)
+ if (empty($format_id)) {
+ return 'form';
+ }
+
+ // NOTE: this code will pick the first processed text PropertyEditor widget
+ // plug-in that is available and consider that the only available choice.
+ // @todo: make it possible to have multiple processed text PropertyEditor
+ // widgets.
+ if (!isset($wysiwyg_plugin) && isset($format_id)) {
+ $definitions = drupal_container()->get('plugin.manager.edit.processed_text_editor')->getDefinitions();
+ if (count($definitions)) {
+ $plugin_ids = array_keys($definitions);
+ $plugin_id = $plugin_ids[0];
+ $wysiwyg_plugin = drupal_container()->get('plugin.manager.edit.processed_text_editor')->createInstance($plugin_id);
+ $wysiwyg_plugin->settingsAdded = FALSE;
+ }
+ }
+
+ // If no WYSIWYG editor is available, then fall back to form-based editing.
+ if (!isset($wysiwyg_plugin)) {
+ return 'form';
+ }
+ // If the WYSIWYG editor is not compatible with the current format, then fall
+ // back to form-based editing.
+ else {
+ $match = $wysiwyg_plugin->checkFormatCompatibility($format_id);
+ if (!$match) {
+ return 'form';
+ }
+ else if ($match) {
+ // Only load the WYSIWYG editor's JavaScript if it hasn't been already.
+ if ($wysiwyg_plugin->settingsAdded === FALSE) {
+ $definition = $wysiwyg_plugin->getDefinition();
+ drupal_add_library($definition['library']['module'], $definition['library']['name']);
+ $wysiwyg_plugin->addJsSettings();
+
+ // Let Create.js know which WYSIWYG editor widget it should use.
+ drupal_add_js(array('edit' => array(
+ 'wysiwygEditorWidgetName' => $definition['propertyEditorName'],
+ )), 'setting');
+ $wysiwyg_plugin->settingsAdded = TRUE;
+ }
+ return 'direct-with-wysiwyg';
+ }
+ }
+}
diff --git a/core/modules/edit/images/attention.png b/core/modules/edit/images/attention.png
new file mode 100644
index 0000000..6a35d1d
--- /dev/null
+++ b/core/modules/edit/images/attention.png
@@ -0,0 +1,4 @@
+PNG
+
+
IHDR *} `PLTEl՟FZݱ|В8 ʂx՜nϏ۫@EN;[cgUH7
tRNS =g-
Su -4_ IDATx^}ʇ @Qzn /g!ul6
; 0!f>>Ǐ kν_j㜻!0-@>8,i vҤrlWn?B(ijk*yT%Pv s=b_v>@?k&
+a|NciKBFUD^']d`5+5P
: IENDB`
\ No newline at end of file
diff --git a/core/modules/edit/images/close.png b/core/modules/edit/images/close.png
new file mode 100644
index 0000000..e3f98b8
--- /dev/null
+++ b/core/modules/edit/images/close.png
@@ -0,0 +1,4 @@
+PNG
+
+
IHDR (-S `PLTE >+ tRNS```00 mi IDATx^= H4ͼ!KsfQGx"LCyל(ux;z
KA.Jo
+E wy/2cdD@ҔLO%8F ?Q IENDB`
\ No newline at end of file
diff --git a/core/modules/edit/images/icon-edit-active.png b/core/modules/edit/images/icon-edit-active.png
new file mode 100644
index 0000000..ad84761
--- /dev/null
+++ b/core/modules/edit/images/icon-edit-active.png
@@ -0,0 +1,3 @@
+PNG
+
+
IHDR j `PLTE [ tRNS@ P00p`ϟ Dƙ IDATxe DQ8Ϩ/BDU9xV+D\?x@qWcF8wicS B}?v;Vf.V$JgX=Kضp0XS"iRw\:LL\~;Z5wu 5E)L IENDB`
\ No newline at end of file
diff --git a/core/modules/edit/images/icon-edit.png b/core/modules/edit/images/icon-edit.png
new file mode 100644
index 0000000..4f0dcc2
--- /dev/null
+++ b/core/modules/edit/images/icon-edit.png
@@ -0,0 +1,5 @@
+PNG
+
+
IHDR j PLTE̻ʪ̡̜ˠ̣̽¼Ƿ ʨªZ(e +tRNSϟ `π@`0p0p`0Pϟϟ c IDATxeW0 {W
+H"ʵ,y{Hpyo?mf,RBRxBvL;&LPJaRb\(Tbn(1wϔJ)ԈkS
+58äT^4 P6c}[i <ާ'-+HP>K IENDB`
\ No newline at end of file
diff --git a/core/modules/edit/images/throbber.gif b/core/modules/edit/images/throbber.gif
new file mode 100644
index 0000000..f2603e8
--- /dev/null
+++ b/core/modules/edit/images/throbber.gif
@@ -0,0 +1,6 @@
+GIF89a Ž{{{ !NETSCAPE2.0 ! , @`)KkŏA|ad0L9~\8L Ǹ0, i{qBC~H'JRĨ`f4&a ! , `sɺ(t34M!-0,l#))9 !q(i<hB 3˥ (`
,9%cm
+b0AY_e ! , dRj:ړtG8$c02P hi, ǑQ0X[LEck5`ڭP2F!
a @q dfz{lQ zK ! , `BjR:$BPFq(ˢ J@0i-2͇ Qck6[G`m:pQ4fqow ! , d1j}MS@S\ H9 #IrPØi8f..r$ł|nl
+*T\![͂l,Q0@(KFMO{
ql{ ! , ^I 3a!P(Ol~`8 LÇ!@$Lgx|f,`"`Jʼn"cUP
+GAw< tz ! , h9C 4kk\
&I&l
)F-P@chH!ш4<
+ x@R`0C"8h0BfgX(rc}Y
+1 ! , aйҚX]mKl[)"aĢ\*"$t	@1pP`,`I,8 S 8U,Q`(d`3-qH$1T{ ;
\ No newline at end of file
diff --git a/core/modules/edit/includes/form.inc b/core/modules/edit/includes/form.inc
new file mode 100644
index 0000000..cdf9935
--- /dev/null
+++ b/core/modules/edit/includes/form.inc
@@ -0,0 +1,105 @@
+ $form_state['field_name']);
+ field_attach_form($entity->entityType(), $entity, $form, $form_state, $langcode, $options);
+
+ $form['#validate'][] = 'edit_field_form_validate';
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+
+ // Simplify the form.
+ _simplify_edit_field_edit_form($form);
+
+ return $form;
+}
+
+/**
+ * Simplifies the field edit form for in-place editing.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ */
+function _simplify_edit_field_edit_form(array &$form) {
+ $elements = element_children($form);
+
+ // Required internal form properties.
+ $internal_elements = array('actions', 'form_build_id', 'form_token', 'form_id');
+
+ // Calculate the remaining form elements.
+ $remaining_elements = array_diff($elements, $internal_elements);
+
+ // Only simplify the form if there is a single element remaining.
+ if (count($remaining_elements) === 1) {
+ $element = $remaining_elements[0];
+
+ if ($form[$element]['#type'] == 'container') {
+ $language = $form[$element]['#language'];
+ $children = element_children($form[$element][$language]);
+
+ // Certain fields require different processing depending on the form
+ // structure.
+ if (count($children) == 0) {
+ // Checkbox elements don't have a title.
+ if ($form[$element][$language]['#type'] != 'checkbox') {
+ $form[$element][$language]['#title_display'] = 'invisible';
+ }
+ }
+ elseif (count($children) == 1) {
+ $form[$element][$language][0]['value']['#title_display'] = 'invisible';
+
+ // UX improvement: make the number of rows of textarea form elements
+ // fit the content. (i.e. no wads of whitespace)
+ if (isset($form[$element][$language][0]['value']['#type'])
+ && $form[$element][$language][0]['value']['#type'] == 'textarea')
+ {
+ $lines = count(explode("\n", $form[$element][$language][0]['value']['#default_value']));
+ $form[$element][$language][0]['value']['#rows'] = $lines + 1;
+ }
+ }
+ }
+ }
+
+ // Make it easy for the JavaScript to identify the submit button.
+ $form['actions']['submit']['#attributes'] = array('class' => array('edit-form-submit'));
+}
+
+/**
+ * Validate field editing form.
+ *
+ * @see edit_field_form()
+ *
+ * @todo: BLOCKED_ON(Drupal core, http://drupal.org/node/1846648)
+ * Clean up once that issue is solved.
+ */
+function edit_field_form_validate(array $form, array &$form_state) {
+ $entity = $form_state['entity'];
+ $options = array('field_name' => $form_state['field_name']);
+
+ // 'submit' in D8 is for "building the entity object", not for actual
+ // submission. It appears though that if there were no validation errors, it
+ // is submitted automatically.
+ field_attach_submit($entity->entityType(), $entity, $form, $form_state, $options);
+
+ // Validation.
+ field_attach_form_validate($entity->entityType(), $entity, $form, $form_state, $options);
+}
diff --git a/core/modules/edit/includes/pages.inc b/core/modules/edit/includes/pages.inc
new file mode 100644
index 0000000..10b682d
--- /dev/null
+++ b/core/modules/edit/includes/pages.inc
@@ -0,0 +1,165 @@
+bundle());
+ if (empty($field_instance)) {
+ throw new NotFoundHttpException();
+ }
+
+ $form_state = array(
+ 'entity' => $entity,
+ 'field_name' => $field_name,
+ 'langcode' => $langcode,
+ 'no_redirect' => TRUE,
+ 'build_info' => array('args' => array()),
+ );
+ form_load_include($form_state, 'inc', 'edit', 'includes/form');
+ $form = drupal_build_form('edit_field_form', $form_state);
+
+ if (!empty($form_state['executed'])) {
+ // Retrieve the updated entity, save it and render only the modified field.
+ $entity = $form_state['entity'];
+ $entity->save();
+ $output = field_view_field($entity->entityType(), $entity, $field_name, $view_mode, $langcode);
+
+ $response->addCommand(new FieldFormSavedCommand(drupal_render($output)));
+ }
+ else {
+ $response->addCommand(new FieldFormCommand(drupal_render($form)));
+
+ $errors = form_get_errors();
+ if (count($errors)) {
+ $response->addCommand(new FieldFormValidationErrorsCommand(theme('status_messages')));
+ }
+ }
+
+ // When working with a hidden form, we don't want any CSS or JS to be loaded.
+ if (isset($_POST['nocssjs']) && $_POST['nocssjs'] === 'true') {
+ drupal_static_reset('drupal_add_css');
+ drupal_static_reset('drupal_add_js');
+ }
+
+ return $response;
+}
+
+/**
+ * Page callback: render a processed text field without transformation filters.
+ *
+ * @param string $entity_type
+ * The entity type of the entity of which a processed text field is being
+ * rerendered.
+ * @param int $entity_id
+ * The entity ID of the entity of which a processed text field is being
+ * rerendered.
+ * @param string $field_name
+ * The name of the (processed text) field that that is being rerendered
+ * @param string $langcode
+ * The name of the language for which the processed text field is being
+ * rererendered.
+ * @param string $view_mode
+ * The view mode the processed text field should be rerendered in.
+ * @return array
+ * A render array.
+ */
+function edit_text_field_render_without_transformation_filters($entity_type, $entity_id, $field_name, $langcode, $view_mode) {
+ $response = new AjaxResponse();
+
+ // Ensure the entity type is valid.
+ if (empty($entity_type)) {
+ throw new NotFoundHttpException();
+ }
+
+ $entity_info = entity_get_info($entity_type);
+ if (!$entity_info) {
+ throw new NotFoundHttpException();
+ }
+
+ $entity = entity_load($entity_type, $entity_id);
+ if (!$entity) {
+ throw new NotFoundHttpException();
+ }
+
+ // Ensure a valid language code is set.
+ $langcode = field_valid_language($langcode);
+
+ // Ensure access to update this particular entity is granted.
+ if (!edit_entity_access('update', $entity_type, $entity)) {
+ throw new AccessDeniedHttpException();
+ }
+
+ // Ensure access to update this particular field is granted.
+ if (!field_access('edit', $field_name, $entity_type, $entity)) {
+ throw new AccessDeniedHttpException();
+ }
+
+ $field_instance = field_info_instance($entity_type, $field_name, $entity->bundle());
+ if (empty($field_instance)) {
+ throw new NotFoundHttpException();
+ }
+
+ // Render the field in our custom display mode; retrieve the re-rendered
+ // markup, this is what we're after.
+ $field_output = field_view_field($entity_type, $entity, $field_name, 'edit-render-without-transformation-filters');
+ $output = $field_output[0]['#markup'];
+ $response->addCommand(new FieldRenderedWithoutTransformationFiltersCommand($output));
+
+ return $response;
+}
diff --git a/core/modules/edit/js/app.js b/core/modules/edit/js/app.js
new file mode 100644
index 0000000..c5d5be5
--- /dev/null
+++ b/core/modules/edit/js/app.js
@@ -0,0 +1,318 @@
+/**
+ * @file
+ * A Backbone View that is the central app controller.
+ */
+(function ($, _, Backbone, Drupal, VIE) {
+
+"use strict";
+
+ Drupal.edit = Drupal.edit || {};
+ Drupal.edit.EditAppView = Backbone.View.extend({
+ vie: null,
+ domService: null,
+
+ // Configuration for state handling.
+ states: [],
+ activeEditorStates: [],
+ singleEditorStates: [],
+
+ // State.
+ $entityElements: [],
+
+ /**
+ * Implements Backbone Views' initialize() function.
+ */
+ initialize: function() {
+ _.bindAll(this, 'appStateChange', 'acceptEditorStateChange', 'editorStateChange');
+
+ // VIE instance for Edit.
+ this.vie = new VIE();
+ // Use our custom DOM parsing service until RDFa is available.
+ this.vie.use(new this.vie.EditService());
+ this.domService = this.vie.service('edit');
+
+ // Instantiate configuration for state handling.
+ this.states = [
+ null, 'inactive', 'candidate', 'highlighted',
+ 'activating', 'active', 'changed', 'saving', 'saved', 'invalid'
+ ];
+ this.activeEditorStates = ['activating', 'active'];
+ this.singleEditorStates = _.union(['highlighted'], this.activeEditorStates);
+
+ // Use Create's Storage widget.
+ this.$el.createStorage({
+ vie: this.vie,
+ editableNs: 'createeditable'
+ });
+
+ // Instantiate an EditableEntity widget for each property.
+ var that = this;
+ this.$entityElements = this.domService.findSubjectElements().each(function() {
+ $(this).createEditable({
+ vie: that.vie,
+ disabled: true,
+ state: 'inactive',
+ acceptStateChange: that.acceptEditorStateChange,
+ statechange: function(event, data) {
+ that.editorStateChange(data.previous, data.current, data.propertyEditor);
+ },
+ decoratePropertyEditor: function(data) {
+ that.decorateEditor(data.propertyEditor);
+ }
+ });
+ });
+
+ // Instantiate OverlayView
+ var overlayView = new Drupal.edit.views.OverlayView({
+ model: this.model
+ });
+
+ // Instantiate MenuView
+ var editMenuView = new Drupal.edit.views.MenuView({
+ el: this.el,
+ model: this.model
+ });
+
+ // When view/edit mode is toggled in the menu, update the editor widgets.
+ this.model.on('change:isViewing', this.appStateChange);
+ },
+
+ /**
+ * Sets the state of PropertyEditor widgets when edit mode begins or ends.
+ *
+ * Should be called whenever EditAppModel's "isViewing" changes.
+ */
+ appStateChange: function() {
+ // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133, https://github.com/bergie/create/issues/140)
+ // We're currently setting the state on EditableEntity widgets instead of
+ // PropertyEditor widgets, because of
+ // https://github.com/bergie/create/issues/133.
+ var newState = (this.model.get('isViewing')) ? 'inactive' : 'candidate';
+ this.$entityElements.each(function() {
+ $(this).createEditable('setState', newState);
+ });
+ },
+
+ /**
+ * Accepts or reject editor (PropertyEditor) state changes.
+ *
+ * This is what ensures that the app is in control of what happens.
+ *
+ * @param from
+ * The previous state.
+ * @param to
+ * The new state.
+ * @param predicate
+ * The predicate of the property for which the state change is happening.
+ * @param context
+ * The context that is trying to trigger the state change.
+ * @param callback
+ * The callback function that should receive the state acceptance result.
+ */
+ acceptEditorStateChange: function(from, to, predicate, context, callback) {
+ var accept = true;
+
+ // If the app is in view mode, then reject all state changes except for
+ // those to 'inactive'.
+ if (this.model.get('isViewing')) {
+ if (to !== 'inactive') {
+ accept = false;
+ }
+ }
+ // Handling of edit mode state changes is more granular.
+ else {
+ // In general, enforce the states sequence. Disallow going back from a
+ // "later" state to an "earlier" state, except in explicitly allowed
+ // cases.
+ if (_.indexOf(this.states, from) > _.indexOf(this.states, to)) {
+ accept = false;
+ // Allow: activating/active -> candidate.
+ // Necessary to stop editing a property.
+ if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') {
+ accept = true;
+ }
+ // Allow: changed/invalid -> candidate.
+ // Necessary to stop editing a property when it is changed or invalid.
+ else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
+ accept = true;
+ }
+ // Allow: highlighted -> candidate.
+ // Necessary to stop highlighting a property.
+ else if (from === 'highlighted' && to === 'candidate') {
+ accept = true;
+ }
+ // Allow: saved -> candidate.
+ // Necessary when successfully saved a property.
+ else if (from === 'saved' && to === 'candidate') {
+ accept = true;
+ }
+ // Allow: invalid -> saving.
+ // Necessary to be able to save a corrected, invalid property.
+ else if (from === 'invalid' && to === 'saving') {
+ accept = true;
+ }
+ }
+
+ // If it's not against the general principle, then here are more
+ // disallowed cases to check.
+ if (accept) {
+ // Ensure only one editor (field) at a time may be higlighted or active.
+ if (from === 'candidate' && _.indexOf(this.singleEditorStates, to) !== -1) {
+ if (this.model.get('highlightedEditor') || this.model.get('activeEditor')) {
+ accept = false;
+ }
+ }
+ // Reject going from activating/active to candidate because of a
+ // mouseleave.
+ else if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') {
+ if (context && context.reason === 'mouseleave') {
+ accept = false;
+ }
+ }
+ // When attempting to stop editing a changed/invalid property, ask for
+ // confirmation.
+ else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
+ if (context && context.reason === 'mouseleave') {
+ accept = false;
+ }
+ else {
+ // Check whether the transition has been confirmed?
+ if (context && context.confirmed) {
+ accept = true;
+ }
+ // Confirm this transition.
+ else {
+ // The callback will be called from the helper function.
+ this._confirmStopEditing(callback);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ callback(accept);
+ },
+
+ /**
+ * Asks the user to confirm whether he wants to stop editing via a modal.
+ *
+ * @param acceptCallback
+ * The callback function as passed to acceptEditorStateChange(). This
+ * callback function will be called with the user's choice.
+ *
+ * @see acceptEditorStateChange()
+ */
+ _confirmStopEditing: function(acceptCallback) {
+ // Only instantiate if there isn't a modal instance visible yet.
+ if (!this.model.get('activeModal')) {
+ var that = this;
+ var modal = new Drupal.edit.views.ModalView({
+ model: this.model,
+ message: Drupal.t('You have unsaved changes'),
+ buttons: [
+ { action: 'discard', classes: 'gray-button', label: Drupal.t('Discard changes') },
+ { action: 'save', classes: 'blue-button', label: Drupal.t('Save') }
+ ],
+ callback: function(action) {
+ // The active modal has been removed.
+ that.model.set('activeModal', null);
+ if (action === 'discard') {
+ acceptCallback(true);
+ }
+ else {
+ acceptCallback(false);
+ var editor = that.model.get('activeEditor');
+ editor.options.widget.setState('saving', editor.options.property);
+ }
+ }
+ });
+ this.model.set('activeModal', modal);
+ // The modal will set the activeModal property on the model when rendering
+ // to prevent multiple modals from being instantiated.
+ modal.render();
+ }
+ else {
+ // Reject as there is still an open transition waiting for confirmation.
+ acceptCallback(false);
+ }
+ },
+
+ /**
+ * Reacts to editor (PropertyEditor) state changes; tracks global state.
+ *
+ * @param from
+ * The previous state.
+ * @param to
+ * The new state.
+ * @param editor
+ * The PropertyEditor widget object.
+ */
+ editorStateChange: function(from, to, editor) {
+ // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133)
+ // Get rid of this once that issue is solved.
+ if (!editor) {
+ return;
+ }
+ else {
+ editor.stateChange(from, to);
+ }
+
+ // Keep track of the highlighted editor in the global state.
+ if (_.indexOf(this.singleEditorStates, to) !== -1 && this.model.get('highlightedEditor') !== editor) {
+ this.model.set('highlightedEditor', editor);
+ }
+ else if (this.model.get('highlightedEditor') === editor && to === 'candidate') {
+ this.model.set('highlightedEditor', null);
+ }
+
+ // Keep track of the active editor in the global state.
+ if (_.indexOf(this.activeEditorStates, to) !== -1 && this.model.get('activeEditor') !== editor) {
+ this.model.set('activeEditor', editor);
+ }
+ else if (this.model.get('activeEditor') === editor && to === 'candidate') {
+ this.model.set('activeEditor', null);
+ }
+
+ // Propagate the state change to the decoration and toolbar views.
+ // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133)
+ // Uncomment this once that issue is solved.
+ // editor.decorationView.stateChange(from, to);
+ // editor.toolbarView.stateChange(from, to);
+ },
+
+ /**
+ * Decorates an editor (PropertyEditor).
+ *
+ * Upon the page load, all appropriate editors are initialized and decorated
+ * (i.e. even before anything of the editing UI becomes visible; even before
+ * edit mode is enabled).
+ *
+ * @param editor
+ * The PropertyEditor widget object.
+ */
+ decorateEditor: function(editor) {
+ // Toolbars are rendered "on-demand" (highlighting or activating).
+ // They are a sibling element before the editor's DOM element.
+ editor.toolbarView = new Drupal.edit.views.ToolbarView({
+ editor: editor,
+ $storageWidgetEl: this.$el
+ });
+
+ // Decorate the editor's DOM element depending on its state.
+ editor.decorationView = new Drupal.edit.views.PropertyEditorDecorationView({
+ el: editor.element,
+ editor: editor,
+ toolbarId: editor.toolbarView.getId()
+ });
+
+ // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133)
+ // Get rid of this once that issue is solved.
+ editor.options.widget.element.on('createeditablestatechange', function(event, data) {
+ editor.decorationView.stateChange(data.previous, data.current);
+ editor.toolbarView.stateChange(data.previous, data.current);
+ });
+ }
+ });
+
+})(jQuery, _, Backbone, Drupal, VIE);
diff --git a/core/modules/edit/js/backbone.drupalform.js b/core/modules/edit/js/backbone.drupalform.js
new file mode 100644
index 0000000..cc0e5c3
--- /dev/null
+++ b/core/modules/edit/js/backbone.drupalform.js
@@ -0,0 +1,157 @@
+/**
+ * @file
+ * Backbone.sync implementation for Edit. This is the beating heart.
+ */
+(function (jQuery, Backbone, Drupal) {
+
+"use strict";
+
+Backbone.defaultSync = Backbone.sync;
+Backbone.sync = function(method, model, options) {
+ if (options.editor.options.editorName === 'form') {
+ return Backbone.syncDrupalFormWidget(method, model, options);
+ }
+ else {
+ return Backbone.syncDirect(method, model, options);
+ }
+};
+
+/**
+ * Performs syncing for "form" PredicateEditor widgets.
+ *
+ * Implemented on top of Form API and the AJAX commands framework. Sets up
+ * scoped AJAX command closures specifically for a given PredicateEditor widget
+ * (which contains a pre-existing form). By submitting the form through
+ * Drupal.ajax and leveraging Drupal.ajax' ability to have scoped (per-instance)
+ * command implementations, we are able to update the VIE model, re-render the
+ * form when there are validation errors and ensure no Drupal.ajax memory leaks.
+ *
+ * @see Drupal.edit.util.form
+ */
+Backbone.syncDrupalFormWidget = function(method, model, options) {
+ if (method === 'update') {
+ var predicate = options.editor.options.property;
+
+ var $formContainer = options.editor.$formContainer;
+ var $submit = $formContainer.find('.edit-form-submit');
+ var base = $submit.attr('id');
+
+ // Successfully saved.
+ Drupal.ajax[base].commands.editFieldFormSaved = function(ajax, response, status) {
+ Drupal.edit.util.form.unajaxifySaving(jQuery(ajax.element));
+
+ // Call Backbone.sync's success callback with the rerendered field.
+ var changedAttributes = {};
+ // @todo: POSTPONED_ON(Drupal core, http://drupal.org/node/1784216)
+ // Once full JSON-LD support in Drupal core lands, we can ensure that the
+ // models that VIE maintains are properly updated.
+ changedAttributes[predicate] = 'JSON-LD representation N/A yet.';
+ changedAttributes[predicate + '/rendered'] = response.data;
+ options.success(changedAttributes);
+ };
+
+ // Unsuccessfully saved; validation errors.
+ Drupal.ajax[base].commands.editFieldFormValidationErrors = function(ajax, response, status) {
+ // Call Backbone.sync's error callback with the validation error messages.
+ options.error(response.data);
+ };
+
+ // The edit_field_form AJAX command is only called upon loading the form for
+ // the first time, and when there are validation errors in the form; Form
+ // API then marks which form items have errors. Therefor, we have to replace
+ // the existing form, unbind the existing Drupal.ajax instance and create a
+ // new Drupal.ajax instance.
+ Drupal.ajax[base].commands.editFieldForm = function(ajax, response, status) {
+ Drupal.edit.util.form.unajaxifySaving(jQuery(ajax.element));
+
+ Drupal.ajax.prototype.commands.insert(ajax, {
+ data: response.data,
+ selector: '#' + $formContainer.attr('id') + ' form'
+ });
+
+ // Create a Drupa.ajax instance for the re-rendered ("new") form.
+ var $newSubmit = $formContainer.find('.edit-form-submit');
+ Drupal.edit.util.form.ajaxifySaving({ nocssjs: false }, $newSubmit);
+ };
+
+ // Click the form's submit button; the scoped AJAX commands above will
+ // handle the server's response.
+ $submit.trigger('click.edit');
+ }
+};
+
+/**
+* Performs syncing for "direct" PredicateEditor widgets.
+ *
+ * @see Backbone.syncDrupalFormWidget()
+ * @see Drupal.edit.util.form
+ */
+Backbone.syncDirect = function(method, model, options) {
+ if (method === 'update') {
+ var fillAndSubmitForm = function(value) {
+ jQuery('#edit_backstage form')
+ // Fill in the value in any that isn't hidden or a submit button.
+ .find(':input[type!="hidden"][type!="submit"]:not(select)').val(value).end()
+ // Submit the form.
+ .find('.edit-form-submit').trigger('click.edit');
+ };
+ var entity = options.editor.options.entity;
+ var predicate = options.editor.options.property;
+ var value = model.get(predicate);
+
+ // If form doesn't already exist, load it and then submit.
+ if (jQuery('#edit_backstage form').length === 0) {
+ var formOptions = {
+ propertyID: Drupal.edit.util.calcPropertyID(entity, predicate),
+ $editorElement: options.editor.element,
+ nocssjs: true
+ };
+ Drupal.edit.util.form.load(formOptions, function(form, ajax) {
+ // Create a backstage area for storing forms that are hidden from view
+ // (hence "backstage" — since the editing doesn't happen in the form, it
+ // happens "directly" in the content, the form is only used for saving).
+ jQuery(Drupal.theme('editBackstage', { id: 'edit_backstage' })).appendTo('body');
+ // Direct forms are stuffed into #edit_backstage, apparently.
+ jQuery('#edit_backstage').append(form);
+ // Disable the browser's HTML5 validation; we only care about server-
+ // side validation. (Not disabling this will actually cause problems
+ // because browsers don't like to set HTML5 validation errors on hidden
+ // forms.)
+ jQuery('#edit_backstage form').attr('novalidate', true);
+ var $submit = jQuery('#edit_backstage form .edit-form-submit');
+ var base = Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
+
+ // Successfully saved.
+ Drupal.ajax[base].commands.editFieldFormSaved = function (ajax, response, status) {
+ Drupal.edit.util.form.unajaxifySaving(jQuery(ajax.element));
+ jQuery('#edit_backstage form').remove();
+
+ options.success();
+ };
+
+ // Unsuccessfully saved; validation errors.
+ Drupal.ajax[base].commands.editFieldFormValidationErrors = function(ajax, response, status) {
+ // Call Backbone.sync's error callback with the validation error messages.
+ options.error(response.data);
+ };
+
+ // The editFieldForm AJAX command is only called upon loading the form
+ // for the first time, and when there are validation errors in the form;
+ // Form API then marks which form items have errors. This is useful for
+ // "form" editors, but pointless for "direct" editors: the form itself
+ // won't be visible at all anyway! Therefor, we ignore the new form and
+ // we continue to use the existing form.
+ Drupal.ajax[base].commands.editFieldForm = function(ajax, response, status) {
+ // no-op
+ };
+
+ fillAndSubmitForm(value);
+ });
+ }
+ else {
+ fillAndSubmitForm(value);
+ }
+ }
+};
+
+})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/createjs/editable.js b/core/modules/edit/js/createjs/editable.js
new file mode 100644
index 0000000..aac1ed2
--- /dev/null
+++ b/core/modules/edit/js/createjs/editable.js
@@ -0,0 +1,43 @@
+/**
+ * @file
+ * Determines which editor to use based on a class attribute.
+ */
+(function (jQuery, drupalSettings) {
+
+"use strict";
+
+ jQuery.widget('Drupal.createEditable', jQuery.Midgard.midgardEditable, {
+ _create: function() {
+ this.vie = this.options.vie;
+
+ this.options.domService = 'edit';
+ this.options.predicateSelector = '*'; //'.edit-field.edit-allowed';
+
+ this.options.editors.direct = {
+ widget: 'drupalContentEditableWidget',
+ options: {}
+ };
+ this.options.editors['direct-with-wysiwyg'] = {
+ widget: drupalSettings.edit.wysiwygEditorWidgetName,
+ options: {}
+ };
+ this.options.editors.form = {
+ widget: 'drupalFormWidget',
+ options: {}
+ };
+
+ jQuery.Midgard.midgardEditable.prototype._create.call(this);
+ },
+
+ _propertyEditorName: function(data) {
+ if (jQuery(this.element).hasClass('edit-type-direct')) {
+ if (jQuery(this.element).hasClass('edit-type-direct-with-wysiwyg')) {
+ return 'direct-with-wysiwyg';
+ }
+ return 'direct';
+ }
+ return 'form';
+ }
+ });
+
+})(jQuery, drupalSettings);
diff --git a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js b/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
new file mode 100644
index 0000000..c773e6e
--- /dev/null
+++ b/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
@@ -0,0 +1,110 @@
+/**
+ * @file
+ * Override of Create.js' default "base" (plain contentEditable) widget.
+ */
+(function (jQuery, Drupal) {
+
+"use strict";
+
+ jQuery.widget('Drupal.drupalContentEditableWidget', jQuery.Create.editWidget, {
+
+ /**
+ * Implements jQuery UI widget factory's _init() method.
+ *
+ * @todo: POSTPONED_ON(Create.js, https://github.com/bergie/create/issues/142)
+ * Get rid of this once that issue is solved.
+ */
+ _init: function() {},
+
+ /**
+ * Implements Create's _initialize() method.
+ */
+ _initialize: function() {
+ var that = this;
+
+ // Sets the state to 'activated' upon clicking the element.
+ this.element.on("click.edit", function(event) {
+ event.stopPropagation();
+ event.preventDefault();
+ that.options.activated();
+ });
+
+ // Sets the state to 'changed' whenever the content has changed.
+ var before = jQuery.trim(this.element.text());
+ this.element.on('keyup paste', function (event) {
+ if (that.options.disabled) {
+ return;
+ }
+ var current = jQuery.trim(that.element.text());
+ if (before !== current) {
+ before = current;
+ that.options.changed(current);
+ }
+ });
+ },
+
+ /**
+ * Makes this PropertyEditor widget react to state changes.
+ */
+ stateChange: function(from, to) {
+ switch (to) {
+ case 'inactive':
+ break;
+ case 'candidate':
+ if (from !== 'inactive') {
+ // Removes the "contenteditable" attribute.
+ this.disable();
+ this._removeValidationErrors();
+ this._cleanUp();
+ }
+ break;
+ case 'highlighted':
+ break;
+ case 'activating':
+ break;
+ case 'active':
+ // Sets the "contenteditable" attribute to "true".
+ this.enable();
+ break;
+ case 'changed':
+ break;
+ case 'saving':
+ this._removeValidationErrors();
+ break;
+ case 'saved':
+ break;
+ case 'invalid':
+ break;
+ }
+ },
+
+ /**
+ * Removes validation errors' markup changes, if any.
+ *
+ * Note: this only needs to happen for type=direct, because for type=direct,
+ * the property DOM element itself is modified; this is not the case for
+ * type=form.
+ */
+ _removeValidationErrors: function() {
+ this.element
+ .removeClass('edit-validation-error')
+ .next('.edit-validation-errors').remove();
+ },
+
+ /**
+ * Cleans up after the widget has been saved.
+ *
+ * Note: this is where the Create.Storage and accompanying Backbone.sync
+ * abstractions "leak" implementation details. That is only the case because
+ * we have to use Drupal's Form API as a transport mechanism. It is
+ * unfortunately a stateful transport mechanism, and that's why we have to
+ * clean it up here. This clean-up is only necessary when canceling the
+ * editing of a property after having attempted to save at least once.
+ */
+ _cleanUp: function() {
+ Drupal.edit.util.form.unajaxifySaving(jQuery('#edit_backstage form .edit-form-submit'));
+ jQuery('#edit_backstage form').remove();
+ }
+ });
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/js/createjs/editingWidgets/formwidget.js b/core/modules/edit/js/createjs/editingWidgets/formwidget.js
new file mode 100644
index 0000000..9a5153d
--- /dev/null
+++ b/core/modules/edit/js/createjs/editingWidgets/formwidget.js
@@ -0,0 +1,149 @@
+/**
+ * @file
+ * Form-based Create.js widget for structured content in Drupal.
+ */
+(function ($, Drupal) {
+
+"use strict";
+
+ $.widget('Drupal.drupalFormWidget', $.Create.editWidget, {
+
+ id: null,
+ $formContainer: null,
+
+ /**
+ * Implements jQuery UI widget factory's _init() method.
+ *
+ * @todo: POSTPONED_ON(Create.js, https://github.com/bergie/create/issues/142)
+ * Get rid of this once that issue is solved.
+ */
+ _init: function() {},
+
+ /**
+ * Implements Create's _initialize() method.
+ */
+ _initialize: function() {
+ // Sets the state to 'activating' upon clicking the element.
+ var that = this;
+ this.element.on("click.edit", function(event) {
+ event.stopPropagation();
+ event.preventDefault();
+ that.options.activating();
+ });
+ },
+
+ /**
+ * Makes this PropertyEditor widget react to state changes.
+ */
+ stateChange: function(from, to) {
+ switch (to) {
+ case 'inactive':
+ break;
+ case 'candidate':
+ if (from !== 'inactive') {
+ this.disable();
+ }
+ break;
+ case 'highlighted':
+ break;
+ case 'activating':
+ this.enable();
+ break;
+ case 'active':
+ break;
+ case 'changed':
+ break;
+ case 'saving':
+ break;
+ case 'saved':
+ break;
+ case 'invalid':
+ break;
+ }
+ },
+
+ /**
+ * Enables the widget.
+ */
+ enable: function () {
+ var $editorElement = $(this.options.widget.element);
+ var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property);
+
+ // Generate a DOM-compatible ID for the form container DOM element.
+ this.id = 'edit-form-for-' + propertyID.replace(/\//g, '_');
+
+ // Render form container.
+ this.$formContainer = $(Drupal.theme('editFormContainer', {
+ id: this.id,
+ loadingMsg: Drupal.t('Loading…')}
+ ));
+ this.$formContainer
+ .find('.edit-form')
+ .addClass('edit-editable edit-highlighted edit-editing')
+ .css('background-color', $editorElement.css('background-color'));
+
+ // Insert form container in DOM.
+ if ($editorElement.css('display') === 'inline') {
+ // @todo: POSTPONED_ON(Drupal core, title/author/date as Entity Properties)
+ // This is untested in Drupal 8, because in Drupal 8 we don't yet
+ // have the ability to edit the node title/author/date, because they
+ // haven't been converted into Entity Properties yet, and they're the
+ // only examples in core of "display: inline" properties.
+ this.$formContainer.prependTo($editorElement.offsetParent());
+
+ var pos = $editorElement.position();
+ this.$formContainer.css('left', pos.left).css('top', pos.top);
+ }
+ else {
+ this.$formContainer.insertBefore($editorElement);
+ }
+
+ // Load form, insert it into the form container and attach event handlers.
+ var widget = this;
+ var formOptions = {
+ propertyID: propertyID,
+ $editorElement: $editorElement,
+ nocssjs: false
+ };
+ Drupal.edit.util.form.load(formOptions, function(form, ajax) {
+ Drupal.ajax.prototype.commands.insert(ajax, {
+ data: form,
+ selector: '#' + widget.id + ' .placeholder'
+ });
+
+ var $submit = widget.$formContainer.find('.edit-form-submit');
+ Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
+ widget.$formContainer
+ .on('formUpdated.edit', ':input', function () {
+ // Sets the state to 'changed'.
+ widget.options.changed();
+ })
+ .on('keypress.edit', 'input', function (event) {
+ if (event.keyCode === 13) {
+ return false;
+ }
+ });
+
+ // Sets the state to 'activated'.
+ widget.options.activated();
+ });
+ },
+
+ /**
+ * Disables the widget.
+ */
+ disable: function () {
+ if (this.$formContainer === null) {
+ return;
+ }
+
+ Drupal.edit.util.form.unajaxifySaving(this.$formContainer.find('.edit-form-submit'));
+ this.$formContainer
+ .off('change.edit', ':input')
+ .off('keypress.edit', 'input')
+ .remove();
+ this.$formContainer = null;
+ }
+ });
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/js/createjs/storage.js b/core/modules/edit/js/createjs/storage.js
new file mode 100644
index 0000000..580ff82
--- /dev/null
+++ b/core/modules/edit/js/createjs/storage.js
@@ -0,0 +1,11 @@
+/**
+ * @file
+ * Subclasses jQuery.Midgard.midgardStorage to have consistent namespaces.
+ */
+(function(jQuery) {
+
+"use strict";
+
+ jQuery.widget('Drupal.createStorage', jQuery.Midgard.midgardStorage, {});
+
+})(jQuery);
diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js
new file mode 100644
index 0000000..2c42068
--- /dev/null
+++ b/core/modules/edit/js/edit.js
@@ -0,0 +1,55 @@
+/**
+ * @file
+ * Behaviors for Edit, including the one that initializes Edit's EditAppView.
+ */
+(function ($, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+Drupal.behaviors.editDiscoverEditables = {
+ attach: function(context) {
+ // @todo BLOCKED_ON(VIE.js, how to let VIE know that some content was removed and how to scan new content for VIE entities, to make them editable?)
+ // Also see ToolbarView.save().
+ // We need to separate the discovery of editables if we want updated
+ // or new content (added by code other than Edit) to be detected
+ // automatically. Once we implement this, we'll be able to get rid of all
+ // calls to Drupal.edit.domService.findSubjectElements() :)
+ }
+};
+
+/**
+ * Attach toggling behavior and in-place editing.
+ */
+Drupal.behaviors.edit = {
+ attach: function(context) {
+ $('#edit_view-edit-toggles').once('edit-init', Drupal.edit.init);
+
+ // As soon as there is at least one editable field, show the Edit tab in the
+ // toolbar.
+ if ($(context).find('.edit-field.edit-allowed').length) {
+ $('.toolbar .icon-edit.edit-nothing-editable-hidden').removeClass('edit-nothing-editable-hidden');
+ }
+ }
+};
+
+Drupal.edit.init = function() {
+ // Instantiate EditAppView, which is the controller of it all. EditAppModel
+ // instance tracks global state (viewing/editing in-place).
+ var appModel = new Drupal.edit.models.EditAppModel();
+ var app = new Drupal.edit.EditAppView({
+ el: $('body'),
+ model: appModel
+ });
+
+ // Instantiate EditRouter.
+ var editRouter = new Drupal.edit.routers.EditRouter({
+ appModel: appModel
+ });
+
+ // Start Backbone's history/route handling.
+ Backbone.history.start();
+};
+
+})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/models/edit-app-model.js b/core/modules/edit/js/models/edit-app-model.js
new file mode 100644
index 0000000..b6ff36f
--- /dev/null
+++ b/core/modules/edit/js/models/edit-app-model.js
@@ -0,0 +1,22 @@
+/**
+ * @file
+ * A Backbone Model that models the current Edit application state.
+ */
+(function(Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.models = Drupal.edit.models || {};
+Drupal.edit.models.EditAppModel = Backbone.Model.extend({
+ defaults: {
+ // We always begin in view mode.
+ isViewing: true,
+ highlightedEditor: null,
+ activeEditor: null,
+ // Reference to a ModalView-instance if a transition requires confirmation.
+ activeModal: null
+ }
+});
+
+})(Backbone, Drupal);
diff --git a/core/modules/edit/js/routers/edit-router.js b/core/modules/edit/js/routers/edit-router.js
new file mode 100644
index 0000000..4d7b196
--- /dev/null
+++ b/core/modules/edit/js/routers/edit-router.js
@@ -0,0 +1,54 @@
+/**
+ * @file
+ * A Backbone Router enabling URLs to make the user enter edit mode directly.
+ */
+(function(Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.routers = {};
+Drupal.edit.routers.EditRouter = Backbone.Router.extend({
+
+ appModel: null,
+
+ routes: {
+ "quick-edit": "edit",
+ "view": "view",
+ "": "view"
+ },
+
+ initialize: function(options) {
+ this.appModel = options.appModel;
+ },
+
+ edit: function() {
+ this.appModel.set('isViewing', false);
+ },
+
+ view: function(query, page) {
+ var that = this;
+
+ // If there's an active editor, attempt to set its state to 'candidate', and
+ // then act according to the user's choice.
+ var activeEditor = this.appModel.get('activeEditor');
+ if (activeEditor) {
+ var editableEntity = activeEditor.options.widget;
+ var predicate = activeEditor.options.property;
+ editableEntity.setState('candidate', predicate, { reason: 'menu' }, function(accepted) {
+ if (accepted) {
+ that.appModel.set('isViewing', true);
+ }
+ else {
+ that.navigate('#quick-edit');
+ }
+ });
+ }
+ // Otherwise, we can switch to view mode directly.
+ else {
+ that.appModel.set('isViewing', true);
+ }
+ }
+});
+
+})(Backbone, Drupal);
diff --git a/core/modules/edit/js/theme.js b/core/modules/edit/js/theme.js
new file mode 100644
index 0000000..f92c308
--- /dev/null
+++ b/core/modules/edit/js/theme.js
@@ -0,0 +1,156 @@
+/**
+ * @file
+ * Provides overridable theme functions for all of Edit's client-side HTML.
+ */
+(function($, Drupal) {
+
+"use strict";
+
+/**
+ * Theme function for the overlay of the Edit module.
+ *
+ * @param settings
+ * An object with the following keys:
+ * - None.
+ * @return
+ * The corresponding HTML.
+ */
+Drupal.theme.editOverlay = function(settings) {
+ var html = '';
+ html += '';
+ return html;
+};
+
+/**
+ * Theme function for a "backstage" for the Edit module.
+ *
+ * @param settings
+ * An object with the following keys:
+ * - id: the id to apply to the backstage.
+ * @return
+ * The corresponding HTML.
+ */
+Drupal.theme.editBackstage = function(settings) {
+ var html = '';
+ html += '';
+ return html;
+};
+
+/**
+ * Theme function for a modal of the Edit module.
+ *
+ * @param settings
+ * An object with the following keys:
+ * - None.
+ * @return
+ * The corresponding HTML.
+ */
+Drupal.theme.editModal = function(settings) {
+ var classes = 'edit-animate-slow edit-animate-invisible edit-animate-delay-veryfast';
+ var html = '';
+ html += '
';
+ html += '
';
+ html += ' ';
+ html += '
';
+ return html;
+};
+
+/**
+ * Theme function for a toolbar container of the Edit module.
+ *
+ * @param settings
+ * An object with the following keys:
+ * - id: the id to apply to the toolbar container.
+ * @return
+ * The corresponding HTML.
+ */
+Drupal.theme.editToolbarContainer = function(settings) {
+ var html = '';
+ html += '
';
+ html += '
';
+ html += ' ';
+ html += '
';
+ html += '
';
+ return html;
+};
+
+/**
+ * Theme function for a toolbar toolgroup of the Edit module.
+ *
+ * @param settings
+ * An object with the following keys:
+ * - classes: the class of the toolgroup.
+ * - buttons: @see Drupal.theme.prototype.editButtons().
+ * @return
+ * The corresponding HTML.
+ */
+Drupal.theme.editToolgroup = function(settings) {
+ var classes = 'edit-toolgroup edit-animate-slow edit-animate-invisible edit-animate-delay-veryfast';
+ var html = '';
+ html += '
';
+ html += Drupal.theme('editButtons', { buttons: settings.buttons });
+ html += '
';
+ return html;
+};
+
+/**
+ * Theme function for buttons of the Edit module.
+ *
+ * Can be used for the buttons both in the toolbar toolgroups and in the modal.
+ *
+ * @param settings
+ * An object with the following keys:
+ * - buttons: an array of objects with the following keys:
+ * - url: the URL the button should point to.
+ * - classes: the classes of the button.
+ * - label: the label of the button.
+ * - hasButtonRole: whether this button should have its "role" attribute set
+ * to "button".
+ * - action: sets a data-edit-modal-action attribute.
+ * @return
+ * The corresponding HTML.
+ */
+Drupal.theme.editButtons = function(settings) {
+ var html = '';
+ for (var i = 0; i < settings.buttons.length; i++) {
+ var button = settings.buttons[i];
+ if (!button.hasOwnProperty('url')) {
+ button.url = '';
+ }
+ if (!button.hasOwnProperty('hasButtonRole')) {
+ button.hasButtonRole = true;
+ }
+
+ html += '';
+ html += button.label;
+ html += '';
+ }
+ return html;
+};
+
+/**
+ * Theme function for a form container of the Edit module.
+ *
+ * @param settings
+ * An object with the following keys:
+ * - id: the id to apply to the toolbar container.
+ * - loadingMsg: The message to show while loading.
+ * @return
+ * The corresponding HTML.
+ */
+Drupal.theme.editFormContainer = function(settings) {
+ var html = '';
+ html += '
';
+ html += '
';
+ html += '
';
+ html += settings.loadingMsg;
+ html += '
';
+ html += '
';
+ html += '
';
+ return html;
+};
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/js/util.js b/core/modules/edit/js/util.js
new file mode 100644
index 0000000..8ed9a2b
--- /dev/null
+++ b/core/modules/edit/js/util.js
@@ -0,0 +1,142 @@
+/**
+ * @file
+ * Provides utility functions for Edit.
+ */
+(function($, Drupal, drupalSettings) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.util = Drupal.edit.util || {};
+
+Drupal.edit.util.constants = {};
+Drupal.edit.util.constants.transitionEnd = "transitionEnd.edit webkitTransitionEnd.edit transitionend.edit msTransitionEnd.edit oTransitionEnd.edit";
+
+Drupal.edit.util.calcPropertyID = function(entity, predicate) {
+ return entity.getSubjectUri() + '/' + predicate;
+};
+
+Drupal.edit.util.buildUrl = function(id, urlFormat) {
+ var parts = id.split('/');
+ return Drupal.formatString(decodeURIComponent(urlFormat), {
+ '!entity_type': parts[0],
+ '!id' : parts[1],
+ '!field_name' : parts[2],
+ '!langcode' : parts[3],
+ '!view_mode' : parts[4]
+ });
+};
+
+/**
+ * Loads rerendered processed text for a given property.
+ *
+ * Leverages Drupal.ajax' ability to have scoped (per-instance) command
+ * implementations to be able to call a callback.
+ *
+ * @param options
+ * An object with the following keys:
+ * - $editorElement (required): the PredicateEditor DOM element.
+ * - propertyID (required): the property ID that uniquely identifies the
+ * property for which this form will be loaded.
+ * - callback (required: A callback function that will receive the rerendered
+ * processed text.
+ */
+Drupal.edit.util.loadRerenderedProcessedText = function(options) {
+ // Create a Drupal.ajax instance to load the form.
+ Drupal.ajax[options.propertyID] = new Drupal.ajax(options.propertyID, options.$editorElement, {
+ url: Drupal.edit.util.buildUrl(options.propertyID, drupalSettings.edit.rerenderProcessedTextURL),
+ event: 'edit-internal.edit',
+ submit: { nocssjs : true },
+ progress: { type : null } // No progress indicator.
+ });
+ // Implement a scoped editFieldRenderedWithoutTransformationFilters AJAX
+ // command: calls the callback.
+ Drupal.ajax[options.propertyID].commands.editFieldRenderedWithoutTransformationFilters = function(ajax, response, status) {
+ options.callback(response.data);
+ // Delete the Drupal.ajax instance that called this very function.
+ delete Drupal.ajax[options.propertyID];
+ options.$editorElement.off('edit-internal.edit');
+ };
+ // This will ensure our scoped editFieldRenderedWithoutTransformationFilters
+ // AJAX command gets called.
+ options.$editorElement.trigger('edit-internal.edit');
+};
+
+Drupal.edit.util.form = {
+ /**
+ * Loads a form, calls a callback to inserts.
+ *
+ * Leverages Drupal.ajax' ability to have scoped (per-instance) command
+ * implementations to be able to call a callback.
+ *
+ * @param options
+ * An object with the following keys:
+ * - $editorElement (required): the PredicateEditor DOM element.
+ * - propertyID (required): the property ID that uniquely identifies the
+ * property for which this form will be loaded.
+ * - nocssjs (required): boolean indicating whether no CSS and JS should be
+ * returned (necessary when the form is invisible to the user).
+ * @param callback
+ * A callback function that will receive the form to be inserted, as well as
+ * the ajax object, necessary if the callback wants to perform other AJAX
+ * commands.
+ */
+ load: function(options, callback) {
+ // Create a Drupal.ajax instance to load the form.
+ Drupal.ajax[options.propertyID] = new Drupal.ajax(options.propertyID, options.$editorElement, {
+ url: Drupal.edit.util.buildUrl(options.propertyID, drupalSettings.edit.fieldFormURL),
+ event: 'edit-internal.edit',
+ submit: { nocssjs : options.nocssjs },
+ progress: { type : null } // No progress indicator.
+ });
+ // Implement a scoped editFieldForm AJAX command: calls the callback.
+ Drupal.ajax[options.propertyID].commands.editFieldForm = function(ajax, response, status) {
+ callback(response.data, ajax);
+ // Delete the Drupal.ajax instance that called this very function.
+ delete Drupal.ajax[options.propertyID];
+ options.$editorElement.off('edit-internal.edit');
+ };
+ // This will ensure our scoped editFieldForm AJAX command gets called.
+ options.$editorElement.trigger('edit-internal.edit');
+ },
+
+ /**
+ * Creates a Drupal.ajax instance that is used to save a form.
+ *
+ * @param options
+ * An object with the following keys:
+ * - nocssjs (required): boolean indicating whether no CSS and JS should be
+ * returned (necessary when the form is invisible to the user).
+ *
+ * @return
+ * The key of the Drupal.ajax instance.
+ */
+ ajaxifySaving: function(options, $submit) {
+ // Re-wire the form to handle submit.
+ var element_settings = {
+ url: $submit.closest('form').attr('action'),
+ setClick: true,
+ event: 'click.edit',
+ progress: { type:'throbber' },
+ submit: { nocssjs : options.nocssjs }
+ };
+ var base = $submit.attr('id');
+
+ Drupal.ajax[base] = new Drupal.ajax(base, $submit[0], element_settings);
+
+ return base;
+ },
+
+ /**
+ * Cleans up the Drupal.ajax instance that is used to save the form.
+ *
+ * @param $submit
+ * The jQuery-wrapped submit DOM element that should be unajaxified.
+ */
+ unajaxifySaving: function($submit) {
+ delete Drupal.ajax[$submit.attr('id')];
+ $submit.off('click.edit');
+ }
+};
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/viejs/EditService.js b/core/modules/edit/js/viejs/EditService.js
new file mode 100644
index 0000000..5545463
--- /dev/null
+++ b/core/modules/edit/js/viejs/EditService.js
@@ -0,0 +1,201 @@
+/**
+ * @file
+ * VIE DOM parsing service for Edit.
+ */
+(function(jQuery, _, VIE, Drupal, drupalSettings) {
+
+"use strict";
+
+ VIE.prototype.EditService = function (options) {
+ var defaults = {
+ name: 'edit',
+ subjectSelector: '.edit-field.edit-allowed'
+ };
+ this.options = _.extend({}, defaults, options);
+
+ this.views = [];
+ this.vie = null;
+ this.name = this.options.name;
+ };
+
+ VIE.prototype.EditService.prototype = {
+ load: function (loadable) {
+ var correct = loadable instanceof this.vie.Loadable;
+ if (!correct) {
+ throw new Error('Invalid Loadable passed');
+ }
+
+ var element;
+ if (!loadable.options.element) {
+ if (typeof document === 'undefined') {
+ return loadable.resolve([]);
+ } else {
+ element = drupalSettings.edit.context;
+ }
+ } else {
+ element = loadable.options.element;
+ }
+
+ var entities = this.readEntities(element);
+ loadable.resolve(entities);
+ },
+
+ // The edit-id data attribute contains the full identifier of
+ // each entity element in the format
+ // `::::`.
+ _getID: function (element) {
+ var id = jQuery(element).attr('data-edit-id');
+ if (!id) {
+ id = jQuery(element).closest('[data-edit-id]').attr('data-edit-id');
+ }
+ return id;
+ },
+
+ // Returns the "URI" of an entity of an element in format
+ // `/`.
+ getElementSubject: function (element) {
+ return this._getID(element).split(':').slice(0, 2).join('/');
+ },
+
+ // Returns the field name for an element in format
+ // `//`.
+ // (Slashes instead of colons because the field name is no namespace.)
+ getElementPredicate: function (element) {
+ if (!this._getID(element)) {
+ throw new Error('Could not find predicate for element');
+ }
+ return this._getID(element).split(':').slice(2, 5).join('/');
+ },
+
+ getElementType: function (element) {
+ return this._getID(element).split(':').slice(0, 1)[0];
+ },
+
+ // Reads all editable entities (currently each Drupal field is considered an
+ // entity, in the future Drupal entities should be mapped to VIE entities)
+ // from DOM and returns the VIE enties it found.
+ readEntities: function (element) {
+ var service = this;
+ var entities = [];
+ var entityElements = jQuery(this.options.subjectSelector, element);
+ entityElements = entityElements.add(jQuery(element).filter(this.options.subjectSelector));
+ entityElements.each(function () {
+ var entity = service._readEntity(jQuery(this));
+ if (entity) {
+ entities.push(entity);
+ }
+ });
+ return entities;
+ },
+
+ // Returns a filled VIE Entity instance for a DOM element. The Entity
+ // is also registered in the VIE entities collection.
+ _readEntity: function (element) {
+ var subject = this.getElementSubject(element);
+ var type = this.getElementType(element);
+ var entity = this._readEntityPredicates(subject, element, false);
+ if (jQuery.isEmptyObject(entity)) {
+ return null;
+ }
+ entity['@subject'] = subject;
+ if (type) {
+ entity['@type'] = this._registerType(type, element);
+ }
+
+ // Register with VIE
+ return this._registerEntity(entity);
+ },
+
+ _registerEntity: function (entityData) {
+ var entityInstance = new this.vie.Entity(entityData);
+ return this.vie.entities.addOrUpdate(entityInstance, {
+ updateOptions: {
+ silent: true
+ }
+ });
+ },
+
+ _registerType: function (typeId, element) {
+ typeId = '';
+ var type = this.vie.types.get(typeId);
+ if (!type) {
+ this.vie.types.add(typeId, []);
+ type = this.vie.types.get(typeId);
+ }
+
+ var predicate = this.getElementPredicate(element);
+ if (type.attributes.get(predicate)) {
+ return type;
+ }
+
+ var label = element.data('edit-field-label');
+ var range = 'Form';
+ if (element.hasClass('edit-type-direct')) {
+ range = 'Direct';
+ }
+ if (element.hasClass('edit-type-direct-with-wysiwyg')) {
+ range = 'Wysiwyg';
+ }
+ type.attributes.add(predicate, [range], 0, 1, {
+ label: element.data('edit-field-label')
+ });
+
+ return type;
+ },
+
+ _readEntityPredicates: function (subject, element, emptyValues) {
+ var entityPredicates = {};
+ var service = this;
+ this.findPredicateElements(subject, element, true).each(function () {
+ var predicateElement = jQuery(this);
+ var predicate = service.getElementPredicate(predicateElement);
+ if (!predicate) {
+ return;
+ }
+ var value = service._readElementValue(predicateElement);
+ if (value === null && !emptyValues) {
+ return;
+ }
+
+ entityPredicates[predicate] = value;
+ });
+ return entityPredicates;
+ },
+
+ _readElementValue: function (element) {
+ return jQuery.trim(element.html());
+ },
+
+ // Subject elements are the DOM elements containing a single or multiple
+ // editable fields.
+ findSubjectElements: function (element) {
+ if (!element) {
+ element = drupalSettings.edit.context;
+ }
+ return jQuery(this.options.subjectSelector, element);
+ },
+
+ // Predicate Elements are the actual DOM elements that users will be able
+ // to edit.
+ findPredicateElements: function (subject, element, allowNestedPredicates, stop) {
+ var predicates = jQuery();
+
+ // Form-type predicates
+ predicates = predicates.add(element.filter('.edit-type-form'));
+
+ // Direct-type predicates
+ var direct = element.filter('.edit-type-direct');
+ predicates = predicates.add(direct.find('.field-item'));
+
+ if (!predicates.length && !stop) {
+ var parentElement = element.parent(this.options.subjectSelector);
+ if (parentElement.length) {
+ return this.findPredicateElements(subject, parentElement, allowNestedPredicates, true);
+ }
+ }
+
+ return predicates;
+ }
+ };
+
+})(jQuery, _, VIE, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/views/menu-view.js b/core/modules/edit/js/views/menu-view.js
new file mode 100644
index 0000000..834f743
--- /dev/null
+++ b/core/modules/edit/js/views/menu-view.js
@@ -0,0 +1,42 @@
+/**
+ * @file
+ * A Backbone View that provides the app-level interactive menu.
+ */
+(function($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.views = Drupal.edit.views || {};
+Drupal.edit.views.MenuView = Backbone.View.extend({
+
+ /**
+ * Implements Backbone Views' initialize() function.
+ */
+ initialize: function() {
+ _.bindAll(this, 'stateChange');
+ this.model.on('change:isViewing', this.stateChange);
+
+ // We have to call stateChange() here, because URL fragments are not passed
+ // the server, thus the wrong anchor may be marked as active.
+ this.stateChange();
+ },
+
+ /**
+ * Listens to app state changes.
+ */
+ stateChange: function() {
+ // Unmark whichever one is currently marked as active.
+ this.$('a.edit_view-edit-toggle')
+ .removeClass('active')
+ .parent().removeClass('active');
+
+ // Mark the correct one as active.
+ var activeAnchor = this.model.get('isViewing') ? 'view' : 'edit';
+ this.$('a.edit_view-edit-toggle.edit-' + activeAnchor)
+ .addClass('active')
+ .parent().addClass('active');
+ }
+});
+
+})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/modal-view.js b/core/modules/edit/js/views/modal-view.js
new file mode 100644
index 0000000..c5d91ab
--- /dev/null
+++ b/core/modules/edit/js/views/modal-view.js
@@ -0,0 +1,108 @@
+/**
+ * @file
+ * A Backbone View that provides an interactive modal.
+ */
+(function($, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.views = Drupal.edit.views || {};
+Drupal.edit.views.ModalView = Backbone.View.extend({
+
+ message: null,
+ buttons: null,
+ callback: null,
+ $elementsToHide: null,
+
+ events: {
+ 'click a[role=button]': 'onButtonClick'
+ },
+
+ /**
+ * Implements Backbone Views' initialize() function.
+ *
+ * @param options
+ * An object with the following keys:
+ * - message: a message to show in the modal.
+ * - buttons: a set of buttons with 'action's defined, ready to be passed to
+ * Drupal.theme.editButtons().
+ * - callback: a callback that will receive the 'action' of the clicked
+ * button.
+ *
+ * @see Drupal.theme.editModal()
+ * @see Drupal.theme.editButtons()
+ */
+ initialize: function(options) {
+ this.message = options.message;
+ this.buttons = options.buttons;
+ this.callback = options.callback;
+ },
+
+ /**
+ * Implements Backbone Views' render() function.
+ */
+ render: function() {
+ // Step 1: move certain UI elements below the overlay.
+ var editor = this.model.get('activeEditor');
+ this.$elementsToHide = $([])
+ .add((editor.element.hasClass('edit-belowoverlay')) ? null : editor.element)
+ .add(editor.toolbarView.$el)
+ .add((editor.options.editorName === 'form')
+ ? editor.$formContainer
+ : editor.element.next('.edit-validation-errors')
+ );
+ this.$elementsToHide.addClass('edit-belowoverlay');
+
+ // Step 2: the modal. When the user makes a choice, the UI elements that
+ // were moved below the overlay will be restored, and the callback will be
+ // called.
+ this.setElement(Drupal.theme('editModal', {}));
+ this.$el.appendTo('body');
+ // Template.
+ this.$('.main p').text(this.message);
+ var $actions = $(Drupal.theme('editButtons', { 'buttons' : this.buttons}));
+ this.$('.actions').append($actions);
+
+ // Step 3; show the modal with an animation.
+ var that = this;
+ setTimeout(function() {
+ that.$el.removeClass('edit-animate-invisible');
+ }, 0);
+ },
+
+ /**
+ * When the user clicks on any of the buttons, the modal should be removed
+ * and the result should be passed to the callback.
+ *
+ * @param event
+ */
+ onButtonClick: function(event) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ // Remove after animation.
+ var that = this;
+ this.$el
+ .addClass('edit-animate-invisible')
+ .on(Drupal.edit.util.constants.transitionEnd, function(e) {
+ that.remove();
+ });
+
+ var action = $(event.target).attr('data-edit-modal-action');
+ return this.callback(action);
+ },
+
+ /**
+ * Overrides Backbone Views' remove() function.
+ */
+ remove: function() {
+ // Move the moved UI elements on top of the overlay again.
+ this.$elementsToHide.removeClass('edit-belowoverlay');
+
+ // Remove the modal itself.
+ this.$el.remove();
+ }
+});
+
+})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/overlay-view.js b/core/modules/edit/js/views/overlay-view.js
new file mode 100644
index 0000000..e357014
--- /dev/null
+++ b/core/modules/edit/js/views/overlay-view.js
@@ -0,0 +1,82 @@
+/**
+ * @file
+ * A Backbone View that provides the app-level overlay.
+ *
+ * The overlay sits on top of the existing content, the properties that are
+ * candidates for editing sit on top of the overlay.
+ */
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.views = Drupal.edit.views || {};
+Drupal.edit.views.OverlayView = Backbone.View.extend({
+
+ events: {
+ 'click': 'onClick'
+ },
+
+ /**
+ * Implements Backbone Views' initialize() function.
+ */
+ initialize: function(options) {
+ _.bindAll(this, 'stateChange');
+ this.model.on('change:isViewing', this.stateChange);
+ },
+
+ /**
+ * Listens to app state changes.
+ */
+ stateChange: function() {
+ if (this.model.get('isViewing')) {
+ this.remove();
+ return;
+ }
+ this.render();
+ },
+
+ /**
+ * Equates clicks anywhere on the overlay to clicking the active editor's (if
+ * any) "close" button.
+ *
+ * @param event
+ */
+ onClick: function(event) {
+ event.preventDefault();
+ var activeEditor = this.model.get('activeEditor');
+ if (activeEditor) {
+ var editableEntity = activeEditor.options.widget;
+ var predicate = activeEditor.options.property;
+ editableEntity.setState('candidate', predicate, { reason: 'overlay' });
+ }
+ },
+
+ /**
+ * Inserts the overlay element and appends it to the body.
+ */
+ render: function() {
+ this.setElement(
+ $(Drupal.theme('editOverlay', {}))
+ .appendTo('body')
+ .addClass('edit-animate-slow edit-animate-invisible')
+ );
+ // Animations
+ this.$el.css('top', $('#navbar').outerHeight());
+ this.$el.removeClass('edit-animate-invisible');
+ },
+
+ /**
+ * Remove the overlay element.
+ */
+ remove: function() {
+ var that = this;
+ this.$el
+ .addClass('edit-animate-invisible')
+ .on(Drupal.edit.util.constants.transitionEnd, function (event) {
+ that.$el.remove();
+ });
+ }
+});
+
+})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/propertyeditordecoration-view.js b/core/modules/edit/js/views/propertyeditordecoration-view.js
new file mode 100644
index 0000000..7d8a27c
--- /dev/null
+++ b/core/modules/edit/js/views/propertyeditordecoration-view.js
@@ -0,0 +1,322 @@
+/**
+ * @file
+ * A Backbone View that decorates a Property Editor widget.
+ *
+ * It listens to state changes of the property editor.
+ */
+(function($, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.views = Drupal.edit.views || {};
+Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
+
+ editor: null,
+ entity: null,
+ predicate : null,
+ editorName: null,
+ toolbarId: null,
+
+ _widthAttributeIsEmpty: null,
+
+ events: {
+ 'mouseenter.edit' : 'onMouseEnter',
+ 'mouseleave.edit' : 'onMouseLeave'
+ },
+
+ /**
+ * Implements Backbone Views' initialize() function.
+ *
+ * @param options
+ * An object with the following keys:
+ * - editor: the editor object with an 'options' object that has these keys:
+ * * entity: the VIE entity for the property.
+ * * property: the predicate of the property.
+ * * editorName: the editor name: 'form', 'direct' or
+ * 'direct-with-wysiwyg'.
+ * * widget: the parent EditableeEntity widget.
+ * - toolbarId: the ID attribute of the toolbar as rendered in the DOM.
+ */
+ initialize: function(options) {
+ this.editor = options.editor;
+ this.toolbarId = options.toolbarId;
+
+ this.entity = this.editor.options.entity;
+ this.predicate = this.editor.options.property;
+ this.editorName = this.editor.options.editorName;
+
+ this.$el.css('background-color', this._getBgColor(this.$el));
+ },
+
+ /**
+ * Listens to editor state changes.
+ */
+ stateChange: function(from, to) {
+ switch (to) {
+ case 'inactive':
+ if (from !== null) {
+ this.undecorate();
+ }
+ break;
+ case 'candidate':
+ this.decorate();
+ if (from !== 'inactive') {
+ this.stopHighlight();
+ if (from !== 'highlighted') {
+ this.stopEdit(this.editorName);
+ }
+ }
+ break;
+ case 'highlighted':
+ this.startHighlight();
+ break;
+ case 'activating':
+ // NOTE: this step only exists for the 'form' editor! It is skipped by
+ // the 'direct' and 'direct-with-wysiwyg' editors, because no loading is
+ // necessary.
+ this.prepareEdit(this.editorName);
+ break;
+ case 'active':
+ if (this.editorName !== 'form') {
+ this.prepareEdit(this.editorName);
+ }
+ this.startEdit(this.editorName);
+ break;
+ case 'changed':
+ break;
+ case 'saving':
+ break;
+ case 'saved':
+ break;
+ case 'invalid':
+ break;
+ }
+ },
+
+ /**
+ * Starts hover: transition to 'highlight' state.
+ *
+ * @param event
+ */
+ onMouseEnter: function(event) {
+ var that = this;
+ this._ignoreHoveringVia(event, '#' + this.toolbarId, function () {
+ var editableEntity = that.editor.options.widget;
+ editableEntity.setState('highlighted', that.predicate);
+ event.stopPropagation();
+ });
+ },
+
+ /**
+ * Stops hover: back to 'candidate' state.
+ *
+ * @param event
+ */
+ onMouseLeave: function(event) {
+ var that = this;
+ this._ignoreHoveringVia(event, '#' + this.toolbarId, function () {
+ var editableEntity = that.editor.options.widget;
+ editableEntity.setState('candidate', that.predicate, { reason: 'mouseleave' });
+ event.stopPropagation();
+ });
+ },
+
+ decorate: function () {
+ this.$el.addClass('edit-animate-fast edit-candidate edit-editable');
+ },
+
+ undecorate: function () {
+ this.$el
+ .removeClass('edit-candidate edit-editable edit-highlighted edit-editing edit-belowoverlay');
+ },
+
+ startHighlight: function () {
+ // Animations.
+ var that = this;
+ setTimeout(function() {
+ that.$el.addClass('edit-highlighted');
+ }, 0);
+ },
+
+ stopHighlight: function() {
+ this.$el
+ .removeClass('edit-highlighted');
+ },
+
+ prepareEdit: function(editorName) {
+ this.$el.addClass('edit-editing');
+
+ // While editing, don't show *any* other editors.
+ // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133)
+ // Revisit this.
+ $('.edit-candidate').not('.edit-editing').removeClass('edit-editable');
+
+ if (editorName === 'form') {
+ this.$el.addClass('edit-belowoverlay');
+ }
+ },
+
+ startEdit: function(editorName) {
+ if (editorName !== 'form') {
+ this._pad();
+ }
+ },
+
+ stopEdit: function(editorName) {
+ this.$el.removeClass('edit-highlighted edit-editing');
+
+ // Make the other editors show up again.
+ // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133)
+ // Revisit this.
+ $('.edit-candidate').addClass('edit-editable');
+
+ if (editorName === 'form') {
+ this.$el.removeClass('edit-belowoverlay');
+ }
+ else {
+ this._unpad();
+ }
+ },
+
+ _pad: function () {
+ var self = this;
+
+ // Add 5px padding for readability. This means we'll freeze the current
+ // width and *then* add 5px padding, hence ensuring the padding is added "on
+ // the outside".
+ // 1) Freeze the width (if it's not already set); don't use animations.
+ if (this.$el[0].style.width === "") {
+ this._widthAttributeIsEmpty = true;
+ this.$el
+ .addClass('edit-animate-disable-width')
+ .css('width', this.$el.width());
+ }
+
+ // 2) Add padding; use animations.
+ var posProp = this._getPositionProperties(this.$el);
+ setTimeout(function() {
+ // Re-enable width animations (padding changes affect width too!).
+ self.$el.removeClass('edit-animate-disable-width');
+
+ // Pad the editable.
+ self.$el
+ .css({
+ 'position': 'relative',
+ 'top': posProp.top - 5 + 'px',
+ 'left': posProp.left - 5 + 'px',
+ 'padding-top' : posProp['padding-top'] + 5 + 'px',
+ 'padding-left' : posProp['padding-left'] + 5 + 'px',
+ 'padding-right' : posProp['padding-right'] + 5 + 'px',
+ 'padding-bottom': posProp['padding-bottom'] + 5 + 'px',
+ 'margin-bottom': posProp['margin-bottom'] - 10 + 'px'
+ });
+ }, 0);
+ },
+
+ _unpad: function () {
+ var self = this;
+
+ // 1) Set the empty width again.
+ if (this._widthAttributeIsEmpty) {
+ this.$el
+ .addClass('edit-animate-disable-width')
+ .css('width', '');
+ }
+
+ // 2) Remove padding; use animations (these will run simultaneously with)
+ // the fading out of the toolbar as its gets removed).
+ var posProp = this._getPositionProperties(this.$el);
+ setTimeout(function() {
+ // Re-enable width animations (padding changes affect width too!).
+ self.$el.removeClass('edit-animate-disable-width');
+
+ // Unpad the editable.
+ self.$el
+ .css({
+ 'position': 'relative',
+ 'top': posProp.top + 5 + 'px',
+ 'left': posProp.left + 5 + 'px',
+ 'padding-top' : posProp['padding-top'] - 5 + 'px',
+ 'padding-left' : posProp['padding-left'] - 5 + 'px',
+ 'padding-right' : posProp['padding-right'] - 5 + 'px',
+ 'padding-bottom': posProp['padding-bottom'] - 5 + 'px',
+ 'margin-bottom': posProp['margin-bottom'] + 10 + 'px'
+ });
+ }, 0);
+ },
+
+ /**
+ * Gets the background color of an element (or the inherited one).
+ *
+ * @param $e
+ * A DOM element.
+ */
+ _getBgColor: function($e) {
+ var c;
+
+ if ($e === null || $e[0].nodeName === 'HTML') {
+ // Fallback to white.
+ return 'rgb(255, 255, 255)';
+ }
+ c = $e.css('background-color');
+ // TRICKY: edge case for Firefox' "transparent" here; this is a
+ // browser bug: https://bugzilla.mozilla.org/show_bug.cgi?id=635724
+ if (c === 'rgba(0, 0, 0, 0)' || c === 'transparent') {
+ return this._getBgColor($e.parent());
+ }
+ return c;
+ },
+
+ /**
+ * Gets the top and left properties of an element and convert extraneous
+ * values and information into numbers ready for subtraction.
+ *
+ * @param $e
+ * A DOM element.
+ */
+ _getPositionProperties: function($e) {
+ var p,
+ r = {},
+ props = [
+ 'top', 'left', 'bottom', 'right',
+ 'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
+ 'margin-bottom'
+ ];
+
+ var propCount = props.length;
+ for (var i = 0; i < propCount; i++) {
+ p = props[i];
+ r[p] = parseInt(this._replaceBlankPosition($e.css(p)), 10);
+ }
+ return r;
+ },
+
+ /**
+ * Replaces blank or 'auto' CSS "position: " values with "0px".
+ *
+ * @param pos
+ * The value for a CSS position declaration.
+ */
+ _replaceBlankPosition: function(pos) {
+ if (pos === 'auto' || !pos) {
+ pos = '0px';
+ }
+ return pos;
+ },
+
+ /**
+ * Ignores hovering to/from the given closest element, but as soon as a hover
+ * occurs to/from *another* element, then call the given callback.
+ */
+ _ignoreHoveringVia: function(event, closest, callback) {
+ if ($(event.relatedTarget).closest(closest).length > 0) {
+ event.stopPropagation();
+ }
+ else {
+ callback();
+ }
+ }
+});
+
+})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/toolbar-view.js b/core/modules/edit/js/views/toolbar-view.js
new file mode 100644
index 0000000..4b8235c
--- /dev/null
+++ b/core/modules/edit/js/views/toolbar-view.js
@@ -0,0 +1,445 @@
+/**
+ * @file
+ * A Backbone View that provides an interactive toolbar (1 per property editor).
+ *
+ * It listens to state changes of the property editor. It also triggers state
+ * changes in response to user interactions with the toolbar, including saving.
+ */
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.views = Drupal.edit.views || {};
+Drupal.edit.views.ToolbarView = Backbone.View.extend({
+
+ editor: null,
+ $storageWidgetEl: null,
+
+ entity: null,
+ predicate : null,
+ editorName: null,
+
+ _id: null,
+
+ events: {
+ 'click.edit a.label': 'onClickInfoLabel',
+ 'mouseleave.edit': 'onMouseLeave',
+ 'click.edit a.field-save': 'onClickSave',
+ 'click.edit a.field-close': 'onClickClose'
+ },
+
+ /**
+ * Implements Backbone Views' initialize() function.
+ *
+ * @param options
+ * An object with the following keys:
+ * - editor: the editor object with an 'options' object that has these keys:
+ * * entity: the VIE entity for the property.
+ * * property: the predicate of the property.
+ * * editorName: the editor name: 'form', 'direct' or
+ * 'direct-with-wysiwyg'.
+ * * element: the jQuery-wrapped editor DOM element
+ * - $storageWidgetEl: the DOM element on which the Create Storage widget is
+ * initialized.
+ */
+ initialize: function(options) {
+ this.editor = options.editor;
+ this.$storageWidgetEl = options.$storageWidgetEl;
+
+ this.entity = this.editor.options.entity;
+ this.predicate = this.editor.options.property;
+ this.editorName = this.editor.options.editorName;
+
+ // Generate a DOM-compatible ID for the toolbar DOM element.
+ var propertyID = Drupal.edit.util.calcPropertyID(this.entity, this.predicate);
+ this._id = 'edit-toolbar-for-' + propertyID.replace(/\//g, '_');
+ },
+
+ /**
+ * Listens to editor state changes.
+ */
+ stateChange: function(from, to) {
+ switch (to) {
+ case 'inactive':
+ // Nothing happens in this stage.
+ break;
+ case 'candidate':
+ if (from !== 'inactive') {
+ if (from !== 'highlighted' && this.editorName !== 'form') {
+ this._unpad(this.editorName);
+ }
+ this.remove();
+ }
+ break;
+ case 'highlighted':
+ // As soon as we highlight, make sure we have a toolbar in the DOM (with at least a title).
+ this.render();
+ this.startHighlight();
+ break;
+ case 'activating':
+ this.setLoadingIndicator(true);
+ break;
+ case 'active':
+ this.startEdit(this.editorName);
+ this.setLoadingIndicator(false);
+ if (this.editorName !== 'form') {
+ this._pad(this.editorName);
+ }
+ if (this.editorName === 'direct-with-wysiwyg') {
+ this.insertWYSIWYGToolGroups();
+ }
+ break;
+ case 'changed':
+ this.$el
+ .find('a.save')
+ .addClass('blue-button')
+ .removeClass('gray-button');
+ break;
+ case 'saving':
+ this.setLoadingIndicator(true);
+ this.save();
+ break;
+ case 'saved':
+ this.setLoadingIndicator(false);
+ break;
+ case 'invalid':
+ this.setLoadingIndicator(false);
+ break;
+ }
+ },
+
+ /**
+ * Saves a property.
+ *
+ * This method deals with the complexity of the editor-dependent ways of
+ * inserting updated content and showing validation error messages.
+ *
+ * One might argue that this does not belong in a view. However, there is no
+ * actual "save" logic here, that lives in Backbone.sync. This is just some
+ * glue code, along with the logic for inserting updated content as well as
+ * showing validation error messages, the latter of which is certainly okay.
+ */
+ save: function() {
+ var that = this;
+ var editor = this.editor;
+ var editableEntity = editor.options.widget;
+ var entity = editor.options.entity;
+ var predicate = editor.options.property;
+
+ // Use Create.js' Storage widget to handle saving. (Uses Backbone.sync.)
+ this.$storageWidgetEl.createStorage('saveRemote', entity, {
+ editor: editor,
+
+ // Successfully saved without validation errors.
+ success: function (model) {
+ editableEntity.setState('saved', predicate);
+
+ // Replace the old content with the new content.
+ var updatedField = entity.get(predicate + '/rendered');
+ var $inner = $(updatedField).html();
+ editor.element.html($inner);
+
+ // @todo BLOCKED_ON(VIE.js, how to let VIE know that some content was removed and how to scan new content for VIE entities, to make them editable?)
+ // Also see Drupal.behaviors.editDiscoverEditables.
+ // VIE doesn't seem to like this? :) It seems that if I delete/
+ // overwrite an existing field, that VIE refuses to find the same
+ // predicate again for the same entity?
+ // self.$el.replaceWith(updatedField);
+ // debugger;
+ // console.log(self.$el, self.el, Drupal.edit.domService.findSubjectElements(self.$el));
+ // Drupal.edit.domService.findSubjectElements(self.$el).each(Drupal.edit.prepareFieldView);
+
+ editableEntity.setState('candidate', predicate);
+ },
+
+ // Save attempted but failed due to validation errors.
+ error: function (validationErrorMessages) {
+ editableEntity.setState('invalid', predicate);
+
+ if (that.editorName === 'form') {
+ editor.$formContainer
+ .find('.edit-form')
+ .addClass('edit-validation-error')
+ .find('form')
+ .prepend(validationErrorMessages);
+ }
+ else {
+ var $errors = $('')
+ .append(validationErrorMessages);
+ editor.element
+ .addClass('edit-validation-error')
+ .after($errors);
+ }
+ }
+ });
+ },
+
+ /**
+ * When the user clicks the info label, nothing should happen.
+ * @note currently redirects the click.edit-event to the editor DOM element.
+ *
+ * @param event
+ */
+ onClickInfoLabel: function(event) {
+ event.stopPropagation();
+ event.preventDefault();
+ // Redirects the event to the editor DOM element.
+ this.editor.element.trigger('click.edit');
+ },
+
+ /**
+ * A mouseleave to the editor doesn't matter; a mouseleave to something else
+ * counts as a mouseleave on the editor itself.
+ *
+ * @param event
+ */
+ onMouseLeave: function(event) {
+ var el = this.editor.element[0];
+ if (event.relatedTarget != el && !$.contains(el, event.relatedTarget)) {
+ this.editor.element.trigger('mouseleave.edit');
+ }
+ event.stopPropagation();
+ },
+
+ /**
+ * Upon clicking "Save", trigger a custom event to save this property.
+ *
+ * @param event
+ */
+ onClickSave: function(event) {
+ event.stopPropagation();
+ event.preventDefault();
+ this.editor.options.widget.setState('saving', this.predicate);
+ },
+
+ /**
+ * Upon clicking "Close", trigger a custom event to stop editing.
+ *
+ * @param event
+ */
+ onClickClose: function(event) {
+ event.stopPropagation();
+ event.preventDefault();
+ this.editor.options.widget.setState('candidate', this.predicate, { reason: 'cancel' });
+ },
+
+ /**
+ * Indicate in the 'info' toolgroup that we're waiting for a server reponse.
+ *
+ * @param bool enabled
+ * Whether the loading indicator should be displayed or not.
+ */
+ setLoadingIndicator: function(enabled) {
+ if (enabled) {
+ this.addClass('info', 'loading');
+ }
+ else {
+ // Only stop showing the loading indicator after half a second to prevent
+ // it from flashing, which is bad UX.
+ var that = this;
+ setTimeout(function() {
+ that.removeClass('info', 'loading');
+ }, 500);
+ }
+ },
+
+ startHighlight: function() {
+ // We get the label to show for this property from VIE's type system.
+ var label = this.predicate;
+ var attributeDef = this.entity.get('@type').attributes.get(this.predicate);
+ if (attributeDef && attributeDef.metadata) {
+ label = attributeDef.metadata.label;
+ }
+
+ this.$el
+ .find('.edit-toolbar')
+ // Append the "info" toolgroup into the toolbar.
+ .append(Drupal.theme('editToolgroup', {
+ classes: 'info',
+ buttons: [
+ { label: label, classes: 'blank-button label', hasButtonRole: false }
+ ]
+ }));
+
+ // Animations.
+ var that = this;
+ setTimeout(function () {
+ that.show('info');
+ }, 0);
+ },
+
+ startEdit: function() {
+ this.$el
+ .addClass('edit-editing')
+ .find('.edit-toolbar')
+ // Append the "ops" toolgroup into the toolbar.
+ .append(Drupal.theme('editToolgroup', {
+ classes: 'ops',
+ buttons: [
+ { label: Drupal.t('Save'), classes: 'field-save save gray-button' },
+ { label: '', classes: 'field-close close gray-button' }
+ ]
+ }));
+ this.show('ops');
+ },
+
+ /**
+ * Adjusts the toolbar to accomodate padding on the PropertyEditor widget.
+ *
+ * @see PropertyEditorDecorationView._pad().
+ */
+ _pad: function(editorName) {
+ // The whole toolbar must move to the top when the property's DOM element
+ // is displayed inline.
+ if (this.editor.element.css('display') === 'inline') {
+ this.$el.css('top', parseInt(this.$el.css('top'), 10) - 5 + 'px');
+ }
+
+ // The toolbar must move to the top and the left.
+ var $hf = this.$el.find('.edit-toolbar-heightfaker');
+ $hf.css({ bottom: '6px', left: '-5px' });
+ // When using a WYSIWYG editor, the width of the toolbar must match the
+ // width of the editable.
+ if (editorName === 'direct-with-wysiwyg') {
+ $hf.css({ width: this.editor.element.width() + 10 });
+ }
+ },
+
+ /**
+ * Undoes the changes made by _pad().
+ *
+ * @see PropertyEditorDecorationView._unpad().
+ */
+ _unpad: function(editorName) {
+ // Move the toolbar back to its original position.
+ var $hf = this.$el.find('.edit-toolbar-heightfaker');
+ $hf.css({ bottom: '1px', left: '' });
+ // When using a WYSIWYG editor, restore the width of the toolbar.
+ if (editorName === 'direct-with-wysiwyg') {
+ $hf.css({ width: '' });
+ }
+ },
+
+ insertWYSIWYGToolGroups: function() {
+ this.$el
+ .find('.edit-toolbar')
+ .append(Drupal.theme('editToolgroup', {
+ classes: 'wysiwyg-tabs',
+ buttons: []
+ }))
+ .append(Drupal.theme('editToolgroup', {
+ classes: 'wysiwyg',
+ buttons: []
+ }));
+
+ // Animate the toolgroups into visibility.
+ var that = this;
+ setTimeout(function () {
+ that.show('wysiwyg-tabs');
+ that.show('wysiwyg');
+ }, 0);
+ },
+
+ /**
+ * Renders the Toolbar's markup into the DOM.
+ *
+ * Note: depending on whether the 'display' property of the $el for which a
+ * toolbar is being inserted into the DOM, it will be inserted differently.
+ */
+ render: function () {
+ // Render toolbar.
+ this.setElement($(Drupal.theme('editToolbarContainer', {
+ id: this.getId()
+ })));
+
+ // Insert in DOM.
+ if (this.$el.css('display') === 'inline') {
+ this.$el.prependTo(this.editor.element.offsetParent());
+ var pos = this.editor.element.position();
+ this.$el.css('left', pos.left).css('top', pos.top);
+ }
+ else {
+ this.$el.insertBefore(this.editor.element);
+ }
+
+ var that = this;
+ // Animate the toolbar into visibility.
+ setTimeout(function () {
+ that.$el.removeClass('edit-animate-invisible');
+ }, 0);
+ },
+
+ remove: function () {
+ if (!this.$el) {
+ return;
+ }
+
+ // Remove after animation.
+ var that = this;
+ var $el = this.$el;
+ this.$el
+ .addClass('edit-animate-invisible')
+ // Prevent this toolbar from being detected *while* it is being removed.
+ .removeAttr('id')
+ .find('.edit-toolbar .edit-toolgroup')
+ .addClass('edit-animate-invisible')
+ .on(Drupal.edit.util.constants.transitionEnd, function (e) {
+ $el.remove();
+ });
+ },
+
+ /**
+ * Calculates the ID for this toolbar container.
+ *
+ * Only used to make sane hovering behavior possible.
+ *
+ * @return string
+ * A string that can be used as the ID for this toolbar container.
+ */
+ getId: function() {
+ return this._id;
+ },
+
+ /**
+ * Shows a toolgroup.
+ *
+ * @param string toolgroup
+ * A toolgroup name.
+ */
+ show: function (toolgroup) {
+ this._find(toolgroup).removeClass('edit-animate-invisible');
+ },
+
+ /**
+ * Adds classes to a toolgroup.
+ *
+ * @param string toolgroup
+ * A toolgroup name.
+ */
+ addClass: function (toolgroup, classes) {
+ this._find(toolgroup).addClass(classes);
+ },
+
+ /**
+ * Removes classes from a toolgroup.
+ *
+ * @param string toolgroup
+ * A toolgroup name.
+ */
+ removeClass: function (toolgroup, classes) {
+ this._find(toolgroup).removeClass(classes);
+ },
+
+ /**
+ * Finds a toolgroup.
+ *
+ * @param string toolgroup
+ * A toolgroup name.
+ */
+ _find: function (toolgroup) {
+ return this.$el.find('.edit-toolbar .edit-toolgroup.' + toolgroup);
+ }
+});
+
+})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/edit/lib/Drupal/edit/Ajax/BaseCommand.php b/core/modules/edit/lib/Drupal/edit/Ajax/BaseCommand.php
new file mode 100644
index 0000000..32d325d
--- /dev/null
+++ b/core/modules/edit/lib/Drupal/edit/Ajax/BaseCommand.php
@@ -0,0 +1,52 @@
+command = $command;
+ $this->data = $data;
+ }
+
+ /**
+ * Implements Drupal\Core\Ajax\CommandInterface:render().
+ */
+ public function render() {
+ return array(
+ 'command' => $this->command,
+ 'data' => $this->data,
+ );
+ }
+
+}
diff --git a/core/modules/edit/lib/Drupal/edit/Ajax/FieldFormCommand.php b/core/modules/edit/lib/Drupal/edit/Ajax/FieldFormCommand.php
new file mode 100644
index 0000000..76b01c5
--- /dev/null
+++ b/core/modules/edit/lib/Drupal/edit/Ajax/FieldFormCommand.php
@@ -0,0 +1,27 @@
+register('plugin.manager.edit.processed_text_editor', 'Drupal\edit\Plugin\Type\ProcessedTextEditorManager');
+ }
+
+}
diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorBase.php b/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorBase.php
new file mode 100644
index 0000000..41c0141
--- /dev/null
+++ b/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorBase.php
@@ -0,0 +1,35 @@
+discovery = new AnnotatedClassDiscovery('edit', 'processed_text_editor');
+ $this->discovery = new AlterDecorator($this->discovery, 'edit_wysiwyg');
+ $this->discovery = new CacheDecorator($this->discovery, 'edit:wysiwyg');
+ $this->factory = new DefaultFactory($this->discovery);
+ }
+
+}
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php
index 6b34ba9..79bdbc7 100644
--- a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php
@@ -23,6 +23,9 @@
* "text",
* "text_long",
* "text_with_summary"
+ * },
+ * edit = {
+ * "editor" = "direct"
* }
* )
*/
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php
index 0f7b615..2695351 100644
--- a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php
@@ -23,6 +23,9 @@
* "text",
* "text_long",
* "text_with_summary"
+ * },
+ * edit = {
+ * "editor" = "direct"
* }
* )
*/
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php
index 11f0c14..b318da1 100644
--- a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php
@@ -22,6 +22,9 @@
* },
* settings = {
* "trim_length" = "600"
+ * },
+ * edit = {
+ * "editor" = "form"
* }
* )
*/
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php
index 349cf63..05a830a 100644
--- a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php
@@ -31,6 +31,9 @@
* },
* settings = {
* "trim_length" = "600"
+ * },
+ * edit = {
+ * "editor" = "form"
* }
* )
*/
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 950fee6..59da2c7 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1839,7 +1839,7 @@ function system_library_info() {
),
);
- // Underscore
+ // Underscore.
$libraries['underscore'] = array(
'title' => 'Underscore.js',
'website' => 'http://underscorejs.org/',
@@ -1849,7 +1849,7 @@ function system_library_info() {
),
);
- // Backbone
+ // Backbone.
$libraries['backbone'] = array(
'title' => 'Backbone.js',
'website' => 'http://backbonejs.org/',
@@ -1862,6 +1862,35 @@ function system_library_info() {
),
);
+ // VIE.
+ $libraries['vie.core'] = array(
+ 'title' => 'VIE.js core (excluding services, views and xdr)',
+ 'website' => 'http://viejs.org/',
+ 'version' => '2.0.0-dev',
+ 'js' => array(
+ 'core/misc/vie/vie-core.js' => array('group' => JS_LIBRARY),
+ ),
+ 'dependencies' => array(
+ array('system', 'jquery'),
+ array('system', 'underscore'),
+ array('system', 'backbone'),
+ ),
+ );
+
+ // Create.
+ $libraries['create.editonly'] = array(
+ 'title' => 'Create.js edit-only (editing features only)',
+ 'website' => 'http://backbonejs.org/',
+ 'version' => '1.0.0-dev',
+ 'js' => array(
+ 'core/misc/create/create-editonly.js' => array('group' => JS_LIBRARY),
+ ),
+ 'dependencies' => array(
+ array('system', 'vie.core'),
+ array('system', 'jquery.ui.widget'),
+ ),
+ );
+
// Cookie.
$libraries['jquery.cookie'] = array(
'title' => 'Cookie',