core/misc/create/create-editonly.js | 1651 ---------
core/misc/vie/vie-core.js | 3719 --------------------
core/modules/edit/edit.module | 50 +-
core/modules/edit/js/app.js | 391 --
core/modules/edit/js/backbone.drupalform.js | 184 -
core/modules/edit/js/createjs/editable.js | 30 -
.../editingWidgets/drupalcontenteditablewidget.js | 83 -
.../edit/js/createjs/editingWidgets/formwidget.js | 152 -
core/modules/edit/js/createjs/storage.js | 11 -
core/modules/edit/js/edit.js | 436 ++-
core/modules/edit/js/editors/directEditor.js | 107 +
core/modules/edit/js/editors/formEditor.js | 187 +
core/modules/edit/js/models/AppModel.js | 22 +
core/modules/edit/js/models/EntityModel.js | 49 +
core/modules/edit/js/models/FieldModel.js | 132 +
core/modules/edit/js/models/edit-app-model.js | 21 -
core/modules/edit/js/util.js | 6 +-
core/modules/edit/js/viejs/EditService.js | 289 --
core/modules/edit/js/views/AppView.js | 384 ++
core/modules/edit/js/views/ContextualLinkView.js | 74 +
core/modules/edit/js/views/EditorDecorationView.js | 372 ++
core/modules/edit/js/views/EditorView.js | 263 ++
core/modules/edit/js/views/FieldToolbarView.js | 408 +++
core/modules/edit/js/views/ModalView.js | 80 +
core/modules/edit/js/views/contextuallink-view.js | 117 -
core/modules/edit/js/views/modal-view.js | 83 -
.../edit/js/views/propertyeditordecoration-view.js | 363 --
core/modules/edit/js/views/toolbar-view.js | 490 ---
core/modules/edit/lib/Drupal/edit/EditorBase.php | 2 +-
.../edit/lib/Drupal/edit/EditorSelector.php | 20 +-
.../edit/lib/Drupal/edit/MetadataGenerator.php | 2 +-
.../edit/lib/Drupal/edit/Plugin/EditorManager.php | 2 +-
.../edit/Plugin/edit/editor/DirectEditor.php | 2 +-
.../Drupal/edit/Plugin/edit/editor/FormEditor.php | 2 +-
.../edit/lib/Drupal/edit/Tests/EditLoadingTest.php | 9 +-
.../lib/Drupal/edit/Tests/EditorSelectionTest.php | 5 +-
.../Drupal/edit/Tests/MetadataGeneratorTest.php | 5 +-
.../edit_test/Plugin/edit/editor/WysiwygEditor.php | 2 +-
core/modules/editor/editor.module | 8 +-
core/modules/editor/js/editor.createjs.js | 157 -
.../editor/js/editor.formattedTextEditor.js | 181 +
core/modules/editor/js/editor.js | 8 +-
.../Drupal/editor/Plugin/edit/editor/Editor.php | 6 +-
.../Drupal/editor/Tests/EditIntegrationTest.php | 4 +-
core/modules/system/system.module | 14 -
45 files changed, 2627 insertions(+), 7956 deletions(-)
diff --git a/core/misc/create/create-editonly.js b/core/misc/create/create-editonly.js
deleted file mode 100644
index aed84a4..0000000
--- a/core/misc/create/create-editonly.js
+++ /dev/null
@@ -1,1651 +0,0 @@
-// 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.on('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.on('add remove reset', widget.checkCollectionConstraints, widget);
-
- widget._bindCollectionView(widget.options.view);
- },
-
- _bindCollectionView: function (view) {
- var widget = this;
- view.on('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.on('add', function (itemView) {
- //itemView.el.effect('slide');
- widget._makeEditable(itemView);
- widget._refreshButtons();
- });
- view.on('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());
-
- if (!this.vie.view || !this.vie.view.Collection) {
- return;
- }
-
- _.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.disablePropertyEditor({
- widget: this,
- editable: editable,
- entity: this.options.model,
- element: editable.element
- });
- }, 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('Midgard-' + 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) {
- data.element[data.editable.widgetName]({
- disabled: true
- });
- 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('Midgard.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.on('focus', function () {
- if (self.options.disabled) {
- return;
- }
- self.options.activated();
- });
- this.element.on('blur', function () {
- if (self.options.disabled) {
- return;
- }
- self.options.deactivated();
- });
- var before = this.element.html();
- this.element.on('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('Midgard.alohaWidget', jQuery.Midgard.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('Midgard.ckeditorWidget', jQuery.Midgard.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();
- widget.options.changed(widget.editor.getData());
- });
- 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());
- });
- this.editor.on('configLoaded', function() {
- jQuery.each(widget.options.editorOptions, function(optionName, option) {
- widget.editor.config[optionName] = option;
- });
- });
- },
-
- 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('Midgard.halloWidget', jQuery.Midgard.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).on('halloactivated', function (event, data) {
- self.options.activated();
- });
- jQuery(this.element).on('hallodeactivated', function (event, data) {
- self.options.deactivated();
- });
- jQuery(this.element).on('hallomodified', function (event, data) {
- self.options.changed(data.content);
- data.editable.setUnmodified();
- });
-
- jQuery(document).on('midgardtoolbarstatechange', function(event, data) {
- // Switch between Hallo configurations when toolbar state changes
- if (data.display === self.options.toolbarState) {
- return;
- }
- self.options.toolbarState = data.display;
- if (!self.element.data('IKS-hallo')) {
- // Hallo not yet instantiated
- return;
- }
- 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('Midgard.redactorWidget', jQuery.Midgard.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).on('focus', function (event) {
- self.options.activated();
- });
- /*
- jQuery(this.element).on('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.on('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.on('startPreventSave', function () {
- if (timeout) {
- window.clearInterval(timeout);
- timeout = null;
- }
- widget.disableAutoSave();
- });
- this.element.on('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.on(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.on(widget.options.editableNs + 'disable', function (event, options) {
- widget.revertChanges(options.instance);
- });
-
- widget.element.on(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.on('midgardcreatestatechange', function (event, options) {
- if (options.state === 'browse' || widget.restorables.length === 0) {
- widget.restorables = [];
- if (restorer) {
- restorer.close();
- }
- return;
- }
- restorer = widget.checkRestore();
- });
-
- widget.element.on('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.restoreLocalAll();
- 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;
- },
-
- restoreLocalAll: 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));
- },
-
- revertChanges: 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
deleted file mode 100644
index 4e1526e..0000000
--- a/core/misc/vie/vie-core.js
+++ /dev/null
@@ -1,3719 +0,0 @@
-/*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]), metadataHelper(SchemaOrg.types[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);
- var ret = Backbone.Model.prototype.set.call(this, attrs, options);
- if (options && options.ignoreChanges) {
- this.changed = {};
- this._previousAttributes = _.clone(this.attributes);
- }
- return ret;
- },
-
- // **`.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;
-/*global VIE:false Backbone:false _:false */
-if (!VIE.prototype.view) {
- VIE.prototype.view = {};
-}
-
-VIE.prototype.view.Entity = Backbone.View.extend({
- initialize: function(options) {
- this.service = options.service ? options.service : 'rdfa';
- this.vie = options.vie;
-
- // Ensure view gets updated when properties of the Entity change.
- _.bindAll(this, 'render', 'renderAbout');
- this.model.on('change', this.render);
- this.model.on('change:@subject', this.renderAbout);
- },
-
- // Rendering a view means writing the properties of the Entity back to
- // the element containing our RDFa annotations.
- render: function() {
- this.vie.save({
- element: this.el,
- entity: this.model
- }).
- to(this.service).
- execute();
- return this;
- },
-
- renderAbout: function () {
- this.vie.service(this.service).setElementSubject(this.model.getSubjectUri(), this.el);
- }
-});
-})();
\ No newline at end of file
diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
index 0c8eb0f..3f13edd 100644
--- a/core/modules/edit/edit.module
+++ b/core/modules/edit/edit.module
@@ -53,6 +53,14 @@ function edit_contextual_links_view_alter(&$element, $items) {
return;
}
+ $element['#attached']['js'][] = array(
+ 'type' => 'setting',
+ 'data' => array('edit' => array(
+ 'metadataURL' => url('edit/metadata'),
+ 'fieldFormURL' => url('edit/form/!entity_type/!id/!field_name/!langcode/!view_mode'),
+ 'context' => 'body',
+ )),
+ );
$element['#attached']['library'][] = array('edit', 'edit');
}
@@ -66,38 +74,24 @@ function edit_library_info() {
);
$libraries['edit'] = array(
'title' => 'Edit: in-place editing',
- 'website' => 'http://drupal.org/project/edit',
'version' => VERSION,
'js' => array(
// Core.
$path . '/js/edit.js' => $options,
- $path . '/js/app.js' => $options,
// Models.
- $path . '/js/models/edit-app-model.js' => $options,
+ $path . '/js/models/AppModel.js' => $options,
+ $path . '/js/models/EntityModel.js' => $options,
+ $path . '/js/models/FieldModel.js' => $options,
// Views.
- $path . '/js/views/propertyeditordecoration-view.js' => $options,
- $path . '/js/views/contextuallink-view.js' => $options,
- $path . '/js/views/modal-view.js' => $options,
- $path . '/js/views/toolbar-view.js' => $options,
- // Backbone.sync implementation on top of Drupal forms.
- $path . '/js/backbone.drupalform.js' => $options,
- // VIE service.
- $path . '/js/viejs/EditService.js' => $options,
- // Create.js subclasses.
- $path . '/js/createjs/editable.js' => $options,
- $path . '/js/createjs/storage.js' => $options,
+ $path . '/js/views/AppView.js' => $options,
+ $path . '/js/views/EditorDecorationView.js' => $options,
+ $path . '/js/views/ContextualLinkView.js' => $options,
+ $path . '/js/views/ModalView.js' => $options,
+ $path . '/js/views/FieldToolbarView.js' => $options,
+ $path . '/js/views/EditorView.js' => $options,
// Other.
$path . '/js/util.js' => $options,
$path . '/js/theme.js' => $options,
- // Basic settings.
- array(
- 'data' => array('edit' => array(
- 'metadataURL' => url('edit/metadata'),
- 'fieldFormURL' => url('edit/form/!entity_type/!id/!field_name/!langcode/!view_mode'),
- 'context' => 'body',
- )),
- 'type' => 'setting',
- ),
),
'css' => array(
$path . '/css/edit.css' => array(),
@@ -106,8 +100,6 @@ function edit_library_info() {
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'),
@@ -115,20 +107,20 @@ function edit_library_info() {
),
);
$libraries['edit.editorWidget.form'] = array(
- 'title' => '"Form" Create.js PropertyEditor widget',
+ 'title' => 'Form in-place editor',
'version' => VERSION,
'js' => array(
- $path . '/js/createjs/editingWidgets/formwidget.js' => $options,
+ $path . '/js/editors/formEditor.js' => $options,
),
'dependencies' => array(
array('edit', 'edit'),
),
);
$libraries['edit.editorWidget.direct'] = array(
- 'title' => '"Direct" Create.js PropertyEditor widget',
+ 'title' => 'Direct in-place editor',
'version' => VERSION,
'js' => array(
- $path . '/js/createjs/editingWidgets/drupalcontenteditablewidget.js' => $options,
+ $path . '/js/editors/directEditor.js' => $options,
),
'dependencies' => array(
array('edit', 'edit'),
diff --git a/core/modules/edit/js/app.js b/core/modules/edit/js/app.js
deleted file mode 100644
index 14d76a0..0000000
--- a/core/modules/edit/js/app.js
+++ /dev/null
@@ -1,391 +0,0 @@
-/**
- * @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: null,
-
- /**
- * 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);
-
- this.$entityElements = $([]);
-
- // Use Create's Storage widget.
- this.$el.createStorage({
- vie: this.vie,
- editableNs: 'createeditable'
- });
-
- // When view/edit mode is toggled in the menu, update the editor widgets.
- this.model.on('change:activeEntity', this.appStateChange);
- },
-
- /**
- * Finds editable properties within a given context.
- *
- * Finds editable properties, registers them with the app, updates their
- * state to match the current app state.
- *
- * @param $context
- * A jQuery-wrapped context DOM element within which will be searched.
- */
- findEditableProperties: function($context) {
- var that = this;
- var activeEntity = this.model.get('activeEntity');
-
- this.domService.findSubjectElements($context).each(function() {
- var $element = $(this);
-
- // Ignore editable properties for which we've already set up Create.js.
- if (that.$entityElements.index($element) !== -1) {
- return;
- }
-
- $element
- // Instantiate an EditableEntity widget.
- .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);
- }
- })
- // This event is triggered just before Edit removes an EditableEntity
- // widget, so that we can do proper clean-up.
- .on('destroyedPropertyEditor.edit', function(event, editor) {
- that.undecorateEditor(editor);
- that.$entityElements = that.$entityElements.not($(this));
- })
- // Transition the new PropertyEditor into the default state.
- .createEditable('setState', 'inactive');
-
- // If the new PropertyEditor is for the entity that's currently being
- // edited, then transition it to the 'candidate' state.
- // (This happens when a field was modified and is re-rendered.)
- var entityOfProperty = $element.createEditable('option', 'model');
- if (entityOfProperty.getSubjectUri() === activeEntity) {
- $element.createEditable('setState', 'candidate');
- }
-
- // Add this new EditableEntity widget element to the list.
- that.$entityElements = that.$entityElements.add($element);
- });
- },
-
- /**
- * Sets the state of PropertyEditor widgets when edit mode begins or ends.
- *
- * Should be called whenever EditAppModel's "activeEntity" 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 activeEntity = this.model.get('activeEntity');
- var $editableFieldsForEntity = $('[data-edit-id^="' + activeEntity + '/"]');
-
- // First, change the status of all PropertyEditor widgets to 'inactive'.
- this.$entityElements.each(function() {
- $(this).createEditable('setState', 'inactive', null, {reason: 'stop'});
- });
-
- // Then, change the status of PropertyEditor widgets of the currently
- // active entity to 'candidate'.
- $editableFieldsForEntity.each(function() {
- $(this).createEditable('setState', 'candidate');
- });
-
- // Manage the page's tab indexes.
- },
-
- /**
- * 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 (context && context.reason === 'stop') {
- if (from === 'candidate' && to === 'inactive') {
- accept = true;
- }
- }
- // 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', type: 'submit', 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') {
- // Discarded if it transitions from a changed state to 'candidate'.
- if (from === 'changed' || from === 'invalid') {
- // Retrieve the storage widget from DOM.
- var createStorageWidget = this.$el.data('DrupalCreateStorage');
- // Revert changes in the model, this will trigger the direct editable
- // content to be reset and redrawn.
- createStorageWidget.revertChanges(editor.options.entity);
- }
- 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);
- });
- },
-
- /**
- * Undecorates an editor (PropertyEditor).
- *
- * Whenever a property has been updated, the old HTML will be replaced by
- * the new (re-rendered) HTML. The EditableEntity widget will be destroyed,
- * as will be the PropertyEditor widget. This method ensures Edit's editor
- * views also are removed properly.
- *
- * @param editor
- * The PropertyEditor widget object.
- */
- undecorateEditor: function(editor) {
- editor.toolbarView.undelegateEvents();
- editor.toolbarView.remove();
- delete editor.toolbarView;
- editor.decorationView.undelegateEvents();
- // Don't call .remove() on the decoration view, because that would remove
- // a potentially rerendered field.
- delete editor.decorationView;
- }
-
- });
-
-})(jQuery, _, Backbone, Drupal, VIE);
diff --git a/core/modules/edit/js/backbone.drupalform.js b/core/modules/edit/js/backbone.drupalform.js
deleted file mode 100644
index 750d16f..0000000
--- a/core/modules/edit/js/backbone.drupalform.js
+++ /dev/null
@@ -1,184 +0,0 @@
-/**
- * @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] = undefined;
- 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) {
- var $form = jQuery('#edit_backstage form');
- // Fill in the value in any that isn't hidden or a submit button.
- $form.find(':input[type!="hidden"][type!="submit"]:not(select)')
- // Don't mess with the node summary.
- .not('[name$="\[summary\]"]').val(value);
- // Submit the form.
- $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').prop('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) {
- Backbone.syncDirectCleanUp();
-
- // 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] = jQuery(response.data).find('.field-item').html();
- 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 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);
- }
- }
-};
-
-/**
- * Cleans up the hidden form that Backbone.syncDirect uses for syncing.
- *
- * This is called automatically by Backbone.syncDirect when saving is successful
- * (i.e. when there are no validation errors). Only when editing is canceled
- * while a PropertyEditor widget is in the invalid state, this must be called
- * "manually" (in practice, ToolbarView does this). This is necessary because
- * Backbone.syncDirect is not aware of the application state, it only does the
- * syncing.
- * An alternative could be to also remove the hidden form when validation errors
- * occur, but then the form must be retrieved again, thus resulting in another
- * roundtrip, which is bad for front-end performance.
- */
-Backbone.syncDirectCleanUp = function() {
- var $submit = jQuery('#edit_backstage form .edit-form-submit');
- Drupal.edit.util.form.unajaxifySaving($submit);
- jQuery('#edit_backstage form').remove();
-};
-
-})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/createjs/editable.js b/core/modules/edit/js/createjs/editable.js
deleted file mode 100644
index 1316023..0000000
--- a/core/modules/edit/js/createjs/editable.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @file
- * Determines which editor (Create.js PropertyEditor widget) to use.
- */
-(function (jQuery, Drupal, 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';
-
- // The Create.js PropertyEditor widget configuration is not hardcoded; it
- // is generated by the server.
- this.options.propertyEditorWidgetsConfiguration = drupalSettings.edit.editors;
-
- jQuery.Midgard.midgardEditable.prototype._create.call(this);
- },
-
- _propertyEditorName: function(data) {
- // Pick a PropertyEditor widget for a property depending on its metadata.
- var propertyID = Drupal.edit.util.calcPropertyID(data.entity, data.property);
- return Drupal.edit.metadataCache[propertyID].editor;
- }
- });
-
-})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js b/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
deleted file mode 100644
index cde6163..0000000
--- a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @file
- * Override of Create.js' default "base" (plain contentEditable) widget.
- */
-(function (jQuery, Drupal) {
-
-"use strict";
-
- // @todo D8: use jQuery UI Widget bridging.
- // @see http://drupal.org/node/1874934#comment-7124904
- jQuery.widget('Midgard.direct', jQuery.Midgard.editWidget, {
-
- /**
- * Implements getEditUISettings() method.
- */
- getEditUISettings: function() {
- return { padding: true, unifiedToolbar: false, fullWidthToolbar: false };
- },
-
- /**
- * 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 '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();
- }
- break;
- case 'highlighted':
- break;
- case 'activating':
- this.options.activated();
- break;
- case 'active':
- // Sets the "contenteditable" attribute to "true".
- this.enable();
- break;
- case 'changed':
- break;
- case 'saving':
- break;
- case 'saved':
- break;
- case 'invalid':
- break;
- }
- }
-
- });
-
-})(jQuery, Drupal);
diff --git a/core/modules/edit/js/createjs/editingWidgets/formwidget.js b/core/modules/edit/js/createjs/editingWidgets/formwidget.js
deleted file mode 100644
index aa2dd0a..0000000
--- a/core/modules/edit/js/createjs/editingWidgets/formwidget.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/**
- * @file
- * Form-based Create.js widget for structured content in Drupal.
- */
-(function ($, Drupal) {
-
-"use strict";
-
- // @todo D8: change the name to "form" + use jQuery UI Widget bridging.
- // @see http://drupal.org/node/1874934#comment-7124904
- $.widget('Midgard.formEditEditor', $.Midgard.editWidget, {
-
- id: null,
- $formContainer: null,
-
- /**
- * Implements getEditUISettings() method.
- */
- getEditUISettings: function() {
- return { padding: false, unifiedToolbar: false, fullWidthToolbar: false };
- },
-
- /**
- * 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() {},
-
- /**
- * 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')
- .attr('role', 'dialog');
-
- // 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'));
- // Allow form widgets to detach properly.
- Drupal.detachBehaviors(this.$formContainer, null, 'unload');
- 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
deleted file mode 100644
index 580ff82..0000000
--- a/core/modules/edit/js/createjs/storage.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * @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
index 61157c4..ec3473c 100644
--- a/core/modules/edit/js/edit.js
+++ b/core/modules/edit/js/edit.js
@@ -1,103 +1,105 @@
/**
* @file
* Attaches behavior for the Edit module.
+ *
+ * Everything happens asynchronously, to allow for:
+ * - dynamically rendered contextual links
+ * - asynchronously retrieved (and cached) per-field in-place editing metadata
+ * - asynchronous setup of in-place editable field and "Quick edit" link
+ *
+ * To achieve this, there are several queues:
+ * - fieldsMetadataQueue: fields whose metadata still needs to be fetched.
+ * - fieldsAvailableQueue: queue of fields whose metadata is known, and for
+ * which it has been confirmed that the user has permission to edit them.
+ * However, FieldModels will only be created for them once there's a
+ * contextual link for their entity: when it's possible to initiate editing.
+ * - contextualLinksQueue: queue of contextual links on entities for which it
+ * is not yet known whether the user has permission to edit at >=1 of them.
*/
(function ($, _, Backbone, Drupal, drupalSettings) {
"use strict";
-Drupal.edit = { metadataCache: {}, contextualLinksQueue: [] };
+var options = $.extend({
+ strings: {
+ quickEdit: Drupal.t('Quick edit'),
+ stopQuickEdit: Drupal.t('Stop quick edit')
+ }
+}, drupalSettings.edit);
+
+// Tracks fields without metadata. Contains objects with the following keys:
+// - DOM el
+// - String editID
+var fieldsMetadataQueue = [];
+
+// Tracks fields ready for use. Contains objects with the following keys:
+// - DOM el
+// - String editID
+// - String entityID
+var fieldsAvailableQueue = [];
+
+// Tracks contextual links on entities. Contains objects with the following keys:
+// - String entityID
+// - DOM el
+// - DOM region
+var contextualLinksQueue = [];
-/**
- * Attach toggling behavior and in-place editing.
- */
Drupal.behaviors.edit = {
attach: function (context) {
- var $context = $(context);
- var $fields = $context.find('[data-edit-id]');
-
- // Initialize the Edit app.
- $('body').once('edit-init', Drupal.edit.init);
-
- function annotateField (field) {
- var hasField = _.has(Drupal.edit.metadataCache, field.editID);
- if (hasField) {
- var meta = Drupal.edit.metadataCache[field.editID];
-
- field.$el.addClass((meta.access) ? 'edit-allowed' : 'edit-disallowed');
- if (meta.access) {
- field.$el
- .attr('data-edit-field-label', meta.label)
- .attr('aria-label', meta.aria)
- .addClass('edit-field edit-type-' + ((meta.editor === 'form') ? 'form' : 'direct'));
- }
- }
- return hasField;
- }
+ // Initialize the Edit app once per page load.
+ $('body').once('edit-init', initEdit);
- // Find all fields in the context without metadata.
- var fieldsToAnnotate = _.map($fields.not('.edit-allowed, .edit-disallowed'), function (el) {
- var $el = $(el);
- return { $el: $el, editID: $el.attr('data-edit-id') };
+ // Process each field element: queue to be used or to fetch metadata.
+ $(context).find('[data-edit-id]').once('edit').each(function (index, fieldElement) {
+ processField(fieldElement);
});
- // Fields whose metadata is known (typically when they were just modified)
- // can be annotated immediately, those remaining must be requested.
- var remainingFieldsToAnnotate = _.reduce(fieldsToAnnotate, function (result, field) {
- if (!annotateField(field)) {
- result.push(field);
- }
- return result;
- }, []);
-
- // Make fields that could be annotated immediately available for editing.
- Drupal.edit.app.findEditableProperties($context);
-
- if (remainingFieldsToAnnotate.length) {
- $(window).ready(function () {
- var id = 'edit-load-metadata';
- // Create a temporary element to be able to use Drupal.ajax.
- var $el = jQuery('').appendTo('body');
- // Create a Drupal.ajax instance to load the form.
- Drupal.ajax[id] = new Drupal.ajax(id, $el, {
- url: drupalSettings.edit.metadataURL,
- event: 'edit-internal.edit',
- submit: { 'fields[]': _.pluck(remainingFieldsToAnnotate, 'editID') },
- // No progress indicator.
- progress: { type: null }
- });
- // Implement a scoped editMetaData AJAX command: calls the callback.
- Drupal.ajax[id].commands.editMetadata = function (ajax, response, status) {
- // Update the metadata cache.
- _.each(response.data, function (metadata, editID) {
- Drupal.edit.metadataCache[editID] = metadata;
- });
-
- // Annotate the remaining fields based on the updated access cache.
- _.each(remainingFieldsToAnnotate, annotateField);
-
- // Find editable fields, make them editable.
- Drupal.edit.app.findEditableProperties($context);
-
- // Metadata cache has been updated, try to set up more contextual
- // links now.
- Drupal.edit.contextualLinksQueue = _.filter(Drupal.edit.contextualLinksQueue, function (data) {
- return !Drupal.edit.setUpContextualLink(data);
- });
-
- // Delete the Drupal.ajax instance that called this very function.
- delete Drupal.ajax[id];
-
- // Also delete the temporary element.
- // $el.remove();
- };
- // This will ensure our scoped editMetadata AJAX command gets called.
- $el.trigger('edit-internal.edit');
+ // Fetch metadata for any fields that are queued to retrieve it.
+ fetchMissingMetadata(function (fieldElementsWithFreshMetadata) {
+ // Metadata has been fetched, reprocess fields whose metadata was missing.
+ _.each(fieldElementsWithFreshMetadata, processField);
+
+ // Metadata has been fetched, try to set up more contextual links now.
+ contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
+ return !initializeEntityContextualLink(contextualLink);
});
+ });
+ },
+ detach: function (context, settings, trigger) {
+ if (trigger === 'unload') {
+ deleteContainedModelsAndQueues($(context));
}
}
};
+Drupal.edit = {
+ // A Drupal.edit.AppView instance.
+ app: null,
+
+ collections: {
+ // All in-place editable entities (Drupal.edit.EntityModel) on the page.
+ entities: null,
+ // All in-place editable fields (Drupal.edit.FieldModel) on the page.
+ fields: null
+ },
+
+ // In-place editors will register themselves in this object.
+ editors: {},
+
+ // Per-field metadata that indicates whether in-place editing is allowed,
+ // which in-place editor should be used, etc.
+ Metadata: {
+ has: function (editID) { return _.has(this.data, editID); },
+ add: function (editID, metadata) { this.data[editID] = metadata; },
+ get: function (editID, key) {
+ return (key === undefined) ? this.data[editID] : this.data[editID][key];
+ },
+ intersection: function (editIDs) { return _.intersection(editIDs, _.keys(this.data)); },
+ // Contains the actual metadata, keyed by edit ID.
+ data: {}
+ }
+};
+
/**
* Detect contextual links on entities annotated by Edit; queue these to be
* processed.
@@ -105,19 +107,161 @@ Drupal.behaviors.edit = {
$(document).on('drupalContextualLinkAdded', function (event, data) {
if (data.$region.is('[data-edit-entity]')) {
var contextualLink = {
- entity: data.$region.attr('data-edit-entity'),
- $el: data.$el,
- $region: data.$region
+ entityID: data.$region.attr('data-edit-entity'),
+ el: data.$el[0],
+ region: data.$region[0]
};
// Set up contextual links for this, otherwise queue it to be set up later.
- if (!Drupal.edit.setUpContextualLink(contextualLink)) {
- Drupal.edit.contextualLinksQueue.push(contextualLink);
+ if (!initializeEntityContextualLink(contextualLink)) {
+ contextualLinksQueue.push(contextualLink);
}
}
});
/**
- * Attempts to set up a "Quick edit" contextual link.
+ * Extracts the entity ID from an edit ID.
+ *
+ * @param String editID
+ * An edit ID: a string of the format
+ * `////`.
+ * @return String
+ * An entity ID: a string of the format `/`.
+ */
+function extractEntityID (editID) {
+ return editID.split('/').slice(0, 2).join('/');
+}
+
+/**
+ * Initialize the Edit app.
+ *
+ * @param DOM bodyElement
+ * This document's body element.
+ */
+function initEdit (bodyElement) {
+ Drupal.edit.collections.entities = new Drupal.edit.EntityCollection();
+ Drupal.edit.collections.fields = new Drupal.edit.FieldCollection();
+
+ // Instantiate AppModel (application state) and AppView, which is the
+ // controller of the whole in-place editing experience.
+ Drupal.edit.app = new Drupal.edit.AppView({
+ el: bodyElement,
+ model: new Drupal.edit.AppModel(),
+ entitiesCollection: Drupal.edit.collections.entities,
+ fieldsCollection: Drupal.edit.collections.fields
+ });
+}
+
+/**
+ * Fetch the field's metadata; queue or initialize it (if EntityModel exists).
+ *
+ * @param DOM fieldElement
+ * A Drupal Field API field's DOM element with a data-edit-id attribute.
+ */
+function processField (fieldElement) {
+ var metadata = Drupal.edit.Metadata;
+ var editID = fieldElement.getAttribute('data-edit-id');
+
+ // Early-return if metadata for this field is mising.
+ if (!metadata.has(editID)) {
+ fieldsMetadataQueue.push({ el: fieldElement, editID: editID });
+ return;
+ }
+ // Early-return if the user is not allowed to in-place edit this field.
+ if (metadata.get(editID, 'access') !== true) {
+ return;
+ }
+
+ // If an EntityModel for this field already exists (and hence also a "Quick
+ // edit" contextual link), then initialize it immediately.
+ var entityID = extractEntityID(editID);
+ if (Drupal.edit.collections.entities.where({ id: entityID }).length > 0) {
+ initializeField(fieldElement, editID);
+ }
+ // Otherwise: queue the field. It is now available to be set up when its
+ // corresponding entity becomes in-place editable.
+ else {
+ fieldsAvailableQueue.push({ el: fieldElement, editID: editID, entityID: entityID });
+ }
+}
+
+/**
+ * Initialize a field: create FieldModel.
+ *
+ * @param DOM fieldElement
+ * The field's DOM element.
+ * @param String editID
+ * The field's edit ID.
+ */
+function initializeField (fieldElement, editID) {
+ var entityId = extractEntityID(editID);
+ var entity = Drupal.edit.collections.entities.where({ id: entityId })[0];
+
+ // @todo Refactor CSS to get rid of this.
+ $(fieldElement).addClass('edit-field');
+
+ // The FieldModel stores the state of an in-place editable entity field.
+ var field = new Drupal.edit.FieldModel({
+ entity: entity,
+ el: fieldElement,
+ editID: editID,
+ // @todo just pass the entire bit of metadata?
+ label: Drupal.edit.Metadata.get(editID, 'label'),
+ editor: Drupal.edit.Metadata.get(editID, 'editor'),
+ html: fieldElement.outerHTML,
+ acceptStateChange: _.bind(Drupal.edit.app.acceptEditorStateChange, Drupal.edit.app)
+ });
+
+ // Track all fields on the page.
+ Drupal.edit.collections.fields.add(field);
+}
+
+/**
+ * Fetches metadata for fields whose metadata is missing.
+ *
+ * Fields whose metadata is missing are tracked at fieldsMetadataQueue.
+ *
+ * @param Function callback
+ * A callback function that receives field elements whose metadata will just
+ * have been fetched.
+ */
+function fetchMissingMetadata (callback) {
+ if (fieldsMetadataQueue.length) {
+ var editIDs = _.pluck(fieldsMetadataQueue, 'editID');
+ var fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el');
+ fieldsMetadataQueue = [];
+
+ $(window).ready(function () {
+ var id = 'edit-load-metadata';
+ // Create a temporary element to be able to use Drupal.ajax.
+ var $el = jQuery('').appendTo('body');
+ // Create a Drupal.ajax instance to load the form.
+ Drupal.ajax[id] = new Drupal.ajax(id, $el, {
+ url: drupalSettings.edit.metadataURL,
+ event: 'edit-internal.edit',
+ submit: { 'fields[]': editIDs },
+ // No progress indicator.
+ progress: { type: null }
+ });
+ // Implement a scoped editMetaData AJAX command: calls the callback.
+ Drupal.ajax[id].commands.editMetadata = function (ajax, response, status) {
+ // Store the metadata.
+ _.each(response.data, function (fieldMetadata, editID) {
+ Drupal.edit.Metadata.add(editID, fieldMetadata);
+ });
+ // Clean-up.
+ delete Drupal.ajax[id];
+ $el.remove();
+
+ callback(fieldElementsWithoutMetadata);
+ };
+ // This will ensure our scoped editMetadata AJAX command gets called.
+ $el.trigger('edit-internal.edit');
+ });
+ }
+}
+
+/**
+ * Attempts to set up a "Quick edit" link and corresponding EntityModel.
*
* @param Object contextualLink
* An object with the following properties:
@@ -133,41 +277,56 @@ $(document).on('drupalContextualLinkAdded', function (event, data) {
* its fields).
* Returns false otherwise.
*/
-Drupal.edit.setUpContextualLink = function (contextualLink) {
+function initializeEntityContextualLink (contextualLink) {
+ var metadata = Drupal.edit.Metadata;
// Check if the user has permission to edit at least one of them.
function hasFieldWithPermission (editIDs) {
- var i, meta = Drupal.edit.metadataCache;
- for (i = 0; i < editIDs.length; i++) {
+ for (var i = 0; i < editIDs.length; i++) {
var editID = editIDs[i];
- if (_.has(meta, editID) && meta[editID].access === true) {
+ if (metadata.get(editID, 'access') === true) {
return true;
}
}
return false;
}
- // Checks if the metadata for all given editIDs exists.
+ // Checks if the metadata for all given edit IDs exists.
function allMetadataExists (editIDs) {
- var editIDsWithMetadata = _.intersection(editIDs, _.keys(Drupal.edit.metadataCache));
- return editIDs.length === editIDsWithMetadata.length;
+ return editIDs.length === metadata.intersection(editIDs).length;
}
- // Find the Edit IDs of all fields within this entity.
- var editIDs = [];
- contextualLink.$region
- .find('[data-edit-id^="' + contextualLink.entity + '/"]')
- .each(function () {
- editIDs.push($(this).attr('data-edit-id'));
- });
+ // Find all fields for this entity and collect their edit IDs.
+ var fields = _.where(fieldsAvailableQueue, { entityID: contextualLink.entityID });
+ var editIDs = _.pluck(fields, 'editID');
+ // No fields found yet.
+ if (editIDs.length === 0) {
+ return false;
+ }
// The entity for the given contextual link contains at least one field that
- // the current user may edit in-place; instantiate ContextualLinkView.
- if (hasFieldWithPermission(editIDs)) {
- new Drupal.edit.views.ContextualLinkView({
- el: $('
').prependTo(contextualLink.$el),
- model: Drupal.edit.app.model,
- entity: contextualLink.entity
+ // the current user may edit in-place; instantiate EntityModel and
+ // ContextualLinkView.
+ else if (hasFieldWithPermission(editIDs)) {
+ var entityModel = new Drupal.edit.EntityModel({
+ id: contextualLink.entityID,
+ el: contextualLink.region
});
+ Drupal.edit.collections.entities.add(entityModel);
+
+ // Initialize all queued fields within this entity (creates FieldModels).
+ _.each(fields, function (field) {
+ initializeField(field.el, field.editID);
+ });
+ fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
+
+ // Set up contextual link view.
+ var contextualLinkView = new Drupal.edit.ContextualLinkView($.extend({
+ el: $('
').prependTo(contextualLink.el),
+ model: entityModel,
+ appModel: Drupal.edit.app.model
+ }, options));
+ entityModel.set('contextualLinkView', contextualLinkView);
+
return true;
}
// There was not at least one field that the current user may edit in-place,
@@ -177,19 +336,58 @@ Drupal.edit.setUpContextualLink = function (contextualLink) {
}
return false;
-};
+}
+
+/**
+ * Delete models and queue items that are contained within a given context.
+ *
+ * Deletes any contained EntityModels (plus their associated FieldModels and
+ * ContextualLinkView) and FieldModels, as well as the corresponding queues.
+ *
+ * After EntityModels, FieldModels must also be deleted, because it is possible
+ * in Drupal for a field DOM element to exist outside of the entity DOM element,
+ * e.g. when viewing the full node, the title of the node is not rendered within
+ * the node (the entity) but as the page title.
+ *
+ * Note: this will not delete an entity that is actively being in-place edited.
+ *
+ * @param jQuery $context
+ * The context within which to delete.
+ */
+function deleteContainedModelsAndQueues($context) {
+ $context.find('[data-edit-entity]').addBack('[data-edit-entity]').each(function (index, entityElement) {
+ // Delete entity model.
+ // @todo change to findWhere() as soon as we have Backbone 1.0
+ var entityModels = Drupal.edit.collections.entities.where({el: entityElement});
+ if (entityModels.length) {
+ // @todo Make this cleaner; let EntityModel.destroy() do this?
+ var contextualLinkView = entityModels[0].get('contextualLinkView');
+ contextualLinkView.undelegateEvents();
+ contextualLinkView.remove();
+
+ entityModels[0].destroy();
+ }
+
+ // Filter queue.
+ function hasOtherRegion (contextualLink) {
+ return contextualLink.region !== entityElement;
+ }
+ contextualLinksQueue = _.filter(contextualLinksQueue, hasOtherRegion);
+ });
+
+ $context.find('[data-edit-id]').addBack('[data-edit-id]').each(function (index, fieldElement) {
+ // Delete field models.
+ Drupal.edit.collections.fields.chain()
+ .filter(function (fieldModel) { return fieldModel.get('el') === fieldElement; })
+ .invoke('destroy');
-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();
- // For now, we work with a singleton app, because for Drupal.behaviors to be
- // able to discover new editable properties that get AJAXed in, it must know
- // with which app instance they should be associated.
- Drupal.edit.app = new Drupal.edit.EditAppView({
- el: $('body'),
- model: appModel
+ // Filter queues.
+ function hasOtherFieldElement (field) {
+ return field.el !== fieldElement;
+ }
+ fieldsMetadataQueue = _.filter(fieldsMetadataQueue, hasOtherFieldElement);
+ fieldsAvailableQueue = _.filter(fieldsAvailableQueue, hasOtherFieldElement);
});
-};
+}
})(jQuery, _, Backbone, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/editors/directEditor.js b/core/modules/edit/js/editors/directEditor.js
new file mode 100644
index 0000000..621860a
--- /dev/null
+++ b/core/modules/edit/js/editors/directEditor.js
@@ -0,0 +1,107 @@
+/**
+ * @file
+ * contentEditable-based in-place editor for plain text content.
+ */
+(function ($, Drupal) {
+
+"use strict";
+
+Drupal.edit.editors.direct = Drupal.edit.EditorView.extend({
+
+ $textElement: null,
+
+ /**
+ * {@inheritdoc}
+ */
+ initialize: function (options) {
+ Drupal.edit.EditorView.prototype.initialize.call(this, options);
+
+ var model = this.model;
+
+ // Store the actual value of this field. We'll need this to restore the
+ // original value when the user discards his modifications.
+ var $textElement = this.$textElement = this.$el.find('.field-item:first');
+ model.set('originalValue', jQuery.trim(this.$textElement.text()));
+
+ // Sets the state to 'changed' whenever the content has changed.
+ var previousText = model.get('originalValue');
+ $textElement.on('keyup paste', function (event) {
+ var currentText = jQuery.trim($textElement.text());
+ if (previousText !== currentText) {
+ previousText = currentText;
+ model.set('currentValue', currentText);
+ model.set('state', 'changed');
+ }
+ });
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ getEditedElement: function () {
+ return this.$textElement;
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ stateChange: function (model, state) {
+ var from = model.previous('state');
+ var to = state;
+ switch (to) {
+ case 'inactive':
+ break;
+ case 'candidate':
+ if (from !== 'inactive') {
+ this.$textElement.removeAttr('contentEditable');
+ }
+ if (from === 'invalid') {
+ this.removeValidationErrors();
+ }
+ break;
+ case 'highlighted':
+ break;
+ case 'activating':
+ // As soon as the current state change has propagated, apply this one.
+ // @see http://jsfiddle.net/5MVzp/2/ vs. http://jsfiddle.net/5MVzp/3/
+ var that = this;
+ _.defer(function() {
+ that.model.set('state', 'active');
+ });
+ break;
+ case 'active':
+ this.$textElement.attr('contentEditable', 'true');
+ break;
+ case 'changed':
+ break;
+ case 'saving':
+ if (from === 'invalid') {
+ this.removeValidationErrors();
+ }
+ this.save();
+ break;
+ case 'saved':
+ break;
+ case 'invalid':
+ this.showValidationErrors();
+ break;
+ }
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ getEditUISettings: function () {
+ return { padding: true, unifiedToolbar: false, fullWidthToolbar: false };
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ revert: function () {
+ this.$textElement.html(this.model.get('originalValue'));
+ }
+
+});
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/js/editors/formEditor.js b/core/modules/edit/js/editors/formEditor.js
new file mode 100644
index 0000000..2225ced
--- /dev/null
+++ b/core/modules/edit/js/editors/formEditor.js
@@ -0,0 +1,187 @@
+/**
+ * @file
+ * Form-based in-place editor. Works for any field type.
+ */
+(function ($, Drupal) {
+
+"use strict";
+
+Drupal.edit.editors.form = Drupal.edit.EditorView.extend({
+
+ $formContainer: null,
+
+ /**
+ * {@inheritdoc}
+ */
+ stateChange: function (model, state) {
+ var from = model.previous('state');
+ var to = state;
+ switch (to) {
+ case 'inactive':
+ break;
+ case 'candidate':
+ if (from !== 'inactive') {
+ this.disable();
+ }
+ if (from === 'invalid') {
+ // No need to call removeValidationErrors() for this in-place editor!
+ }
+ break;
+ case 'highlighted':
+ break;
+ case 'activating':
+ this.enable();
+ break;
+ case 'active':
+ break;
+ case 'changed':
+ break;
+ case 'saving':
+ this.save();
+ break;
+ case 'saved':
+ break;
+ case 'invalid':
+ this.showValidationErrors();
+ break;
+ }
+ },
+
+ /**
+ * Enables the widget.
+ */
+ enable: function () {
+ // Generate a DOM-compatible ID for the form container DOM element.
+ var id = 'edit-form-for-' + this.model.get('editID').replace(/\//g, '_');
+
+ // Render form container.
+ this.$formContainer = $(Drupal.theme('editFormContainer', {
+ id: id,
+ loadingMsg: Drupal.t('Loading…')}
+ ));
+ this.$formContainer
+ .find('.edit-form')
+ .addClass('edit-editable edit-highlighted edit-editing')
+ .attr('role', 'dialog');
+
+ // Insert form container in DOM.
+ if (this.$el.css('display') === 'inline') {
+ this.$formContainer.prependTo(this.$el.offsetParent());
+ // Position the form container to render on top of the field's element.
+ var pos = this.$el.position();
+ this.$formContainer.css('left', pos.left).css('top', pos.top);
+ }
+ else {
+ this.$formContainer.insertBefore(this.$el);
+ }
+
+ // Load form, insert it into the form container and attach event handlers.
+ var that = this;
+ var formOptions = {
+ propertyID: this.model.get('editID'),
+ $editorElement: this.$el,
+ nocssjs: false
+ };
+ Drupal.edit.util.form.load(formOptions, function(form, ajax) {
+ Drupal.ajax.prototype.commands.insert(ajax, {
+ data: form,
+ selector: '#' + id + ' .placeholder'
+ });
+
+ var $submit = that.$formContainer.find('.edit-form-submit');
+ Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
+ that.$formContainer
+ .on('formUpdated.edit', ':input', function () {
+ that.model.set('state', 'changed');
+ })
+ .on('keypress.edit', 'input', function (event) {
+ if (event.keyCode === 13) {
+ return false;
+ }
+ });
+
+ // The in-place editor has loaded; change state to 'active'.
+ that.model.set('state', 'active');
+ });
+ },
+
+ /**
+ * Disables the widget.
+ */
+ disable: function () {
+ if (this.$formContainer === null) {
+ return;
+ }
+
+ Drupal.edit.util.form.unajaxifySaving(this.$formContainer.find('.edit-form-submit'));
+ // Allow form widgets to detach properly.
+ Drupal.detachBehaviors(this.$formContainer, null, 'unload');
+ this.$formContainer
+ .off('change.edit', ':input')
+ .off('keypress.edit', 'input')
+ .remove();
+ this.$formContainer = null;
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ save: function () {
+ var $formContainer = this.model.attributes.editorView.$formContainer;
+ var $submit = $formContainer.find('.edit-form-submit');
+ var base = $submit.attr('id');
+ var that = this;
+
+ // Successfully saved.
+ Drupal.ajax[base].commands.editFieldFormSaved = function(ajax, response, status) {
+ Drupal.edit.util.form.unajaxifySaving(jQuery(ajax.element));
+
+ // First, transition the state to 'saved'.
+ that.model.set('state', 'saved');
+ // Then, set the 'html' attribute on the field model. This will cause the
+ // field to be rerendered.
+ that.model.set('html', response.data);
+ };
+
+ // Unsuccessfully saved; validation errors.
+ Drupal.ajax[base].commands.editFieldFormValidationErrors = function(ajax, response, status) {
+ that.model.set('validationErrors', response.data);
+ that.model.set('state', 'invalid');
+ };
+
+ // 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 Drupal.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');
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ showValidationErrors: function () {
+ this.$formContainer
+ .find('.edit-form')
+ .addClass('edit-validation-error')
+ .find('form')
+ .prepend(this.model.get('validationErrors'));
+ }
+});
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/js/models/AppModel.js b/core/modules/edit/js/models/AppModel.js
new file mode 100644
index 0000000..75da33b
--- /dev/null
+++ b/core/modules/edit/js/models/AppModel.js
@@ -0,0 +1,22 @@
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+$.extend(Drupal.edit, {
+
+ /**
+ *
+ */
+ AppModel: Backbone.Model.extend({
+ defaults: {
+ highlightedEditor: null,
+ activeEditor: null,
+ // Reference to a ModalView-instance if a state change requires confirmation.
+ activeModal: null
+ }
+ })
+});
+
+}(jQuery, _, Backbone, Drupal));
diff --git a/core/modules/edit/js/models/EntityModel.js b/core/modules/edit/js/models/EntityModel.js
new file mode 100644
index 0000000..9edfea3
--- /dev/null
+++ b/core/modules/edit/js/models/EntityModel.js
@@ -0,0 +1,49 @@
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+$.extend(Drupal.edit, {
+
+ /**
+ *
+ */
+ EntityModel: Backbone.Model.extend({
+ defaults: {
+ // The DOM element that represents this entity. It may seem bizarre to
+ // have a DOM element in a Backbone Model, but we need to be able to map
+ // entities in the DOM to EntityModels in memory.
+ el: null,
+
+ // An entity ID, of the form "/", e.g. "node/1".
+ id: null,
+ // Indicates whether this instance of this entity is currently being
+ // edited.
+ isActive: false,
+ // A Drupal.edit.FieldCollection for all fields of this entity.
+ fields: null
+ },
+ initialize: function () {
+ this.set('fields', new Drupal.edit.FieldCollection());
+ },
+ destroy: function(options) {
+ if (this.get('isActive')) {
+ throw new Error("EntityModel cannot be destroyed while it is being edited.");
+ }
+ Backbone.Model.prototype.destroy.apply(this, options);
+
+ // Destroy all fields of this entity.
+ this.get('fields').each(function (fieldModel) {
+ fieldModel.destroy();
+ });
+ }
+ }),
+
+ // A collection of EntityModels.
+ EntityCollection: Backbone.Collection.extend({
+ model: Drupal.edit.EntityModel
+ })
+});
+
+}(jQuery, _, Backbone, Drupal));
diff --git a/core/modules/edit/js/models/FieldModel.js b/core/modules/edit/js/models/FieldModel.js
new file mode 100644
index 0000000..a1a6e84
--- /dev/null
+++ b/core/modules/edit/js/models/FieldModel.js
@@ -0,0 +1,132 @@
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+$.extend(Drupal.edit, {
+
+ /**
+ *
+ */
+ FieldModel: Backbone.Model.extend({
+ defaults: {
+ // The DOM element that represents this field. It may seem bizarre to have
+ // a DOM element in a Backbone Model, but we need to be able to map fields
+ // in the DOM to FieldModels in memory.
+ el: null,
+
+ // Possible states: @see Drupal.edit.FieldModel.states.
+ state: 'inactive',
+ // A Drupal.edit.EntityModel. Its "fields" attribute, which is a
+ // FieldCollection, is automatically updated to include this FieldModel.
+ entity: null,
+
+
+ //
+ // Data set by the metadata callback.
+ //
+ // The ID of the in-place editor to use.
+ editor: null,
+ // The label to use.
+ label: null,
+
+ //
+ // Data derived from the information in the DOM.
+ //
+
+ // The edit ID, format: `::::`.
+ // @todo rename to "id"?
+ editID: null,
+ // The full HTML representation of this field (with the element that has
+ // the data-edit-id as the outer element). Used to propagate changes from
+ // this field instance to other instances of the same field.
+ html: null,
+
+ // Not the full HTML representation of this field, but the "actual"
+ // original value of the field, stored by the used in-place editor, and
+ // in a representation that can be chosen by the in-place editor.
+ originalValue: null,
+ // Analogous to originalValue, but the current value.
+ currentValue: null,
+
+ validationErrors: null,
+
+ //
+ // Callbacks.
+ //
+
+ // Callback function for validating changes between states. Receives the
+ // previous state, new state, context, and a callback
+ acceptStateChange: null,
+
+
+ //
+ // Attached views.
+ //
+ decorated: false,
+ editorView: null,
+ toolbarView: null,
+ decorationView: null
+
+ },
+ initialize: function () {
+ this.get('entity').get('fields').add(this);
+ this.on('change:state', function(model, state) {
+ console.log('%c [MOD] ' + this.previous('state') + ' → ' + state + ' ' + model.get('editID'), 'background-color: blue; color: white');
+ });
+ this.on('error', function(model, error) {
+ console.log('%c' + model.get("editID") + " " + error, 'color: orange');
+ });
+ },
+ destroy: function(options) {
+ if (this.get('state') !== 'inactive') {
+ throw new Error("FieldModel cannot be destroyed if it is not inactive state.");
+ }
+ Backbone.Model.prototype.destroy.apply(this, options);
+ },
+ validate: function (attrs, options) {
+ // We only care about validating the 'state' attribute.
+ if (!_.has(attrs, 'state')) {
+ return;
+ }
+
+ var current = this.get('state');
+ var next = attrs.state;
+ if (current !== next) {
+ // Ensure it's a valid state.
+ if (_.indexOf(this.constructor.states, next) === -1) {
+ return '"' + next + '" is an invalid state';
+ }
+ // Check if the acceptStateChange callback accepts it.
+ if (!this.get('acceptStateChange')(current, next, options)) {
+ return 'state change not accepted';
+ }
+ }
+ },
+ // Parses the `/` part from the edit ID.
+ getEntityID: function() {
+ return this.get('editID').split('/').slice(0, 2).join('/');
+ },
+ // Parses the `//` part from the edit ID.
+ getFieldID: function() {
+ return this.get('editID').split('/').slice(2, 5).join('/');
+ }
+ }, {
+ states: [
+ 'inactive', 'candidate', 'highlighted',
+ 'activating', 'active', 'changed', 'saving', 'saved', 'invalid'
+ ],
+ followsStateSequence: function (from, to) {
+ return _.indexOf(this.states, from) < _.indexOf(this.states, to);
+ }
+ }),
+
+ // A collection of FieldModels.
+ // @todo link back to the entity at the collection level (not the collection element level)?
+ FieldCollection: Backbone.Collection.extend({
+ model: Drupal.edit.FieldModel
+ })
+});
+
+}(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
deleted file mode 100644
index 0c90fd0..0000000
--- a/core/modules/edit/js/models/edit-app-model.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @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: {
- activeEntity: null,
- 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/util.js b/core/modules/edit/js/util.js
index e0aa490..d287a1d 100644
--- a/core/modules/edit/js/util.js
+++ b/core/modules/edit/js/util.js
@@ -12,10 +12,6 @@ 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;
-};
-
/**
* Retrieves a setting of the editor-specific Edit UI integration.
*
@@ -24,7 +20,7 @@ Drupal.edit.util.calcPropertyID = function(entity, predicate) {
* be used.
*
* @param editor
- * A Create.js PropertyEditor widget instance.
+ * A Drupal.edit.Editor instance.
* @param setting
* Name of the Edit UI integration setting.
*
diff --git a/core/modules/edit/js/viejs/EditService.js b/core/modules/edit/js/viejs/EditService.js
deleted file mode 100644
index 00cb04b..0000000
--- a/core/modules/edit/js/viejs/EditService.js
+++ /dev/null
@@ -1,289 +0,0 @@
-/**
- * @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);
- },
-
- _getViewForElement:function (element, collectionView) {
- var viewInstance;
-
- jQuery.each(this.views, function () {
- if (jQuery(this.el).get(0) === element.get(0)) {
- if (collectionView && !this.template) {
- return true;
- }
- viewInstance = this;
- return false;
- }
- });
- return viewInstance;
- },
-
- _registerEntityView:function (entity, element, isNew) {
- if (!element.length) {
- return;
- }
-
- // Let's only have this overhead for direct types. Form-based editors are
- // handled in backbone.drupalform.js and the PropertyEditor instance.
- if (jQuery(element).hasClass('edit-type-form')) {
- return;
- }
-
- var service = this;
- var viewInstance = this._getViewForElement(element);
- if (viewInstance) {
- return viewInstance;
- }
-
- viewInstance = new this.vie.view.Entity({
- model:entity,
- el:element,
- tagName:element.get(0).nodeName,
- vie:this.vie,
- service:this.name
- });
-
- this.views.push(viewInstance);
-
- return viewInstance;
- },
-
- save: function(saveable) {
- var correct = saveable instanceof this.vie.Savable;
- if (!correct) {
- throw "Invalid Savable passed";
- }
-
- if (!saveable.options.element) {
- // FIXME: we could find element based on subject
- throw "Unable to write entity to edit.module-markup, no element given";
- }
-
- if (!saveable.options.entity) {
- throw "Unable to write to edit.module-markup, no entity given";
- }
-
- var $element = jQuery(saveable.options.element);
- this._writeEntity(saveable.options.entity, saveable.options.element);
- saveable.resolve();
- },
-
- _writeEntity:function (entity, element) {
- var service = this;
- this.findPredicateElements(this.getElementSubject(element), element, true).each(function () {
- var predicateElement = jQuery(this);
- var predicate = service.getElementPredicate(predicateElement);
- if (!entity.has(predicate)) {
- return true;
- }
-
- var value = entity.get(predicate);
- if (value && value.isCollection) {
- // Handled by CollectionViews separately
- return true;
- }
- if (value === service.readElementValue(predicate, predicateElement)) {
- return true;
- }
- // Unlike in the VIE's RdfaService no (re-)mapping needed here.
- predicateElement.html(value);
- });
- return true;
- },
-
- // 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);
- }
-
- var entityInstance = new this.vie.Entity(entity);
- entityInstance = this.vie.entities.addOrUpdate(entityInstance, {
- updateOptions: {
- silent: true,
- ignoreChanges: true
- }
- });
-
- this._registerEntityView(entityInstance, element);
- return entityInstance;
- },
-
- _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 range = predicate.split('/')[0];
- 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(predicate, predicateElement);
- if (value === null && !emptyValues) {
- return;
- }
-
- entityPredicates[predicate] = value;
- entityPredicates[predicate + '/rendered'] = predicateElement[0].outerHTML;
- });
- return entityPredicates;
- },
-
- readElementValue : function(predicate, element) {
- // Unlike in RdfaService there is parsing needed here.
- if (element.hasClass('edit-type-form')) {
- return undefined;
- }
- else {
- 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();
- // Make sure that element is wrapped by jQuery.
- var $element = jQuery(element);
-
- // 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/AppView.js b/core/modules/edit/js/views/AppView.js
new file mode 100644
index 0000000..a124c77
--- /dev/null
+++ b/core/modules/edit/js/views/AppView.js
@@ -0,0 +1,384 @@
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+$.extend(Drupal.edit, {
+
+ /**
+ *
+ */
+ AppView: Backbone.View.extend({
+
+ // Configuration for state handling.
+ activeEditorStates: [],
+ singleEditorStates: [],
+
+ /**
+ * {@inheritdoc}
+ */
+ initialize: function (options) {
+ _.bindAll(this, 'appStateChange', 'acceptEditorStateChange', 'editorStateChange');
+
+ // Instantiate configuration for state handling.
+ // @see Drupal.edit.FieldModel.states
+ this.activeEditorStates = ['activating', 'active'];
+ this.singleEditorStates = _.union(['highlighted'], this.activeEditorStates);
+
+ options.entitiesCollection
+ // Track app state.
+ .on('change:isActive', this.appStateChange, this)
+ .on('change:isActive', this.enforceSingleActiveEntity, this);
+
+ options.fieldsCollection
+ // Track app state.
+ .on('change:state', this.editorStateChange, this)
+ // Respond to field model HTML representation change events.
+ .on('change:html', this.propagateUpdatedField, this)
+ .on('change:html', this.renderUpdatedField, this)
+ // Respond to addition.
+ .on('add', this.rerenderedFieldToCandidate, this)
+ // Respond to destruction.
+ .on('destroy', this.undecorate, this);
+ },
+
+ /**
+ * Handles setup/teardown and state changes when the active entity changes.
+ */
+ appStateChange: function (entityModel, isActive) {
+ var app = this;
+ if (isActive) {
+ // Move all fields of this entity from the 'inactive' state to the
+ // 'candidate' state.
+ entityModel.get('fields').each(function (fieldModel) {
+ // First, set up decoration views.
+ app.decorate(fieldModel);
+ // Second, change the field's state.
+ fieldModel.set('state', 'candidate');
+ });
+ }
+ else {
+ // Move all fields of this entity from whatever state they are in to
+ // the 'inactive' state.
+ entityModel.get('fields').each(function (fieldModel) {
+ // First, change the field's state.
+ fieldModel.set('state', 'inactive', { reason: 'stop' });
+ // Second, tear down decoration views.
+ app.undecorate(fieldModel);
+ });
+ }
+ },
+
+ /**
+ * Accepts or reject editor (Editor) state changes.
+ *
+ * This is what ensures that the app is in control of what happens.
+ *
+ * @param String from
+ * The previous state.
+ * @param String to
+ * The new state.
+ * @param null|Object context
+ * The context that is trying to trigger the state change.
+ * @param Function callback
+ * The callback function that should receive the state acceptance result.
+ */
+ acceptEditorStateChange: function(from, to, context, callback) {
+ var accept = true;
+
+ console.log("accept? %s → %s (reason: %s)", from, to, (context && context.reason) ? context.reason : 'NONE');
+
+ // If the app is in view mode, then reject all state changes except for
+ // those to 'inactive'.
+ if (context && (context.reason === 'stop' || context.reason === 'rerender')) {
+ if (from === 'candidate' && to === 'inactive') {
+ accept = true;
+ }
+ }
+ // 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 (!Drupal.edit.FieldModel.followsStateSequence(from, 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 {
+ // Do not accept this change right now, instead open a modal
+ // that will ask the user to confirm his choice.
+ accept = false;
+ // The callback will be called from the helper function.
+ this._confirmStopEditing(callback);
+ }
+ }
+ }
+ }
+ }
+
+ return accept;
+ },
+
+ // @todo rename to decorateField
+ decorate: function (fieldModel) {
+ // Create a new Editor.
+ var editorName = fieldModel.get('editor');
+ fieldModel.set('editorName', editorName);
+ var editorView = new Drupal.edit.editors[editorName]({
+ el: $(fieldModel.get('el')),
+ model: fieldModel
+ });
+
+ // Toolbars are rendered "on-demand" (highlighting or activating).
+ // They are a sibling element before the editor's DOM element.
+ var toolbarView = new Drupal.edit.FieldToolbarView({
+ model: fieldModel,
+ $field: $(editorView.getEditedElement()),
+ editorView: editorView
+ });
+
+ // Decorate the editor's DOM element depending on its state.
+ var decorationView = new Drupal.edit.EditorDecorationView({
+ el: $(editorView.getEditedElement()),
+ model: fieldModel,
+ editorView: editorView,
+ toolbarId: toolbarView.getId()
+ });
+
+ // Create references in the field model; necessary for undecorate() and
+ // necessary for some EditorView implementations.
+ fieldModel.set('editorView', editorView);
+ fieldModel.set('toolbarView', toolbarView);
+ fieldModel.set('decorationView', decorationView);
+ fieldModel.set('decorated', true);
+ },
+
+ // @todo rename to undecorateField
+ undecorate: function (fieldModel) {
+ // Early-return if this field was not yet decorated.
+ if (!fieldModel.get('decorated')) {
+ return;
+ }
+
+ // Unbind event handlers; remove toolbar element; delete toolbar view.
+ fieldModel.get('toolbarView').remove();
+ fieldModel.unset('toolbarView');
+
+ // Unbind event handlers; delete decoration view. Don't remove the element
+ // because that would remove the field itself.
+ fieldModel.get('decorationView').remove();
+ fieldModel.unset('decorationView');
+
+ // Unbind event handlers; delete editor view. Don't remove the element
+ // because that would remove the field itself.
+ fieldModel.get('editorView').remove();
+ fieldModel.unset('editorView');
+ },
+
+ /**
+ * Asks the user to confirm whether he wants to stop editing via a modal.
+ *
+ * @see acceptEditorStateChange()
+ */
+ _confirmStopEditing: function () {
+ // Only instantiate if there isn't a modal instance visible yet.
+ if (!this.model.get('activeModal')) {
+ var that = this;
+ var modal = new Drupal.edit.ModalView({
+ model: this.model,
+ message: Drupal.t('You have unsaved changes'),
+ buttons: [
+ { action: 'discard', classes: 'gray-button', label: Drupal.t('Discard changes') },
+ { action: 'save', type: 'submit', classes: 'blue-button', label: Drupal.t('Save') }
+ ],
+ callback: function(action) {
+ // The active modal has been removed.
+ that.model.set('activeModal', null);
+ // Set the state that matches the user's action.
+ var targetState = (action === 'discard') ? 'candidate' : 'saving';
+ that.model.get('activeEditor').set('state', 'candidate', { confirmed: true });
+ }
+ });
+ 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();
+ }
+ },
+
+ /**
+ * Reacts to field state changes; tracks global state.
+ *
+ * @param Drupal.edit.FieldModel fieldModel
+ * @param String state
+ * The state of the associated field. One of Drupal.edit.FieldModel.states.
+ */
+ editorStateChange: function(fieldModel, state) {
+ var from = fieldModel.previous('state');
+ var to = state;
+
+ console.log("%c [APP] %s → %s %s", "background-color: black; color: white", from, to, fieldModel.get('editID'));
+
+ // Keep track of the highlighted editor in the global state.
+ if (_.indexOf(this.singleEditorStates, to) !== -1 && this.model.get('highlightedEditor') !== fieldModel) {
+ this.model.set('highlightedEditor', fieldModel);
+ }
+ else if (this.model.get('highlightedEditor') === fieldModel && 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') !== fieldModel) {
+ this.model.set('activeEditor', fieldModel);
+ }
+ else if (this.model.get('activeEditor') === fieldModel && to === 'candidate') {
+ // Discarded if it transitions from a changed state to 'candidate'.
+ if (from === 'changed' || from === 'invalid') {
+ fieldModel.get('editorView').revert();
+ }
+ this.model.set('activeEditor', null);
+ }
+ },
+
+ // Updates all known instances of a specific entity field whenever the HTML
+ // representation of one of them has changed.
+ // @todo: this is currently prototype-level code, test this. The principle is
+ // sound, but the tricky thing is that an editID includes the view mode, but
+ // we actually want to update the same field in other view modes too, which
+ // means this method will have to check if there are any such instances, and
+ // if so, go to the server and re-render those too.
+ propagateUpdatedField: function (changedModel) {
+ // changedModel.collection.where({ editID: changedModel.get('editID') })
+ // .set('html', changedModel.get('html'));
+ },
+
+ renderUpdatedField: function (field) {
+ // Get data necessary to rerender property before it is unavailable.
+ var html = field.get('html');
+ var $fieldWrapper = $(field.get('el')).closest('[data-edit-id]');
+ var $context = $fieldWrapper.parent();
+
+ // First set the state to 'candidate', to allow all attached views to
+ // clean up all their "active state"-related changes.
+ // @todo: get rid of this, ensure all views do the proper clean-up when
+ // going directly to the 'inactive' state.
+ field.set('state', 'candidate');
+
+ // Set the field's state to 'inactive', to enable the updating of its DOM
+ // value.
+ field.set('state', 'inactive', { reason: 'rerender' });
+
+ // Destroy the field model; this will cause all attached views to be
+ // destroyed too, and removal from all collections in which it exists.
+ field.destroy();
+
+ // Replace the old content with the new content.
+ $fieldWrapper.replaceWith(html);
+
+ // Attach behaviors again to the modified piece of HTML; this will create
+ // a new field model and call rerenderedFieldToCandidate() with it.
+ Drupal.attachBehaviors($context);
+ },
+
+ /**
+ * If the new in-place editable field is for the entity that's currently
+ * being edited, then transition it to the 'candidate' state.
+ *
+ * This happens when a field was modified, saved and hence rerendered.
+ *
+ * @param Drupal.edit.FieldModel fieldModel
+ * A field that was just added to the collection of fields.
+ */
+ rerenderedFieldToCandidate: function (fieldModel) {
+ var activeEntity = Drupal.edit.collections.entities.where({ isActive: true })[0];
+
+ // Early-return if there is no active entity.
+ if (activeEntity === null) {
+ return;
+ }
+
+ // If the field's entity is the active entity, make it a candidate.
+ if (fieldModel.get('entity') === activeEntity) {
+ this.decorate(fieldModel);
+ fieldModel.set('state', 'candidate');
+ }
+ },
+
+ /**
+ * EntityModel Collection change handler, called on change:isActive, enforces
+ * a single active entity.
+ */
+ enforceSingleActiveEntity: function (changedEntityModel) {
+ // When an entity is deactivated, we don't need to enforce anything.
+ if (changedEntityModel.get('isActive') === false) {
+ return;
+ }
+
+ // This entity was activated; deactivate all other entities.
+ changedEntityModel.collection.chain()
+ .filter(function (entityModel) {
+ return entityModel.get('isActive') === true && entityModel !== changedEntityModel;
+ })
+ .each(function (entityModel) {
+ entityModel.set('isActive', false);
+ });
+ }
+
+ })
+});
+
+}(jQuery, _, Backbone, Drupal));
diff --git a/core/modules/edit/js/views/ContextualLinkView.js b/core/modules/edit/js/views/ContextualLinkView.js
new file mode 100644
index 0000000..1dac235
--- /dev/null
+++ b/core/modules/edit/js/views/ContextualLinkView.js
@@ -0,0 +1,74 @@
+/**
+ * @file
+ * A Backbone View that a dynamic contextual link.
+ */
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+// Main model: Drupal.edit.EntityModel
+Drupal.edit.ContextualLinkView = Backbone.View.extend({
+
+ events: function () {
+ // Prevents delay and simulated mouse events.
+ function touchEndToClick (event) {
+ event.preventDefault();
+ event.target.click();
+ }
+ return {
+ 'click a': function (event) {
+ event.preventDefault();
+ this.model.set('isActive', !this.model.get('isActive'));
+ },
+ 'touchEnd a': touchEndToClick
+ };
+ },
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param Object options
+ * An object with the following keys:
+ * - appModel: the application state model
+ * - strings: the strings for the "Quick edit" link
+ */
+ initialize: function (options) {
+ // @todo Is this a good idea at all? I'm very unsure…
+ if (!this.model instanceof Drupal.edit.EntityModel) {
+ throw new Error('Drupal.edit.ContextualLinkView only accepts Drupal.edit.EntityModel models.');
+ }
+
+ // Initial render.
+ this.render();
+
+ // Re-render whenever this entity's isActive attribute changes.
+ this.model.on('change:isActive', this.render, this);
+
+ // Hide the contextual links whenever an in-place editor is active.
+ this.options.appModel.on('change:activeEditor', this.toggleContextualLinksVisibility, this);
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ render: function () {
+ var strings = this.options.strings;
+ var text = !this.model.get('isActive') ? strings.quickEdit : strings.stopQuickEdit;
+ this.$el.find('a').text(text);
+ return this;
+ },
+
+ /**
+ * Hides the contextual links if an in-place editor is active.
+ *
+ * @param Drupal.edit.AppModel model
+ * @param null|Drupal.edit.FieldModel activeEditor
+ * The model of the field that is currently being edited, or, if none, null.
+ */
+ toggleContextualLinksVisibility: function (model, activeEditor) {
+ this.$el.parents('.contextual').toggle(activeEditor === null);
+ }
+
+});
+
+})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/EditorDecorationView.js b/core/modules/edit/js/views/EditorDecorationView.js
new file mode 100644
index 0000000..5854a50
--- /dev/null
+++ b/core/modules/edit/js/views/EditorDecorationView.js
@@ -0,0 +1,372 @@
+/**
+ * @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.EditorDecorationView = Backbone.View.extend({
+ toolbarId: null,
+
+ _widthAttributeIsEmpty: null,
+
+ events: {
+ 'mouseenter.edit' : 'onMouseEnter',
+ 'mouseleave.edit' : 'onMouseLeave',
+ 'click': 'onClick',
+ 'tabIn.edit': 'onMouseEnter',
+ 'tabOut.edit': 'onMouseLeave'
+ },
+
+ /**
+ * Implements Backbone.View.prototype.initialize().
+ *
+ * @param Object options
+ * An object with the following keys:
+ * - editorView: the editor object with an 'options' object that has these keys:
+ * * property: the predicate of the property.
+ * * editorName: the name of the Editor.
+ * - toolbarId: the ID attribute of the toolbar as rendered in the DOM.
+ */
+ initialize: function (options) {
+ this.editorView = options.editorView;
+
+ this.toolbarId = options.toolbarId;
+
+ this.model.on('change:state', this.stateChange, this);
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ remove: function () {
+ // The el property is the field, which should not be removed. Remove the
+ // pointer to it, then call Backbone.View.prototype.remove().
+ this.setElement();
+ Backbone.View.prototype.remove.call(this);
+ },
+
+ /**
+ * Determines the actions to take given a change of state.
+ *
+ * @param Drupal.edit.FieldModel model
+ * @param String state
+ * The state of the associated field. One of Drupal.edit.FieldModel.states.
+ */
+ stateChange: function (model, state) {
+ var from = model.previous('state');
+ var to = state;
+ switch (to) {
+ case 'inactive':
+ this.undecorate();
+ break;
+ case 'candidate':
+ this.decorate();
+ if (from !== 'inactive') {
+ this.stopHighlight();
+ if (from !== 'highlighted') {
+ this.stopEdit();
+ }
+ }
+ break;
+ case 'highlighted':
+ this.startHighlight();
+ break;
+ case 'activating':
+ // NOTE: this state is not used by every editor! It's only used by those
+ // that need to interact with the server.
+ this.prepareEdit();
+ break;
+ case 'active':
+ if (from !== 'activating') {
+ this.prepareEdit();
+ }
+ this.startEdit();
+ break;
+ case 'changed':
+ break;
+ case 'saving':
+ break;
+ case 'saved':
+ break;
+ case 'invalid':
+ break;
+ }
+ },
+
+ /**
+ * Starts hover; transitions to 'highlight' state.
+ *
+ * @param jQuery event
+ */
+ onMouseEnter: function (event) {
+ var that = this;
+ this._ignoreHoveringVia(event, '#' + this.toolbarId, function () {
+ that.model.set('state', 'highlighted');
+ event.stopPropagation();
+ });
+ },
+
+ /**
+ * Stops hover; transitions to 'candidate' state.
+ *
+ * @param jQuery event
+ */
+ onMouseLeave: function (event) {
+ var that = this;
+ this._ignoreHoveringVia(event, '#' + this.toolbarId, function () {
+ that.model.set('state', 'candidate', { reason: 'mouseleave' });
+ event.stopPropagation();
+ });
+ },
+
+ /**
+ * Transition to 'activating' stage.
+ *
+ * @param jQuery event
+ */
+ onClick: function (event) {
+ this.model.set('state', 'activating');
+ event.preventDefault();
+ event.stopPropagation();
+ },
+
+ /**
+ * Adds classes used to indicate an elements editable state.
+ */
+ decorate: function () {
+ this.$el.addClass('edit-animate-fast edit-candidate edit-editable');
+ },
+
+ /**
+ * Removes classes used to indicate an elements editable state.
+ */
+ undecorate: function () {
+ this.$el.removeClass('edit-candidate edit-editable edit-highlighted edit-editing');
+ },
+
+ /**
+ * Adds that class that indicates that an element is highlighted.
+ */
+ startHighlight: function () {
+ // Animations.
+ var that = this;
+ // Use a timeout to grab the next available animation frame.
+ setTimeout(function () {
+ that.$el.addClass('edit-highlighted');
+ }, 0);
+ },
+
+ /**
+ * Removes the class that indicates that an element is highlighted.
+ */
+ stopHighlight: function () {
+ this.$el.removeClass('edit-highlighted');
+ },
+
+ /**
+ * Removes the class that indicates that an element as editable.
+ */
+ prepareEdit: function () {
+ this.$el.addClass('edit-editing');
+
+ // While editing, do not show any other editors.
+ $('.edit-candidate').not('.edit-editing').removeClass('edit-editable');
+ },
+
+ /**
+ * Updates the display of the editable element once editing has begun.
+ */
+ startEdit: function () {
+ if (this.getEditUISetting('padding')) {
+ this._pad();
+ }
+ },
+
+ /**
+ * Removes the class that indicates that an element is being edited.
+ *
+ * Reapplies the class that indicates that a candidate editable element is
+ * again available to be edited.
+ */
+ stopEdit: function () {
+ this.$el.removeClass('edit-highlighted edit-editing');
+
+ // Make the other editors show up again.
+ $('.edit-candidate').addClass('edit-editable');
+
+ if (this.getEditUISetting('padding')) {
+ this._unpad();
+ }
+ },
+
+ /**
+ * Retrieves a setting of the editor-specific Edit UI integration.
+ *
+ * @param String setting
+ *
+ * @see Drupal.edit.util.getEditUISetting().
+ */
+ getEditUISetting: function (setting) {
+ return Drupal.edit.util.getEditUISetting(this.editorView, setting);
+ },
+
+ /**
+ * Adds padding around the editable element in order to make it pop visually.
+ */
+ _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())
+ .css('background-color', this._getBgColor(this.$el));
+ }
+
+ // 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);
+ },
+
+ /**
+ * Removes the padding around the element being edited when editing ceases.
+ */
+ _unpad: function () {
+ var self = this;
+
+ // 1) Set the empty width again.
+ if (this._widthAttributeIsEmpty) {
+ this.$el
+ .addClass('edit-animate-disable-width')
+ .css('width', '')
+ .css('background-color', '');
+ }
+
+ // 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 DOM $e
+ */
+ _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.
+ *
+ * Convert extraneous values and information into numbers ready for
+ * subtraction.
+ *
+ * @param DOM $e
+ */
+ _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 String pos
+ * (optional) 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.
+ *
+ * When a hover occurs to/from another element, invoke the callback.
+ *
+ * @param jQuery event
+ * @param jQuery closest
+ * A jQuery-wrapped DOM element or compatibale jQuery input. The element
+ * whose mouseenter and mouseleave events should be ignored.
+ * @param Function 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/EditorView.js b/core/modules/edit/js/views/EditorView.js
new file mode 100644
index 0000000..791549e
--- /dev/null
+++ b/core/modules/edit/js/views/EditorView.js
@@ -0,0 +1,263 @@
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+$.extend(Drupal.edit, {
+
+ /**
+ * A base implementation that outlines the structure for in-place editors.
+ *
+ * Specific in-place editor implementations should subclass (extend) this View
+ * and override whichever method they deem necessary to override.
+ *
+ * Look at Drupal.edit.editors.form and Drupal.edit.editors.direct for
+ * examples.
+ */
+ EditorView: Backbone.View.extend({
+
+ /**
+ * Implements Backbone.View.prototype.initialize().
+ *
+ * If you override this method, you should call this method (the parent
+ * class' initialize()) first, like this:
+ * Drupal.edit.EditorView.prototype.initialize.call(this, options);
+ *
+ * For an example, @see Drupal.edit.editors.direct.
+ */
+ initialize: function (options) {
+ this.model.on('change:state', this.stateChange, this);
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ remove: function () {
+ // The el property is the field, which should not be removed. Remove the
+ // pointer to it, then call Backbone.View.prototype.remove().
+ this.setElement();
+ Backbone.View.prototype.remove.call(this);
+ },
+
+ /**
+ * Returns the edited element.
+ *
+ * For some single cardinality fields, it may be necessary or useful to
+ * not in-place edit (and hence decorate) the DOM element with the
+ * data-edit-id attribute (which is the field's wrapper), but a specific
+ * element within the field's wrapper.
+ * e.g. using a WYSIWYG editor on a body field should happen on the DOM
+ * element containing the text itself, not on the field wrapper.
+ *
+ * For example, @see Drupal.edit.editors.direct.
+ *
+ * @return jQuery
+ * A jQuery-wrapped DOM element.
+ */
+ getEditedElement: function () {
+ return this.$el;
+ },
+
+ /**
+ * Returns 3 Edit UI settings that depend on the in-place editor:
+ * - padding: @todo
+ * - unifiedToolbar: @todo
+ * - fullWidthToolbar: @todo
+ */
+ getEditUISettings: function () {
+ return { padding: false, unifiedToolbar: false, fullWidthToolbar: false };
+ },
+
+ /**
+ * Determines the actions to take given a change of state.
+ *
+ * @param Drupal.edit.FieldModel model
+ * @param String state
+ * The state of the associated field. One of Drupal.edit.FieldModel.states.
+ */
+ stateChange: function (model, state) {
+ var from = model.previous('state');
+ var to = state;
+ switch (to) {
+ case 'inactive':
+ // An in-place editor view will not yet exist in this state, hence
+ // this will never be reached. Listed for sake of completeness.
+ break;
+ case 'candidate':
+ // Nothing to do for the typical in-place editor: it should not be
+ // visible yet.
+
+ // Except when we come from the 'invalid' state, then we clean up.
+ if (from === 'invalid') {
+ this.removeValidationErrors();
+ }
+ break;
+ case 'highlighted':
+ // Nothing to do for the typical in-place editor: it should not be
+ // visible yet.
+ break;
+ case 'activating':
+ // The user has indicated he wants to do in-place editing: if
+ // something needs to be loaded (CSS/JavaScript/server data/…), then
+ // do so at this stage, and once the in-place editor is ready,
+ // set the 'active' state.
+ // A "loading" indicator will be shown in the UI for as long as the
+ // field remains in this state.
+ var that = this;
+ var loadDependencies = function (callback) {
+ // Do the loading here.
+ callback();
+ };
+ loadDependencies(function () {
+ that.model.set('state', 'active');
+ });
+ break;
+ case 'active':
+ // The user can now actually use the in-place editor.
+ break;
+ case 'changed':
+ // Nothing to do for the typical in-place editor. The UI will show an
+ // indicator that the field has changed.
+ break;
+ case 'saving':
+ // When the user has indicated he wants to save his changes to this
+ // field, this state will be entered.
+ // If the previous saving attempt resulted in validation errors, the
+ // previous state will be 'invalid'. Clean up those validation errors
+ // while the user is saving.
+ if (from === 'invalid') {
+ this.removeValidationErrors();
+ }
+ this.save();
+ break;
+ case 'saved':
+ // Nothing to do for the typical in-place editor. Immediately after
+ // being saved, a field will go to the 'candidate' state, where it
+ // should no longer be visible (after all, the field will then again
+ // just be a *candidate* to be in-place edited).
+ break;
+ case 'invalid':
+ // The modified field value was attempted to be saved, but there were
+ // validation errors.
+ this.showValidationErrors();
+ break;
+ }
+ },
+
+ /**
+ * Reverts the modified value back to the original value (before editing
+ * started).
+ */
+ revert: function () {
+ // A no-op by default; each editor should implement reverting itself.
+
+ // Note that if the in-place editor does not cause the FieldModel's
+ // element to be modified, then nothing needs to happen.
+ },
+
+ /**
+ * Saves the modified value in the in-place editor for this field.
+ */
+ save: function () {
+ var model = this.model;
+
+ function fillAndSubmitForm (value) {
+ var $form = jQuery('#edit_backstage form');
+ // Fill in the value in any that isn't hidden or a submit button.
+ $form.find(':input[type!="hidden"][type!="submit"]:not(select)')
+ // Don't mess with the node summary.
+ .not('[name$="\\[summary\\]"]').val(value);
+ // Submit the form.
+ $form.find('.edit-form-submit').trigger('click.edit');
+ }
+
+ var value = model.get('currentValue');
+
+ var formOptions = {
+ propertyID: model.get('editID'),
+ $editorElement: $(this.getEditedElement()),
+ 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').prop('novalidate', true);
+ var $submit = jQuery('#edit_backstage form .edit-form-submit');
+ var base = Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
+
+ function removeHiddenForm () {
+ Drupal.edit.util.form.unajaxifySaving($submit);
+ jQuery('#edit_backstage').remove();
+ }
+
+ // Successfully saved.
+ Drupal.ajax[base].commands.editFieldFormSaved = function (ajax, response, status) {
+ removeHiddenForm();
+
+ // First, transition the state to 'saved'.
+ model.set('state', 'saved');
+ // Then, set the 'html' attribute on the field model. This will cause the
+ // field to be rerendered.
+ model.set('html', response.data);
+ };
+
+ // Unsuccessfully saved; validation errors.
+ Drupal.ajax[base].commands.editFieldFormValidationErrors = function(ajax, response, status) {
+ removeHiddenForm();
+
+ model.set('validationErrors', response.data);
+ model.set('state', 'invalid');
+ };
+
+ // 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
+ // the form-based in-place editor, but pointless for any other: the form
+ // itself won't be visible at all anyway! So, we just ignore it.
+ Drupal.ajax[base].commands.editFieldForm = function() {};
+
+ fillAndSubmitForm(value);
+ });
+ },
+
+ /**
+ * Shows validation error messages.
+ *
+ * Should be called when the state is changed to 'invalid'.
+ */
+ showValidationErrors: function () {
+ var $errors = $('')
+ .append(this.model.get('validationErrors'));
+ $(this.model.get('el'))
+ .addClass('edit-validation-error')
+ .after($errors);
+ },
+
+ /**
+ * Cleans up validation error messages.
+ *
+ * Should be called when the state is changed to 'candidate' or 'saving'. In
+ * the case of the latter: the user has modified the value in the in-place
+ * editor again to attempt to save again. In the case of the latter: the
+ * invalid value was discarded.
+ */
+ removeValidationErrors: function () {
+ $(this.model.get('el'))
+ .removeClass('edit-validation-error')
+ .next('.edit-validation-errors')
+ .remove();
+ }
+
+ })
+});
+
+}(jQuery, _, Backbone, Drupal));
diff --git a/core/modules/edit/js/views/FieldToolbarView.js b/core/modules/edit/js/views/FieldToolbarView.js
new file mode 100644
index 0000000..117debb
--- /dev/null
+++ b/core/modules/edit/js/views/FieldToolbarView.js
@@ -0,0 +1,408 @@
+/**
+ * @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.FieldToolbarView = Backbone.View.extend({
+ $field: null,
+
+ _loader: null,
+ _loaderVisibleStart: 0,
+
+ _id: null,
+
+ events: {
+ 'click.edit button.label': 'onClickInfoLabel',
+ 'mouseleave.edit': 'onMouseLeave',
+ 'click.edit button.field-save': 'onClickSave',
+ 'click.edit button.field-close': 'onClickClose'
+ },
+
+ /**
+ * Implements Backbone.View.prototype.initialize().
+ */
+ initialize: function (options) {
+ this.$field = options.$field;
+ this.editorView = options.editorView;
+
+ this._loader = null;
+ this._loaderVisibleStart = 0;
+
+ // Generate a DOM-compatible ID for the form container DOM element.
+ this._id = 'edit-toolbar-for-' + this.model.get('editID').replace(/\//g, '_');
+
+ this.model.on('change:state', this.stateChange, this);
+ },
+
+ /**
+ * Implements Backbone.View.prototype.render().
+ *
+ * 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._id
+ })));
+
+ // Insert in DOM.
+ if (this.$field.css('display') === 'inline') {
+ this.$el.prependTo(this.$field.offsetParent());
+ var pos = this.$field.position();
+ this.$el.css('left', pos.left).css('top', pos.top);
+ }
+ else {
+ this.$el.insertBefore(this.$field);
+ }
+
+ return this;
+ },
+
+ /**
+ * Determines the actions to take given a change of state.
+ *
+ * @param Drupal.edit.FieldModel model
+ * @param String state
+ * The state of the associated field. One of Drupal.edit.FieldModel.states.
+ */
+ stateChange: function (model, state) {
+ var from = model.previous('state');
+ var to = state;
+ switch (to) {
+ case 'inactive':
+ if (from) {
+ this.remove();
+ }
+ break;
+ case 'candidate':
+ if (from === 'inactive') {
+ this.render();
+ }
+ else {
+ // Remove all toolgroups; they're no longer necessary.
+ this.$el
+ .removeClass('edit-highlighted edit-editing')
+ .find('.edit-toolbar .edit-toolgroup').remove();
+ if (from !== 'highlighted' && this.getEditUISetting('padding')) {
+ this._unpad();
+ }
+ }
+ break;
+ case 'highlighted':
+ // As soon as we highlight, make sure we have a toolbar in the DOM (with at least a title).
+ this.startHighlight();
+ break;
+ case 'activating':
+ this.setLoadingIndicator(true);
+ break;
+ case 'active':
+ this.startEdit();
+ this.setLoadingIndicator(false);
+ if (this.getEditUISetting('fullWidthToolbar')) {
+ this.$el.addClass('edit-toolbar-fullwidth');
+ }
+
+ if (this.getEditUISetting('padding')) {
+ this._pad();
+ }
+ if (this.getEditUISetting('unifiedToolbar')) {
+ this.insertWYSIWYGToolGroups();
+ }
+ break;
+ case 'changed':
+ this.$el
+ .find('button.save')
+ .addClass('blue-button')
+ .removeClass('gray-button');
+ break;
+ case 'saving':
+ this.setLoadingIndicator(true);
+ break;
+ case 'saved':
+ this.setLoadingIndicator(false);
+ break;
+ case 'invalid':
+ this.setLoadingIndicator(false);
+ break;
+ }
+ },
+
+ /**
+ * Redirects the click.edit-event to the editor DOM element.
+ *
+ * @param jQuery event
+ */
+ onClickInfoLabel: function (event) {
+ event.stopPropagation();
+ event.preventDefault();
+ // Redirects the event to the editor DOM element.
+ this.$field.trigger('click.edit');
+ },
+
+ /**
+ * Controls mouseleave events.
+ *
+ * A mouseleave to the editor doesn't matter; a mouseleave to something else
+ * counts as a mouseleave on the editor itself.
+ *
+ * @param jQuery event
+ */
+ onMouseLeave: function (event) {
+ if (event.relatedTarget !== this.$field[0] && !$.contains(this.$field, event.relatedTarget)) {
+ this.$field.trigger('mouseleave.edit');
+ }
+ event.stopPropagation();
+ },
+
+ /**
+ * Set the model state to 'saving' when the save button is clicked.
+ *
+ * @param jQuery event
+ */
+ onClickSave: function (event) {
+ event.stopPropagation();
+ event.preventDefault();
+ this.model.set('state', 'saving');
+ },
+
+ /**
+ * Sets the model state to candidate when the cancel button is clicked.
+ *
+ * @param jQuery event
+ */
+ onClickClose: function (event) {
+ event.stopPropagation();
+ event.preventDefault();
+ this.model.set('state', 'candidate', { reason: 'cancel' });
+ },
+
+ /**
+ * Indicates in the 'info' toolgroup that we're waiting for a server reponse.
+ *
+ * Prevents flickering loading indicator by only showing it after 0.6 seconds
+ * and if it is shown, only hiding it after another 0.6 seconds.
+ *
+ * @param Boolean enabled
+ * Whether the loading indicator should be displayed or not.
+ */
+ setLoadingIndicator: function (enabled) {
+ var that = this;
+ if (enabled) {
+ this._loader = setTimeout(function() {
+ that.addClass('info', 'loading');
+ that._loaderVisibleStart = new Date().getTime();
+ }, 600);
+ }
+ else {
+ var currentTime = new Date().getTime();
+ clearTimeout(this._loader);
+ if (this._loaderVisibleStart) {
+ setTimeout(function() {
+ that.removeClass('info', 'loading');
+ }, this._loaderVisibleStart + 600 - currentTime);
+ }
+ this._loader = null;
+ this._loaderVisibleStart = 0;
+ }
+ },
+
+ /**
+ *
+ */
+ startHighlight: function () {
+ // Retrieve the lavel to show for this field.
+ var label = this.model.get('label');
+
+ this.$el
+ .addClass('edit-highlighted')
+ .find('.edit-toolbar')
+ // Append the "info" toolgroup into the toolbar.
+ .append(Drupal.theme('editToolgroup', {
+ classes: 'info edit-animate-only-background-and-padding',
+ buttons: [
+ { label: label, classes: 'blank-button label' }
+ ]
+ }));
+
+ // 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'), type: 'submit', classes: 'field-save save gray-button' },
+ { label: '' + Drupal.t('Close') + '', classes: 'field-close close gray-button' }
+ ]
+ }));
+ this.show('ops');
+ },
+
+ /**
+ * Retrieves a setting of the editor-specific Edit UI integration.
+ *
+ * @param String setting
+ *
+ * @see Drupal.edit.util.getEditUISetting().
+ */
+ getEditUISetting: function (setting) {
+ return Drupal.edit.util.getEditUISetting(this.editorView, setting);
+ },
+
+ /**
+ * Adjusts the toolbar to accomodate padding on the editor.
+ *
+ * @see EditorDecorationView._pad().
+ */
+ _pad: function () {
+ // The whole toolbar must move to the top when the property's DOM element
+ // is displayed inline.
+ if (this.$field.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' });
+
+ if (this.getEditUISetting('fullWidthToolbar')) {
+ $hf.css({ width: this.$field.width() + 10 });
+ }
+ },
+
+ /**
+ * Undoes the changes made by _pad().
+ *
+ * @see EditorDecorationView._unpad().
+ */
+ _unpad: function () {
+ // Move the toolbar back to its original position.
+ var $hf = this.$el.find('.edit-toolbar-heightfaker');
+ $hf.css({ bottom: '1px', left: '' });
+
+ if (this.getEditUISetting('fullWidthToolbar')) {
+ $hf.css({ width: '' });
+ }
+ },
+
+ /**
+ *
+ */
+ insertWYSIWYGToolGroups: function () {
+ this.$el
+ .find('.edit-toolbar')
+ .append(Drupal.theme('editToolgroup', {
+ id: this.getFloatedWysiwygToolgroupId(),
+ classes: 'wysiwyg-floated',
+ buttons: []
+ }))
+ .append(Drupal.theme('editToolgroup', {
+ id: this.getMainWysiwygToolgroupId(),
+ classes: 'wysiwyg-main',
+ buttons: []
+ }));
+
+ // Animate the toolgroups into visibility.
+ var that = this;
+ setTimeout(function () {
+ that.show('wysiwyg-floated');
+ that.show('wysiwyg-main');
+ }, 0);
+ },
+
+ /**
+ * Retrieves the ID for this toolbar's container.
+ *
+ * Only used to make sane hovering behavior possible.
+ *
+ * @return String
+ * A string that can be used as the ID for this toolbar's container.
+ */
+ getId: function () {
+ return 'edit-toolbar-for-' + this._id;
+ },
+
+ /**
+ * Retrieves the ID for this toolbar's floating WYSIWYG toolgroup.
+ *
+ * Used to provide an abstraction for any WYSIWYG editor to plug in.
+ *
+ * @return String
+ * A string that can be used as the ID.
+ */
+ getFloatedWysiwygToolgroupId: function () {
+ return 'edit-wysiwyg-floated-toolgroup-for-' + this._id;
+ },
+
+ /**
+ * Retrieves the ID for this toolbar's main WYSIWYG toolgroup.
+ *
+ * Used to provide an abstraction for any WYSIWYG editor to plug in.
+ *
+ * @return String
+ * A string that can be used as the ID.
+ */
+ getMainWysiwygToolgroupId: function () {
+ return 'edit-wysiwyg-main-toolgroup-for-' + 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/js/views/ModalView.js b/core/modules/edit/js/views/ModalView.js
new file mode 100644
index 0000000..15497cc
--- /dev/null
+++ b/core/modules/edit/js/views/ModalView.js
@@ -0,0 +1,80 @@
+/**
+ * @file
+ * A Backbone View that provides an interactive modal.
+ */
+(function ($, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit.ModalView = Backbone.View.extend({
+
+ message: null,
+ buttons: null,
+ callback: null,
+ $elementsToHide: null,
+
+ events: {
+ 'click button': 'onButtonClick'
+ },
+
+ /**
+ * Implements Backbone.View.prototype.initialize().
+ *
+ * @param Object 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.View.prototype.render().
+ */
+ render: function () {
+ 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);
+
+ // Show the modal with an animation.
+ var that = this;
+ setTimeout(function() {
+ that.$el.removeClass('edit-animate-invisible');
+ }, 0);
+ },
+
+ /**
+ * Passes the clicked button action to the callback; closes the modal.
+ *
+ * @param jQuery 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);
+ }
+});
+
+})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/contextuallink-view.js b/core/modules/edit/js/views/contextuallink-view.js
deleted file mode 100644
index 472bd6e..0000000
--- a/core/modules/edit/js/views/contextuallink-view.js
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * @file
- * A Backbone View that a dynamic contextual link.
- */
-(function ($, _, Backbone, Drupal) {
-
-"use strict";
-
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.views = Drupal.edit.views || {};
-Drupal.edit.views.ContextualLinkView = Backbone.View.extend({
-
- entity: null,
-
- events: function () {
- // Prevents delay and simulated mouse events.
- function touchEndToClick (event) {
- event.preventDefault();
- event.target.click();
- }
- return {
- 'click a': 'onClick',
- 'touchEnd a': touchEndToClick
- };
- },
-
- /**
- * Implements Backbone Views' initialize() function.
- *
- * @param options
- * An object with the following keys:
- * - entity: the entity ID (e.g. node/1) of the entity
- */
- initialize: function (options) {
- this.entity = options.entity;
-
- // Initial render.
- this.render();
-
- // Re-render whenever the app state's active entity changes.
- this.model.on('change:activeEntity', this.render, this);
-
- // Hide the contextual links whenever an in-place editor is active.
- this.model.on('change:activeEditor', this.toggleContextualLinksVisibility, this);
- },
-
- /**
- * Equates clicks anywhere on the overlay to clicking the active editor's (if
- * any) "close" button.
- *
- * @param {Object} event
- */
- onClick: function (event) {
- event.preventDefault();
-
- var that = this;
- function updateActiveEntity () {
- // The active entity is the current entity, i.e. stop editing the current
- // entity.
- if (that.model.get('activeEntity') === that.entity) {
- that.model.set('activeEntity', null);
- }
- // The active entity is different from the current entity, i.e. start
- // editing this entity instead of the previous one.
- else {
- that.model.set('activeEntity', that.entity);
- }
- }
-
- // If there's an active editor, attempt to set its state to 'candidate', and
- // only then do what the user asked.
- // (Only when all PropertyEditor widgets of an entity are in the 'candidate'
- // state, it is possible to stop editing it.)
- var activeEditor = this.model.get('activeEditor');
- if (activeEditor) {
- var editableEntity = activeEditor.options.widget;
- var predicate = activeEditor.options.property;
- editableEntity.setState('candidate', predicate, { reason: 'stop or switch' }, function (accepted) {
- if (accepted) {
- updateActiveEntity();
- }
- else {
- // No change.
- }
- });
- }
- // Otherwise, we can immediately do what the user asked.
- else {
- updateActiveEntity();
- }
- },
-
- /**
- * Render the "Quick edit" contextual link.
- */
- render: function () {
- var activeEntity = this.model.get('activeEntity');
- var string = (activeEntity !== this.entity) ? Drupal.t('Quick edit') : Drupal.t('Stop quick edit');
- this.$el.find('a').text(string);
- return this;
- },
-
- /**
- * Model change handler; hides the contextual links if an editor is active.
- *
- * @param Drupal.edit.models.EditAppModel model
- * An EditAppModel model.
- * @param jQuery|null activeEditor
- * The active in-place editor (jQuery object) or, if none, null.
- */
- toggleContextualLinksVisibility: function (model, activeEditor) {
- this.$el.parents('.contextual').toggle(activeEditor === null);
- }
-
-});
-
-})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/modal-view.js b/core/modules/edit/js/views/modal-view.js
deleted file mode 100644
index b98c876..0000000
--- a/core/modules/edit/js/views/modal-view.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @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 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() {
- 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);
-
- // 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);
- }
-});
-
-})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/propertyeditordecoration-view.js b/core/modules/edit/js/views/propertyeditordecoration-view.js
deleted file mode 100644
index bee33d1..0000000
--- a/core/modules/edit/js/views/propertyeditordecoration-view.js
+++ /dev/null
@@ -1,363 +0,0 @@
-/**
- * @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({
-
- toolbarId: null,
-
- _widthAttributeIsEmpty: null,
-
- events: {
- 'mouseenter.edit' : 'onMouseEnter',
- 'mouseleave.edit' : 'onMouseLeave',
- 'click': 'onClick',
- 'tabIn.edit': 'onMouseEnter',
- 'tabOut.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.
- * * widget: the parent EditableEntity widget.
- * * editorName: the name of the PropertyEditor 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.predicate = this.editor.options.property;
- this.editorName = this.editor.options.editorName;
-
- // Only start listening to events as soon as we're no longer in the 'inactive' state.
- this.undelegateEvents();
- },
-
- /**
- * Listens to editor state changes.
- */
- stateChange: function(from, to) {
- switch (to) {
- case 'inactive':
- if (from !== null) {
- this.undecorate();
- if (from === 'invalid') {
- this._removeValidationErrors();
- }
- }
- break;
- case 'candidate':
- this.decorate();
- if (from !== 'inactive') {
- this.stopHighlight();
- if (from !== 'highlighted') {
- this.stopEdit();
- if (from === 'invalid') {
- this._removeValidationErrors();
- }
- }
- }
- break;
- case 'highlighted':
- this.startHighlight();
- break;
- case 'activating':
- // NOTE: this state is not used by every editor! It's only used by those
- // that need to interact with the server.
- this.prepareEdit();
- break;
- case 'active':
- if (from !== 'activating') {
- this.prepareEdit();
- }
- this.startEdit();
- break;
- case 'changed':
- break;
- case 'saving':
- if (from === 'invalid') {
- this._removeValidationErrors();
- }
- 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();
- });
- },
-
- /**
- * Clicks: transition to 'activating' stage.
- *
- * @param event
- */
- onClick: function(event) {
- var editableEntity = this.editor.options.widget;
- editableEntity.setState('activating', this.predicate);
- event.preventDefault();
- event.stopPropagation();
- },
-
- decorate: function () {
- this.$el.addClass('edit-animate-fast edit-candidate edit-editable');
- this.delegateEvents();
- },
-
- undecorate: function () {
- this.$el
- .removeClass('edit-candidate edit-editable edit-highlighted edit-editing');
- this.undelegateEvents();
- },
-
- startHighlight: function () {
- // Animations.
- var that = this;
- setTimeout(function() {
- that.$el.addClass('edit-highlighted');
- }, 0);
- },
-
- stopHighlight: function() {
- this.$el
- .removeClass('edit-highlighted');
- },
-
- prepareEdit: function() {
- 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');
- },
-
- startEdit: function() {
- if (this.getEditUISetting('padding')) {
- this._pad();
- }
- },
-
- stopEdit: function() {
- 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 (this.getEditUISetting('padding')) {
- this._unpad();
- }
- },
-
- /**
- * Retrieves a setting of the editor-specific Edit UI integration.
- *
- * @see Drupal.edit.util.getEditUISetting().
- */
- getEditUISetting: function(setting) {
- return Drupal.edit.util.getEditUISetting(this.editor, setting);
- },
-
- _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())
- .css('background-color', this._getBgColor(this.$el));
- }
-
- // 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', '')
- .css('background-color', '');
- }
-
- // 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();
- }
- },
-
- /**
- * 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() {
- if (this.editorName !== 'form') {
- this.$el
- .removeClass('edit-validation-error')
- .next('.edit-validation-errors')
- .remove();
- }
- }
-
-});
-
-})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/toolbar-view.js b/core/modules/edit/js/views/toolbar-view.js
deleted file mode 100644
index f4b2123..0000000
--- a/core/modules/edit/js/views/toolbar-view.js
+++ /dev/null
@@ -1,490 +0,0 @@
-/**
- * @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,
-
- _loader: null,
- _loaderVisibleStart: 0,
-
- _id: null,
-
- events: {
- 'click.edit button.label': 'onClickInfoLabel',
- 'mouseleave.edit': 'onMouseLeave',
- 'click.edit button.field-save': 'onClickSave',
- 'click.edit button.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.
- * * 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;
-
- this._loader = null;
- this._loaderVisibleStart = 0;
-
- // Generate a DOM-compatible ID for the toolbar DOM element.
- this._id = Drupal.edit.util.calcPropertyID(this.entity, this.predicate).replace(/\//g, '_');
- },
-
- /**
- * Listens to editor state changes.
- */
- stateChange: function(from, to) {
- switch (to) {
- case 'inactive':
- if (from) {
- this.remove();
- if (this.editorName !== 'form') {
- Backbone.syncDirectCleanUp();
- }
- }
- break;
- case 'candidate':
- if (from === 'inactive') {
- this.render();
- }
- else {
- if (this.editorName !== 'form') {
- Backbone.syncDirectCleanUp();
- }
- // Remove all toolgroups; they're no longer necessary.
- this.$el
- .removeClass('edit-highlighted edit-editing')
- .find('.edit-toolbar .edit-toolgroup').remove();
- if (from !== 'highlighted' && this.getEditUISetting('padding')) {
- this._unpad();
- }
- }
- break;
- case 'highlighted':
- // As soon as we highlight, make sure we have a toolbar in the DOM (with at least a title).
- this.startHighlight();
- break;
- case 'activating':
- this.setLoadingIndicator(true);
- break;
- case 'active':
- this.startEdit();
- this.setLoadingIndicator(false);
- if (this.getEditUISetting('fullWidthToolbar')) {
- this.$el.addClass('edit-toolbar-fullwidth');
- }
-
- if (this.getEditUISetting('padding')) {
- this._pad();
- }
- if (this.getEditUISetting('unifiedToolbar')) {
- this.insertWYSIWYGToolGroups();
- }
- break;
- case 'changed':
- this.$el
- .find('button.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);
-
- // Now that the changes to this property have been saved, the saved
- // attributes are now the "original" attributes.
- entity._originalAttributes = entity._previousAttributes = _.clone(entity.attributes);
-
- // Get data necessary to rerender property before it is unavailable.
- var updatedProperty = entity.get(predicate + '/rendered');
- var $propertyWrapper = editor.element.closest('.edit-field');
- var $context = $propertyWrapper.parent();
-
- editableEntity.setState('candidate', predicate);
- // Unset the property, because it will be parsed again from the DOM, iff
- // its new value causes it to still be rendered.
- entity.unset(predicate, { silent: true });
- entity.unset(predicate + '/rendered', { silent: true });
- // Trigger event to allow for proper clean-up of editor-specific views.
- editor.element.trigger('destroyedPropertyEditor.edit', editor);
-
- // Replace the old content with the new content.
- $propertyWrapper.replaceWith(updatedProperty);
- Drupal.attachBehaviors($context);
- },
-
- // 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' });
- },
-
- /**
- * Indicates in the 'info' toolgroup that we're waiting for a server reponse.
- *
- * Prevents flickering loading indicator by only showing it after 0.6 seconds
- * and if it is shown, only hiding it after another 0.6 seconds.
- *
- * @param bool enabled
- * Whether the loading indicator should be displayed or not.
- */
- setLoadingIndicator: function(enabled) {
- var that = this;
- if (enabled) {
- this._loader = setTimeout(function() {
- that.addClass('info', 'loading');
- that._loaderVisibleStart = new Date().getTime();
- }, 600);
- }
- else {
- var currentTime = new Date().getTime();
- clearTimeout(this._loader);
- if (this._loaderVisibleStart) {
- setTimeout(function() {
- that.removeClass('info', 'loading');
- }, this._loaderVisibleStart + 600 - currentTime);
- }
- this._loader = null;
- this._loaderVisibleStart = 0;
- }
- },
-
- 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
- .addClass('edit-highlighted')
- .find('.edit-toolbar')
- // Append the "info" toolgroup into the toolbar.
- .append(Drupal.theme('editToolgroup', {
- classes: 'info edit-animate-only-background-and-padding',
- buttons: [
- { label: label, classes: 'blank-button label' }
- ]
- }));
-
- // 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'), type: 'submit', classes: 'field-save save gray-button' },
- { label: '' + Drupal.t('Close') + '', classes: 'field-close close gray-button' }
- ]
- }));
- this.show('ops');
- },
-
- /**
- * Retrieves a setting of the editor-specific Edit UI integration.
- *
- * @see Drupal.edit.util.getEditUISetting().
- */
- getEditUISetting: function(setting) {
- return Drupal.edit.util.getEditUISetting(this.editor, setting);
- },
-
- /**
- * Adjusts the toolbar to accomodate padding on the PropertyEditor widget.
- *
- * @see PropertyEditorDecorationView._pad().
- */
- _pad: function() {
- // 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' });
-
- if (this.getEditUISetting('fullWidthToolbar')) {
- $hf.css({ width: this.editor.element.width() + 10 });
- }
- },
-
- /**
- * Undoes the changes made by _pad().
- *
- * @see PropertyEditorDecorationView._unpad().
- */
- _unpad: function() {
- // Move the toolbar back to its original position.
- var $hf = this.$el.find('.edit-toolbar-heightfaker');
- $hf.css({ bottom: '1px', left: '' });
-
- if (this.getEditUISetting('fullWidthToolbar')) {
- $hf.css({ width: '' });
- }
- },
-
- insertWYSIWYGToolGroups: function() {
- this.$el
- .find('.edit-toolbar')
- .append(Drupal.theme('editToolgroup', {
- id: this.getFloatedWysiwygToolgroupId(),
- classes: 'wysiwyg-floated',
- buttons: []
- }))
- .append(Drupal.theme('editToolgroup', {
- id: this.getMainWysiwygToolgroupId(),
- classes: 'wysiwyg-main',
- buttons: []
- }));
-
- // Animate the toolgroups into visibility.
- var that = this;
- setTimeout(function () {
- that.show('wysiwyg-floated');
- that.show('wysiwyg-main');
- }, 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.editor.element.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);
- }
- },
-
- /**
- * Retrieves the ID for this toolbar's container.
- *
- * Only used to make sane hovering behavior possible.
- *
- * @return string
- * A string that can be used as the ID for this toolbar's container.
- */
- getId: function () {
- return 'edit-toolbar-for-' + this._id;
- },
-
- /**
- * Retrieves the ID for this toolbar's floating WYSIWYG toolgroup.
- *
- * Used to provide an abstraction for any WYSIWYG editor to plug in.
- *
- * @return string
- * A string that can be used as the ID.
- */
- getFloatedWysiwygToolgroupId: function () {
- return 'edit-wysiwyg-floated-toolgroup-for-' + this._id;
- },
-
- /**
- * Retrieves the ID for this toolbar's main WYSIWYG toolgroup.
- *
- * Used to provide an abstraction for any WYSIWYG editor to plug in.
- *
- * @return string
- * A string that can be used as the ID.
- */
- getMainWysiwygToolgroupId: function () {
- return 'edit-wysiwyg-main-toolgroup-for-' + 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/EditorBase.php b/core/modules/edit/lib/Drupal/edit/EditorBase.php
index 2bef9cf..39abaee 100644
--- a/core/modules/edit/lib/Drupal/edit/EditorBase.php
+++ b/core/modules/edit/lib/Drupal/edit/EditorBase.php
@@ -12,7 +12,7 @@
use Drupal\field\Plugin\Core\Entity\FieldInstance;
/**
- * Defines a base editor (Create.js PropertyEditor widget) implementation.
+ * Defines a base editor implementation.
*/
abstract class EditorBase extends PluginBase implements EditPluginInterface {
diff --git a/core/modules/edit/lib/Drupal/edit/EditorSelector.php b/core/modules/edit/lib/Drupal/edit/EditorSelector.php
index ce80def..c42cb02 100644
--- a/core/modules/edit/lib/Drupal/edit/EditorSelector.php
+++ b/core/modules/edit/lib/Drupal/edit/EditorSelector.php
@@ -17,7 +17,7 @@
class EditorSelector implements EditorSelectorInterface {
/**
- * The manager for editor (Create.js PropertyEditor widget) plugins.
+ * The manager for editor plugins.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
@@ -34,7 +34,7 @@ class EditorSelector implements EditorSelectorInterface {
* Constructs a new EditorSelector.
*
* @param \Drupal\Component\Plugin\PluginManagerInterface
- * The manager for Create.js PropertyEditor widget plugins.
+ * The manager for editor plugins.
*/
public function __construct(PluginManagerInterface $editor_manager) {
$this->editorManager = $editor_manager;
@@ -102,22 +102,6 @@ public function getEditorAttachments(array $editor_ids) {
$attachments[] = $editor->getAttachments();
}
- // JavaScript settings for Edit.
- $definitions = $this->editorManager->getDefinitions();
- foreach ($definitions as $definition) {
- $attachments[] = array(
- // This will be used in Create.js' propertyEditorWidgetsConfiguration.
- 'js' => array(
- array(
- 'type' => 'setting',
- 'data' => array('edit' => array('editors' => array(
- $definition['id'] => array('widget' => $definition['jsClassName'])
- )))
- )
- )
- );
- }
-
return NestedArray::mergeDeepArray($attachments);
}
diff --git a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
index b613442..cecc676 100644
--- a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
+++ b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
@@ -33,7 +33,7 @@ class MetadataGenerator implements MetadataGeneratorInterface {
protected $editorSelector;
/**
- * The manager for editor (Create.js PropertyEditor widget) plugins.
+ * The manager for editor plugins.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/EditorManager.php b/core/modules/edit/lib/Drupal/edit/Plugin/EditorManager.php
index 8d0032f..fbcbda7 100644
--- a/core/modules/edit/lib/Drupal/edit/Plugin/EditorManager.php
+++ b/core/modules/edit/lib/Drupal/edit/Plugin/EditorManager.php
@@ -17,7 +17,7 @@
/**
* Editor manager.
*
- * The "Form" Create.js PropertyEditor widget must always be available.
+ * The form editor must always be available.
*/
class EditorManager extends PluginManagerBase {
diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php
index c3b9b55..c598604 100644
--- a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php
+++ b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php
@@ -12,7 +12,7 @@
use Drupal\field\Plugin\Core\Entity\FieldInstance;
/**
- * Defines the "direct" Create.js PropertyEditor widget.
+ * Defines the direct editor.
*
* @Plugin(
* id = "direct",
diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php
index c8d8bc8..c5f5fe3 100644
--- a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php
+++ b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php
@@ -12,7 +12,7 @@
use Drupal\field\Plugin\Core\Entity\FieldInstance;
/**
- * Defines the "form" Create.js PropertyEditor widget.
+ * Defines the form editor.
*
* @Plugin(
* id = "form",
diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
index e1ce851..d3e5e97 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
@@ -110,7 +110,7 @@ function testUserWithPermission() {
$this->assertRaw('data-edit-id="node/1/body/und/full"');
// Retrieving the metadata should result in a 200 response, containing:
- // 1. a settings command with correct in-place editor metadata
+ // 1. a settings command with useless metadata: AjaxController is dumb
// 2. an insert command that loads the required in-place editors
// 3. a metadata command with correct per-field metadata
$response = $this->retrieveMetadata(array('node/1/body/und/full'));
@@ -120,15 +120,10 @@ function testUserWithPermission() {
// First command: settings.
$this->assertIdentical('settings', $ajax_commands[0]['command'], 'The first AJAX command is a settings command.');
- $edit_editors = array(
- 'direct' => array('widget' => 'direct'),
- 'form' => array('widget' => 'formEditEditor'),
- );
- $this->assertIdentical($edit_editors, $ajax_commands[0]['settings']['edit']['editors'], 'The settings command contains the expected settings.');
// Second command: insert libraries into DOM.
$this->assertIdentical('insert', $ajax_commands[1]['command'], 'The second AJAX command is an append command.');
- $command = new AppendCommand('body', '' . "\n");
+ $command = new AppendCommand('body', '' . "\n");
$this->assertIdentical($command->render(), $ajax_commands[1], 'The append command contains the expected data.');
// Third command: actual metadata.
diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditorSelectionTest.php b/core/modules/edit/lib/Drupal/edit/Tests/EditorSelectionTest.php
index 26ec6c0..767c570 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/EditorSelectionTest.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/EditorSelectionTest.php
@@ -16,7 +16,7 @@
class EditorSelectionTest extends EditTestBase {
/**
- * The manager for editor (Create.js PropertyEditor widget) plugins.
+ * The manager for editor plugins.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
@@ -106,8 +106,7 @@ function testText() {
* processing, but with varying text format compatibility.
*/
function testTextWysiwyg() {
- // Enable edit_test module so that the 'wysiwyg' Create.js PropertyEditor
- // widget becomes available.
+ // Enable edit_test module so that the 'wysiwyg' editor becomes available.
$this->enableModules(array('edit_test'));
$field_name = 'field_textarea';
diff --git a/core/modules/edit/lib/Drupal/edit/Tests/MetadataGeneratorTest.php b/core/modules/edit/lib/Drupal/edit/Tests/MetadataGeneratorTest.php
index 6c4569f..1394285 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/MetadataGeneratorTest.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/MetadataGeneratorTest.php
@@ -18,7 +18,7 @@
class MetadataGeneratorTest extends EditTestBase {
/**
- * The manager for editor (Create.js PropertyEditor widget) plugins.
+ * The manager for editor plugins.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
@@ -128,8 +128,7 @@ function testEditorWithCustomMetadata() {
$this->installSchema('system', 'url_alias');
$this->enableModules(array('user', 'filter'));
- // Enable edit_test module so that the WYSIWYG Create.js PropertyEditor
- // widget becomes available.
+ // Enable edit_test module so that the WYSIWYG editor becomes available.
$this->enableModules(array('edit_test'));
// Create a rich text field.
diff --git a/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php b/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php
index e40e8b4..81ff964 100644
--- a/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php
+++ b/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php
@@ -12,7 +12,7 @@
use Drupal\field\Plugin\Core\Entity\FieldInstance;
/**
- * Defines the "wysiwyg" Create.js PropertyEditor widget.
+ * Defines the wysiwyg editor.
*
* @Plugin(
* id = "wysiwyg",
diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module
index 10f938b..0f317b9 100644
--- a/core/modules/editor/editor.module
+++ b/core/modules/editor/editor.module
@@ -78,12 +78,12 @@ function editor_library_info() {
array('system', 'jquery.once'),
),
);
- // Create.js PropertyEditor widget library names begin with "edit.editor".
- $libraries['edit.editorWidget.editor'] = array(
- 'title' => '"Editor" Create.js PropertyEditor widget',
+
+ $libraries['edit.formattedTextEditor.editor'] = array(
+ 'title' => 'Formatted text editor',
'version' => VERSION,
'js' => array(
- $path . '/js/editor.createjs.js' => array(
+ $path . '/js/editor.formattedTextEditor.js' => array(
'scope' => 'footer',
'attributes' => array('defer' => TRUE),
),
diff --git a/core/modules/editor/js/editor.createjs.js b/core/modules/editor/js/editor.createjs.js
deleted file mode 100644
index 693d9d9..0000000
--- a/core/modules/editor/js/editor.createjs.js
+++ /dev/null
@@ -1,157 +0,0 @@
-/**
- * @file
- * Text editor-based Create.js widget for processed text content in Drupal.
- *
- * Depends on editor.module. Works with any (WYSIWYG) editor that implements the
- * editor.js API, including the optional attachInlineEditor() and onChange()
- * methods.
- * For example, assuming that a hypothetical editor's name was "Magical Editor"
- * and its editor.js API implementation lived at Drupal.editors.magical, this
- * JavaScript would use:
- * - Drupal.editors.magical.attachInlineEditor()
- * - Drupal.editors.magical.onChange()
- * - Drupal.editors.magical.detach()
- */
-(function (jQuery, Drupal, drupalSettings) {
-
-"use strict";
-
-// @todo D8: use jQuery UI Widget bridging.
-// @see http://drupal.org/node/1874934#comment-7124904
-jQuery.widget('Midgard.editor', jQuery.Midgard.editWidget, {
-
- textFormat: null,
- textFormatHasTransformations: null,
- textEditor: null,
-
- /**
- * Implements Create.editWidget.getEditUISettings.
- */
- getEditUISettings: function () {
- return { padding: true, unifiedToolbar: true, fullWidthToolbar: true };
- },
-
- /**
- * Implements jQuery.widget._init.
- *
- * @todo D8: Remove this.
- * @see http://drupal.org/node/1874934
- */
- _init: function () {},
-
- /**
- * Implements Create.editWidget._initialize.
- */
- _initialize: function () {
- var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property);
- var metadata = Drupal.edit.metadataCache[propertyID].custom;
-
- this.textFormat = drupalSettings.editor.formats[metadata.format];
- this.textFormatHasTransformations = metadata.formatHasTransformations;
- this.textEditor = Drupal.editors[this.textFormat.editor];
- },
-
- /**
- * Implements Create.editWidget.stateChange.
- */
- stateChange: function (from, to) {
- var that = this;
- switch (to) {
- case 'inactive':
- break;
-
- case 'candidate':
- // Detach the text editor when entering the 'candidate' state from one
- // of the states where it could have been attached.
- if (from !== 'inactive' && from !== 'highlighted') {
- this.textEditor.detach(this.element.get(0), this.textFormat);
- }
- break;
-
- case 'highlighted':
- break;
-
- case 'activating':
- // When transformation filters have been been applied to the processed
- // text of this field, then we'll need to load a re-processed version of
- // it without the transformation filters.
- if (this.textFormatHasTransformations) {
- var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property);
- this._getUntransformedText(propertyID, this.element, function (untransformedText) {
- that.element.html(untransformedText);
- that.options.activated();
- });
- }
- // When no transformation filters have been applied: start WYSIWYG
- // editing immediately!
- else {
- this.options.activated();
- }
- break;
-
- case 'active':
- this.textEditor.attachInlineEditor(
- this.element.get(0),
- this.textFormat,
- this.toolbarView.getMainWysiwygToolgroupId(),
- this.toolbarView.getFloatedWysiwygToolgroupId()
- );
- // Set the state to 'changed' whenever the content has changed.
- this.textEditor.onChange(this.element.get(0), function (html) {
- that.options.changed(html);
- });
- break;
-
- case 'changed':
- break;
-
- case 'saving':
- break;
-
- case 'saved':
- break;
-
- case 'invalid':
- break;
- }
- },
-
- /**
- * Loads untransformed text for a given property.
- *
- * More accurately: it re-processes processed text to exclude transformation
- * filters used by the text format.
- *
- * @param String propertyID
- * A property ID that uniquely identifies the given property.
- * @param jQuery $editorElement
- * The property's PropertyEditor DOM element.
- * @param Function callback
- * A callback function that will receive the untransformed text.
- *
- * @see \Drupal\editor\Ajax\GetUntransformedTextCommand
- */
- _getUntransformedText: function (propertyID, $editorElement, callback) {
- // Create a Drupal.ajax instance to load the form.
- Drupal.ajax[propertyID] = new Drupal.ajax(propertyID, $editorElement, {
- url: Drupal.edit.util.buildUrl(propertyID, drupalSettings.editor.getUntransformedTextURL),
- event: 'editor-internal.editor',
- submit: { nocssjs : true },
- progress: { type : null } // No progress indicator.
- });
- // Implement a scoped editorGetUntransformedText AJAX command: calls the
- // callback.
- Drupal.ajax[propertyID].commands.editorGetUntransformedText = function(ajax, response, status) {
- callback(response.data);
- // Delete the Drupal.ajax instance that called this very function.
- delete Drupal.ajax[propertyID];
- $editorElement.off('editor-internal.editor');
- };
- // This will ensure our scoped editorGetUntransformedText AJAX command
- // gets called.
- $editorElement.trigger('editor-internal.editor');
- }
-
-});
-
-})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/editor/js/editor.formattedTextEditor.js b/core/modules/editor/js/editor.formattedTextEditor.js
new file mode 100644
index 0000000..8d1ace7
--- /dev/null
+++ b/core/modules/editor/js/editor.formattedTextEditor.js
@@ -0,0 +1,181 @@
+/**
+ * @file
+ * Text editor-based in-place editor for processed text content in Drupal.
+ *
+ * Depends on editor.module. Works with any (WYSIWYG) editor that implements the
+ * editor.js API, including the optional attachInlineEditor() and onChange()
+ * methods.
+ * For example, assuming that a hypothetical editor's name was "Magical Editor"
+ * and its editor.js API implementation lived at Drupal.editors.magical, this
+ * JavaScript would use:
+ * - Drupal.editors.magical.attachInlineEditor()
+ */
+(function ($, Drupal, drupalSettings) {
+
+"use strict";
+
+Drupal.edit.editors.editor = Drupal.edit.EditorView.extend({
+
+ textFormat: null,
+ textFormatHasTransformations: null,
+ textEditor: null,
+ $textElement: null,
+
+ /**
+ * {@inheritdoc}
+ */
+ initialize: function (options) {
+ Drupal.edit.EditorView.prototype.initialize.call(this, options);
+
+ var editID = this.model.get('editID');
+ var metadata = Drupal.edit.Metadata.get(editID, 'custom');
+ this.textFormat = drupalSettings.editor.formats[metadata.format];
+ this.textFormatHasTransformations = metadata.formatHasTransformations;
+ this.textEditor = Drupal.editors[this.textFormat.editor];
+
+ // Store the actual value of this field. We'll need this to restore the
+ // original value when the user discards his modifications.
+ this.$textElement = this.$el.find('.field-item:first');
+ this.model.set('originalValue', this.$textElement.html());
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ getEditedElement: function () {
+ return this.$textElement;
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ stateChange: function (model, state) {
+ var that = this;
+ var from = model.previous('state');
+ var to = state;
+ switch (to) {
+ case 'inactive':
+ break;
+
+ case 'candidate':
+ // Detach the text editor when entering the 'candidate' state from one
+ // of the states where it could have been attached.
+ if (from !== 'inactive' && from !== 'highlighted') {
+ this.textEditor.detach(this.$textElement.get(0), this.textFormat);
+ }
+ if (from === 'invalid') {
+ this.removeValidationErrors();
+ }
+ break;
+
+ case 'highlighted':
+ break;
+
+ case 'activating':
+ // When transformation filters have been been applied to the processed
+ // text of this field, then we'll need to load a re-processed version of
+ // it without the transformation filters.
+ if (this.textFormatHasTransformations) {
+ var editID = this.model.get('editID');
+ this._getUntransformedText(editID, this.$el, function (untransformedText) {
+ that.$textElement.html(untransformedText);
+ that.model.set('state', 'active');
+ });
+ }
+ // When no transformation filters have been applied: start WYSIWYG
+ // editing immediately!
+ else {
+ // As soon as the current state change has propagated, apply this one.
+ // @see http://jsfiddle.net/5MVzp/2/ vs. http://jsfiddle.net/5MVzp/3/
+ _.defer(function() {
+ that.model.set('state', 'active');
+ });
+ }
+ break;
+
+ case 'active':
+ this.textEditor.attachInlineEditor(
+ this.$textElement.get(0),
+ this.textFormat,
+ this.model.get('toolbarView').getMainWysiwygToolgroupId(),
+ this.model.get('toolbarView').getFloatedWysiwygToolgroupId()
+ );
+ // Set the state to 'changed' whenever the content has changed.
+ this.textEditor.onChange(this.$textElement.get(0), function (htmlText) {
+ that.model.set('currentValue', htmlText);
+ that.model.set('state', 'changed');
+ });
+ break;
+
+ case 'changed':
+ break;
+
+ case 'saving':
+ if (from === 'invalid') {
+ this.removeValidationErrors();
+ }
+ this.save();
+ break;
+
+ case 'saved':
+ break;
+
+ case 'invalid':
+ this.showValidationErrors();
+ break;
+ }
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ getEditUISettings: function () {
+ return { padding: true, unifiedToolbar: true, fullWidthToolbar: true };
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ revert: function () {
+ this.$textElement.html(this.model.get('originalValue'));
+ },
+
+ /**
+ * Loads untransformed text for a given field.
+ *
+ * More accurately: it re-processes processed text to exclude transformation
+ * filters used by the text format.
+ *
+ * @param String editID
+ * A edit ID that uniquely identifies the given field.
+ * @param jQuery $editorElement
+ * The property's PropertyEditor DOM element.
+ * @param Function callback
+ * A callback function that will receive the untransformed text.
+ *
+ * @see \Drupal\editor\Ajax\GetUntransformedTextCommand
+ */
+ _getUntransformedText: function (editID, $editorElement, callback) {
+ // Create a Drupal.ajax instance to load the form.
+ Drupal.ajax[editID] = new Drupal.ajax(editID, $editorElement, {
+ url: Drupal.edit.util.buildUrl(editID, drupalSettings.editor.getUntransformedTextURL),
+ event: 'editor-internal.editor',
+ submit: { nocssjs : true },
+ progress: { type : null } // No progress indicator.
+ });
+ // Implement a scoped editorGetUntransformedText AJAX command: calls the
+ // callback.
+ Drupal.ajax[editID].commands.editorGetUntransformedText = function(ajax, response, status) {
+ callback(response.data);
+ // Delete the Drupal.ajax instance that called this very function.
+ delete Drupal.ajax[editID];
+ $editorElement.off('editor-internal.editor');
+ };
+ // This will ensure our scoped editorGetUntransformedText AJAX command
+ // gets called.
+ $editorElement.trigger('editor-internal.editor');
+ }
+
+});
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/editor/js/editor.js b/core/modules/editor/js/editor.js
index 6483170..f07aa73 100644
--- a/core/modules/editor/js/editor.js
+++ b/core/modules/editor/js/editor.js
@@ -83,8 +83,12 @@ Drupal.behaviors.editor = {
var $this = $(this);
var activeFormatID = $this.val();
var field = behavior.findFieldForFormatSelector($this);
-
- Drupal.editorDetach(field, settings.editor.formats[activeFormatID], trigger);
+ if ('activeFormatID' in settings.editor.formats) {
+ Drupal.editorDetach(field, settings.editor.formats[activeFormatID], trigger);
+ }
+ else {
+ console.log('%c editor.js: The format ' + activeFormatID + ' does not have an editor.', 'background-color: red; color: white;');
+ }
});
},
diff --git a/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php b/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php
index 81c23d7..08cd210 100644
--- a/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php
+++ b/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php
@@ -14,7 +14,7 @@
use Drupal\field\Plugin\Core\Entity\FieldInstance;
/**
- * Defines the "editor" Create.js PropertyEditor widget.
+ * Defines the formatted text editor.
*
* @Plugin(
* id = "editor",
@@ -90,8 +90,8 @@ public function getAttachments() {
// Get the attachments for all text editors that the user might use.
$attachments = $manager->getAttachments($formats);
- // Also include editor.module's Create.js PropertyEditor widget.
- $attachments['library'][] = array('editor', 'edit.editorWidget.editor');
+ // Also include editor.module's formatted text editor.
+ $attachments['library'][] = array('editor', 'edit.formattedTextEditor.editor');
return $attachments;
}
diff --git a/core/modules/editor/lib/Drupal/editor/Tests/EditIntegrationTest.php b/core/modules/editor/lib/Drupal/editor/Tests/EditIntegrationTest.php
index 1dcadc8..6e8e36f 100644
--- a/core/modules/editor/lib/Drupal/editor/Tests/EditIntegrationTest.php
+++ b/core/modules/editor/lib/Drupal/editor/Tests/EditIntegrationTest.php
@@ -21,7 +21,7 @@
class EditIntegrationTest extends EditTestBase {
/**
- * The manager for editor (Create.js PropertyEditor widget) plug-ins.
+ * The manager for editor plug-ins.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
@@ -143,7 +143,7 @@ function testEditorSelection() {
}
/**
- * Tests (custom) metadata when the "Editor" Create.js editor is used.
+ * Tests (custom) metadata when the formatted text editor is used.
*/
function testMetadata() {
$this->editorManager = new EditorManager($this->container->get('container.namespaces'));
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 8b8b4d0..615e5dc 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2109,20 +2109,6 @@ function system_library_info() {
),
);
- // 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',