From 4d25155b5c832f6b73053f4f520a67dabed1bc64 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= <splendidnoise@gmail.com>
Date: Fri, 12 Oct 2012 15:29:45 -0400
Subject: [PATCH] Issue #1808076 by frega, jessebeach: Merged in frega fork of
 the wimleers fork of the edit module that incorporates
 Create.js, VIE, Backbone and Underscore.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

commit 8880684b71f6e2a3788912e7048b56839eb15a94
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Fri Oct 12 15:27:15 2012 -0400

    Issue #1808076 by jessebeach: Moved VIE, Backbone and Underscore to their own libraries.

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit 75c6f5a763bc6c47ab74ed6da8191cd4c2de4afd
Merge: 60a0d7e c668f4a
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Fri Oct 12 15:21:03 2012 -0400

    Issue #1808076 by frega: Merge branch 'refs/heads/frega/7.x-1.x' into node/1808076-createjs

    Conflicts:
    	edit.info
    	edit.module
    	js/ajax.js
    	js/edit.js
    	js/theme.js
    	js/ui-editables.js

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit c668f4a97feacadbf34c9e846ab6bad1427f5c44
Merge: 5bc0ef3 7fdfe85
Author: frega <fl@flink-solutions.de>
Date:   Thu Oct 11 20:35:10 2012 +0200

    Merge branch '7.x-1.x' of ssh://github.com/frega/edit-createjs into toolbar-views-refactor

commit 5bc0ef3bca9248e4fd7cb6fefbadcc421ec83d95
Author: frega <fl@flink-solutions.de>
Date:   Thu Oct 11 20:33:13 2012 +0200

    =Initial stab at refactoring Drupal.edit.toolbar into Drupal.edit.ToolbarView instances. It does work, but not completed and requires a lot more refactoring/de-coupling

commit 7fdfe852c06c282a5dafe21c324af0047476f0b5
Author: frega <fl@flink-solutions.de>
Date:   Thu Oct 11 13:32:07 2012 +0200

    =Fix a typo in README and fixed a lingering reference to  in the EditableFieldView

commit c0eb56c84d951994ec7e3b64be352f93b2626232
Author: frega <fl@flink-solutions.de>
Date:   Thu Oct 11 13:24:17 2012 +0200

    =Start refactoring views.js

commit 2867a21081687f847aaeb78c46771b1f4a46c4c5
Author: frega <fl@flink-solutions.de>
Date:   Wed Oct 10 15:33:38 2012 +0200

    =Improve README.md by including a 'How to install'-section, fix order of steps.

commit 67cb49b419745dcdfc508b66fc446eb6dca31b78
Merge: 6157b06 2d2c01d
Author: frega <fl@flink-solutions.de>
Date:   Wed Oct 10 15:32:25 2012 +0200

    Merge branch '7.x-1.x' of ssh://github.com/wimleers/edit-createjs into 7.x-1.x

commit 6157b06a7cbad1ca480c3737c4ad2bd84108c314
Author: frega <fl@flink-solutions.de>
Date:   Wed Oct 10 15:32:05 2012 +0200

    =Improve README.md by including a 'How to install'-section

commit 2d2c01d2fa4a025884e372255c1f57f5b97afa1e
Merge: cc913db 2bc56c7
Author: frega <fl@flink-solutions.de>
Date:   Wed Oct 10 06:14:39 2012 -0700

    Merge pull request #9 from frega/7.x-1.x

    7.x 1.x - Fixes #8

commit 2bc56c75be31803883ae414e6c9427f6f7309bd0
Author: frega <fl@flink-solutions.de>
Date:   Wed Oct 10 13:18:18 2012 +0200

    =Fixes #8, rebases on latest version of spark (alpha6), complete cleanup, add semicolons.

commit 25597453f7bd70483ad70744d731ec93c69cf28e
Author: frega <fl@flink-solutions.de>
Date:   Wed Oct 10 13:17:09 2012 +0200

    =Fixes #8, rebases on latest version of spark (alpha6), complete cleanup

commit 1d7a8a02d147914b31b205dc3d2ae08a937dfb76
Author: frega <fl@flink-solutions.de>
Date:   Wed Oct 10 13:11:22 2012 +0200

    =This rehashes the README.md, so removing this

commit 2b7da27a26e60f919009d32926090c9181a6b2a1
Author: frega <fl@flink-solutions.de>
Date:   Wed Oct 10 13:07:10 2012 +0200

    =Fixes #8, rebases on latest version of spark (alpha6)

commit 8ded6e6e645c2ab7909d35e498378ea94dd3a608
Merge: cc913db 0a9ca67
Author: frega <fl@flink-solutions.de>
Date:   Wed Oct 10 10:28:54 2012 +0200

    Merge branch '7.x-1.x' of ssh://github.com/frega/edit-createjs into 7.x-1.x

commit 0a9ca676e2b0c2c07336b604c03a4935484f696b
Author: frega <fl@flink-solutions.de>
Date:   Sun Oct 7 22:17:56 2012 +0200

    =Fixes #4; #edit_backstage (holding the <form> for direct editables) should not be removed on disabling the overlay as it gets added on page load not on overlay enabling.

commit 265257a09b85e817a33b0882fa7cb738f1f16a67
Author: frega <fl@flink-solutions.de>
Date:   Sun Oct 7 12:17:03 2012 +0200

    =Initial stab at creating a document, covering the current code, execution flow

commit cc913db4b61d9c1bf7f22e709a9a86b485d3d60d
Merge: 3af8c94 a4a53dd
Author: Wim Leers <work@wimleers.com>
Date:   Sat Oct 6 15:39:11 2012 -0700

    Merge pull request #6 from frega/7.x-1.x

    =Rough initial work on addressing issue #5 - Abstract form AJAX handling

commit a4a53dd90e7b88852bddf13f22fc7f44c99b4220
Author: frega <fl@flink-solutions.de>
Date:   Sat Oct 6 19:20:57 2012 +0200

    =Fixes #1 - Clicking cancel or clicking on other editables leaves (last active) editable in a state where they can't be re-enabled

commit b08e146929d484785d68f25f4d4a1d6e466dea90
Author: frega <fl@flink-solutions.de>
Date:   Sat Oct 6 17:58:30 2012 +0200

    =Fixes #7 - Editables get repadded when switching view and edit state.

commit 3ce3f8a610a8159b12eca4e521b545c765c952bd
Author: frega <fl@flink-solutions.de>
Date:   Mon Oct 1 13:28:15 2012 +0200

    =Rough initial work on addressing issue #5 - Abstract form AJAX handling

commit 3af8c94062d0d358745e32cf8064ae0782fad281
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Tue Sep 25 23:03:30 2012 +0200

    Simpler click handling

commit 270d96a5a6f957c1899d87f37543cffbbc2107fe
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Tue Sep 25 22:50:04 2012 +0200

    Read element labels from VIE's type system

commit 43083e824ae4779049eb02146a8ec1f8fd9ee478
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Tue Sep 25 22:41:55 2012 +0200

    Register types read from DOM to VIE's type system

commit e3e727a0a0ccd7b0676697c14f793a0a56f06fc6
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Tue Sep 25 22:08:39 2012 +0200

    Switch over to the VIE SparkEditService in most places

commit 369912f1d26fb92a5ba065e3b1ababa1ef63deb5
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Tue Sep 25 19:33:54 2012 +0200

    Stub for Spark Edit -specific VIE service

commit e403031b4a18356c18ca148392b3c2431ba38705
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Tue Sep 25 19:21:38 2012 +0200

    Move VIE and Create.js to a subdirectory as they're reusable libraries

commit 88e5ba850f3a454799219af021ef0514e99f856e
Author: scor <scorlosquet@gmail.com>
Date:   Mon Aug 27 13:10:00 2012 -0400

    add RDFa markup to the title of the node

commit dffa1cd051276dc85d9e3c6538c0cafd5b0c2257
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Sat Aug 25 17:02:20 2012 +0200

    More fixes for saving

commit 5cc83fe3dd35c6fcd29e593c0a293028c4adf761
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Sat Aug 25 16:42:44 2012 +0200

    Correct values for editedEditable

commit 058e2f3ca7c5afb9d8fe05264293335a3b911c57
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Sat Aug 25 15:45:20 2012 +0200

    Some documentation

commit b2ab04bbccebdbb204d90b36453190155fe2cb3b
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Sat Aug 25 15:18:19 2012 +0200

    Switch dependencies to minified versions. We can use custom builds with unnecessary parts removed later

commit 720e0af72a42469484a7e71f8764567c1a8960da
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Sat Aug 25 13:52:22 2012 +0200

    Handle clicks to overlay

commit dd1c1fb6f77cfb2da32585a82a4b02af0dcd71dd
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Fri Aug 24 18:28:20 2012 +0200

    Migrate pad and unpad to views

commit 88065ad0b046723692bf58c531581ad7fe722c65
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Fri Aug 24 17:34:32 2012 +0200

    Simplify init

commit daa30e957846cf6b844684f5b919edcee3f273a3
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Fri Aug 24 15:48:16 2012 +0200

    Move editing UI to views

commit 7d0b2fb475c999af3531a40133540539ab984915
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Fri Aug 24 13:33:06 2012 +0200

    Start implementing the FieldView

commit 8900fce7e7ded4074ddbd7675e8579424cae6014
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Fri Aug 24 13:32:50 2012 +0200

    Move label reading to util

commit 6361ae45d2f3e5c47df68febbf1a39358ce6316e
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Fri Aug 24 11:52:19 2012 +0200

    Document EditableView

commit c5622bdebfe2683d47f29cae80466cebb23ac6a8
Merge: 184187f f77beea
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Fri Aug 24 11:41:00 2012 +0200

    Merge branch '7.x-1.x' of github.com:wimleers/edit-createjs into 7.x-1.x

commit 184187f8f8fc4b05cd4af8796a72de1e38559742
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Fri Aug 24 11:40:47 2012 +0200

    Switch state to a Backbone model

commit f77beea5000ee82a761b6d5201d0e218d4223192
Author: Wim Leers <wim.leers@acquia.com>
Date:   Fri Aug 24 10:09:11 2012 +0200

    Fix unpadding of editables, and actually fix the restoring of direct editables.

commit 071d8428f529fce635054b8cc206616341afafae
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 23:56:36 2012 +0200

    Workaround for reading also the entity title correctly

commit 868b41d19d37bf759e2d75df81cb79a23e3b9c61
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 23:49:02 2012 +0200

    Use a view so that Create.js Storage can handle restoring etc.

commit a475f9c9d1a56eb7cc6ef6fef3c221c806a5f0ac
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 23:48:21 2012 +0200

    Ugly hack to get jQuery UI widget factory in

commit d2565d93610e2bebe0d027b46f1febc3a64b1b6c
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 22:32:08 2012 +0200

    Call 'stopEdit' when returning to View state

commit 43c43ac9fa62cf6a879240e4ed6b984e0263f346
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 22:11:16 2012 +0200

    Initialize Create.js storage widget to eventually get localStorage restore support

commit 87b5c47d5f0bc7a2ad883adc5a41028cf0fc5595
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 21:56:40 2012 +0200

    More refactoring for Create.js compatibility

commit e76fed073cdb4b5d9332dc9cc534b7ebccfed4f0
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 21:56:15 2012 +0200

    Add Create.js

commit 9e2cdb38a97fdf071468d9f38899571314fd6c8d
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 18:40:44 2012 +0200

    Namespace change

commit a57c79c4a6ca0c69e4535b8bcc0657b677b3c4bd
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 17:28:22 2012 +0200

    Move rest of DOM-parsing to util

commit 170677d7c454c24e28244c021c1e1b103c47b744
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 17:13:01 2012 +0200

    Initial Create.js editor widget for Drupal forms

commit 3bfc9cbf77689f3cf8b0c1ec02c98af75ffa2cb7
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 17:12:18 2012 +0200

    Make getID safer

commit 425dcf7b89f333fc8cff5330a92fdc5ca65482dd
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 16:13:30 2012 +0200

    Move finder methods to util

commit 9d62f2abf597428ebdb5042d5a7065a084525426
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 16:10:22 2012 +0200

    Move getID to util

commit 5eb8e7e59cd00185831db3166e7695ba19eb8e82
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 16:04:05 2012 +0200

    Initial subclassing of Create.js editable widget for Spark

commit 46d4cc3de327b30ceb7fdfb0ab8d4a16374efc24
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 16:01:34 2012 +0200

    Start moving DOM parsing to util

commit 7e0c807663939063ffca1621d668052611315168
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 14:44:37 2012 +0200

    Spark still works, but now we have VIE entities that get updated as well

commit 844697662246c7dd965fe9d89c183aba53c76f52
Merge: a8b2411 19e4ed7
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 14:40:23 2012 +0200

    Merge branch '7.x-1.x' of github.com:wimleers/edit-createjs into 7.x-1.x

commit a8b2411e971a50b948fd9058aceb53942e61cfeb
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 14:40:10 2012 +0200

    Refactoring starts

commit 19e4ed78c03e8da3e2fc8a33d9ab1f29ef2edbd7
Merge: ecc0ab2 4dd96ad
Author: Wim Leers <wim.leers@acquia.com>
Date:   Thu Aug 23 13:18:33 2012 +0200

    Merge branch '7.x-1.x' of github.com:wimleers/edit-createjs into 7.x-1.x

    * '7.x-1.x' of github.com:wimleers/edit-createjs:
      Use data attribs instead of unreliable RDFa for now

commit ecc0ab29d38655bdf8449379df7929938649f330
Author: Wim Leers <wim.leers@acquia.com>
Date:   Thu Aug 23 13:17:27 2012 +0200

    Create.js deps.

commit 4dd96ad9f7c77c28994277d522484b19e6442fec
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 13:16:24 2012 +0200

    Use data attribs instead of unreliable RDFa for now

commit 748eb75bd8cbb5147f13387ef34947de079fff3f
Author: Henri Bergius <henri.bergius@iki.fi>
Date:   Thu Aug 23 12:27:43 2012 +0200

    Start using VIE for entity field finding

Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>
---
 README.md              |   52 +
 edit.module            |   59 +-
 js/SparkEditService.js |  199 ++
 js/ajax.js             |  103 +-
 js/edit.js             |  711 +----
 js/editable.js         |   56 +
 js/formwidget.js       |   56 +
 js/lib/backbone.js     | 1431 ++++++++++
 js/lib/create.js       |    7 +
 js/lib/underscore.js   |    5 +
 js/lib/vie.js          | 6943 ++++++++++++++++++++++++++++++++++++++++++++++++
 js/theme.js            |   16 +-
 js/ui-editables.js     |  245 +-
 js/util.js             |  106 +
 js/views.js            |  755 ++++++
 15 files changed, 9987 insertions(+), 757 deletions(-)
 create mode 100644 README.md
 create mode 100644 js/SparkEditService.js
 create mode 100644 js/editable.js
 create mode 100644 js/formwidget.js
 create mode 100644 js/lib/backbone.js
 create mode 100644 js/lib/create.js
 create mode 100644 js/lib/underscore.js
 create mode 100644 js/lib/vie.js
 create mode 100644 js/views.js

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e1335a2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+Spark Edit module
+=================
+
+This repository contains the version of [Spark's](http://drupal.org/project/spark) inline editing module refactored to run on the [Create.js](http://createjs.org/) stack.
+
+Using the shared Create.js & [VIE](http://viejs.org/) codebase makes it possible to share code between CMS projects, essentially making Spark development easier and the code more maintainable. These shared JavaScript libraries are used in various projects including TYPO3, Midgard, and Symfony CMF.
+
+For some background on this approach, refer to [Decoupling Content Management](http://decoupledcms.org/).
+
+The work of porting existing Spark edit code to Create.js was initially undertaken by [Wim Leers](http://wimleers.com/) and [Henri Bergius](http://bergie.iki.fi/) during [DrupalCon Munich 2012](http://munich2012.drupal.org/).
+
+## How to install.
+
+* Download the spark distribution (currently works with 7.x-1.0-alpha6) from the [project page](http://drupal.org/project/spark).
+* Replace edit.module with edit-createjs.module (cd profiles/spark/modules/contrib ; rm -rf edit/ ; git clone https://github.com/wimleers/edit-createjs.git).
+* Download [Backbone](http://backbonejs.org/backbone-min.js) and [Underscore](http://documentcloud.github.com/underscore/underscore-min.js).
+* Create directories sites/all/libraries/backbone/ and sites/all/libraries/underscore/. Move the minified files to the respective directories and make sure sites/all/libraries/backbone/backbone-min.js and sites/all/libraries/underscore/underscore-min.js subsequently exist.
+* Install Spark (e.g. if you have drush installed $ cd {SPARK-DIRECTORY} ; drush si spark --db-url="mysql://{USER}:{PWD}@localhost/{DBNAME}").
+
+## How does this work?
+
+Bootstrapping:
+
+* The entry point to the system is the `Drupal.edit.init` call
+* Initialization instantiates VIE, and prepares a Backbone model to keep Spark edit state
+* Initialization also loads the Create.js [storage widget](http://createjs.org/guide/#storage) to handle localStorage, restoring unsaved content etc.
+* Once the dependencies have been prepared, all editable fields will be retrieved from DOM, and a VIE entity will be instantiated for each
+* The DOM element and the VIE entity are given to a Backbone `EditableFieldView` instance
+* The `EditableFieldView`s instantiate a Create.js [editable widget](http://createjs.org/guide/#editable) for their editable contents
+
+Switching to edit mode:
+
+* The Backbone `EditableFieldView`s listen to Spark edit state changes
+* When switching to editing stage, they decorate the editables with borders
+* The views also subscribe to mouse movements and clicks
+* When mouse is over a view, that view will display its label
+* When a view is clicked, the editable widget is enabled, and it will load the appropriate editor (WYSIWYG, simple contentEditable, form, etc)
+
+Switching to view mode:
+
+* The `EditableFieldViews` again receive the state change from Spark edit
+* The views disable their UI elements, returning the DOM to the undecorated and "passive" state
+
+## Status
+
+Most of the code has now been ported to use VIE entities for the editable contents, Create.js editor widgets for the actual content editing, and Backbone views for rendering the UI.
+
+There are still some lingering bugs from this port that will be resolved soon.
+
+After that the main remaining bigger task is to move saving from direct form submissions in the views to a custom `Backbone.sync` implementation. This will make it easier to support both the forms-based Drupal 7 saving process, and the RESTful Drupal 8 API, as we can just swap the `sync` methods used.
+
+Currently the DOM parsing depends on Spark's custom HTML5 data attributes. VIE and Create.js would support RDFa out-of-the-box, and so this would be the preferred annotation in the long run.
diff --git a/edit.module b/edit.module
index c6f0704..aaf1466 100644
--- a/edit.module
+++ b/edit.module
@@ -120,6 +120,7 @@ function edit_library() {
   $wysiwyg_module = variable_get(EDIT_WYSIWYG_VARIABLE, EDIT_WYSIWYG_DEFAULT);
 
   $path = drupal_get_path('module', 'edit');
+
   $libraries['edit'] = array(
     'title' => 'Edit: in-place editing',
     'website' => 'http://drupal.org/project/edit',
@@ -162,6 +163,59 @@ function edit_library() {
       array('system', 'jquery.form'),
       array('system', 'drupal.form'),
       array('system', 'drupal.ajax'),
+      array('edit', 'edit.createjs'),
+    ),
+  );
+
+  $libraries['edit.createjs'] = array(
+    'title' => 'CreateJS and deps',
+    'website' => 'http://createjs.org',
+    'version' => NULL,
+    'js' => array(
+      drupal_get_path('module', 'jquery_update') . '/replace/ui/ui/jquery.ui.widget.js' => array(),
+      $path . '/js/lib/create.js' => array('defer' => TRUE),
+      $path . '/js/SparkEditService.js' => array('defer' => TRUE),
+      $path . '/js/editable.js' => array('defer' => TRUE),
+      $path . '/js/formwidget.js' => array('defer' => TRUE),
+      $path . '/js/views.js' => array('defer' => TRUE),
+    ),
+    'dependencies' => array(
+      array('edit', 'edit.vie'),
+    ),
+  );
+
+  $libraries['edit.vie'] = array(
+    'title' => 'Vienna IKS Editables',
+    'website' => 'http://wiki.iks-project.eu/index.php/VIE',
+    'version' => '2.0',
+    'js' => array(
+      $path . '/js/lib/vie.js' => array('defer' => TRUE),
+    ),
+    'dependencies' => array(
+      array('edit', 'edit.backbone'),
+    ),
+  );
+
+  // Register underscore as a library.
+  $libraries['edit.underscore'] = array(
+    'title' => 'Underscore.js',
+    'website' => 'http://underscorejs.org',
+    'version' => '1.4.2',
+    'js' => array(
+      $path . '/js/lib/underscore.js' => array(),
+    ),
+  );
+
+  // Register backbone as a library.
+  $libraries['edit.backbone'] = array(
+    'title' => 'Backbone.js',
+    'website' => 'http://backbonejs.org',
+    'version' => '0.9.2',
+    'js' => array(
+      $path . '/js/lib/backbone.js' => array(),
+    ),
+    'dependencies' => array(
+      array('edit', 'edit.underscore'),
     ),
   );
 
@@ -560,8 +614,9 @@ function edit_preprocess_field(&$variables) {
       $language  = $variables['element']['#language'];
       $format_id = $field[$language][0]['format'];
 
-      // Let the WYSIWYG editor know the text format.
-      $variables['attributes_array']['data-edit-text-format'] = $format_id;
+      // Let the WYSIWYG editor know the allowed tags.
+      $allowed_tags = filter_get_allowed_tags_by_format($format_id);
+      $variables['attributes_array']['data-allowed-tags'] = ($allowed_tags === TRUE) ? '' : implode(',', $allowed_tags);
 
       // Ensure the WYSIWYG editor has the necessary text format related
       // metadata.
diff --git a/js/SparkEditService.js b/js/SparkEditService.js
new file mode 100644
index 0000000..eda9029
--- /dev/null
+++ b/js/SparkEditService.js
@@ -0,0 +1,199 @@
+//    VIE DOM parsing service for Spark Edit
+(function () {
+
+  VIE.prototype.SparkEditService = 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.SparkEditService.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 = Drupal.settings.edit.context;
+        }
+      } else {
+        element = loadable.options.element;
+      }
+
+      var entities = this.readEntities(element);
+      loadable.resolve(entities);
+    },
+
+    // The edit-id data attribute contains the full identifier of
+    // each entity element in format `<nodetype>:<id>:<fieldname>`.
+    _getID: function (element) {
+      var id = jQuery(element).data('edit-id');
+      if (!id) {
+        id = jQuery(element).closest('[data-edit-id]').data('edit-id');
+      }
+      return id;
+    },
+
+    // Returns the "URI" of an entity of an element in format 
+    // `<NodeType>:<id>`.
+    getElementSubject: function (element) {
+      return this._getID(element).split(':').slice(0, 2).join(':');
+    },
+
+    // Returns the field name for an element.
+    getElementPredicate: function (element) {
+      if (!this._getID(element)) {
+        debugger;
+      }
+      return this._getID(element).split(':').pop();
+    },
+
+    getElementType: function (element) {
+      return this._getID(element).split(':').slice(0, 1);
+    },
+
+    // Reads all editable entities (_Fields_ in Spark Edit lingo) from DOM
+    // and returns the VIE enties it found.
+    readEntities: function (element) {
+      var service = this;
+      var entities = [];
+      var entityElements = jQuery(this.options.subjectSelector, element);
+      entityElements = entityElements.add(jQuery(element).filter(this.options.subjectSelector));
+      entityElements.each(function () {
+        var entity = service._readEntity(jQuery(this));
+        if (entity) {
+          entities.push(entity);
+        }
+      });
+      return entities;
+    },
+
+    // Returns a filled VIE Entity instance for a DOM element. The Entity
+    // is also registered in the VIE entities collection.
+    _readEntity: function (element) {
+      var subject = this.getElementSubject(element);
+      var type = this.getElementType(element);
+      var entity = this._readEntityPredicates(subject, element, false);
+      if (jQuery.isEmptyObject(entity)) {
+        return null;
+      }
+      entity['@subject'] = subject;
+      if (type) {
+        entity['@type'] = this._registerType(type, element);
+      }
+
+      // Register with VIE
+      return this._registerEntity(entity);
+    },
+
+    _registerEntity: function (entityData) {
+      var entityInstance = new this.vie.Entity(entityData);
+      return this.vie.entities.addOrUpdate(entityInstance, {
+        updateOptions: {
+          silent: true
+        }
+      });
+    },
+
+    _registerType: function (typeId, element) {
+      typeId = '<http://viejs.org/ns/' + typeId + '>';
+      var type = this.vie.types.get(typeId);
+      if (!type) {
+        this.vie.types.add(typeId, []);
+        type = this.vie.types.get(typeId);
+      }
+
+      var predicate = this.getElementPredicate(element);
+      if (type.attributes.get(predicate)) {
+        return type;
+      }
+
+      var label = element.data('edit-field-label');
+      var range = 'Form';
+      if (element.hasClass('edit-type-direct')) {
+        range = 'Direct';
+      }
+      if (element.hasClass('edit-type-direct-with-wysiwyg')) {
+        range = 'Wysiwyg';
+      }
+      type.attributes.add(predicate, [range], 0, 1, {
+        label: element.data('edit-field-label')
+      });
+
+      return type;
+    },
+
+    _readEntityPredicates: function (subject, element, emptyValues) {
+      var entityPredicates = {};
+      var service = this;
+      this.findPredicateElements(subject, element, true).each(function () {
+        var predicateElement = jQuery(this);
+        var predicate = service.getElementPredicate(predicateElement);
+        if (!predicate) {
+          return;
+        }
+        var value = service._readElementValue(predicateElement);
+        if (value === null && !emptyValues) {
+          return;
+        }
+
+        entityPredicates[predicate] = value;
+      });
+      return entityPredicates;
+    },
+
+    _readElementValue: function (element) {
+      return jQuery.trim(element.html());
+    },
+
+    // Subject elements are the DOM elements containing a single or multiple
+    // editable fields. In Spark Edit these elements are called _Fields_, 
+    // and the actual DOM elements which are edited are called _Editables_.
+    findSubjectElements: function (element) {
+      if (!element) {
+        element = Drupal.settings.edit.context;
+      }
+      return jQuery(this.options.subjectSelector, element);
+    },
+
+    // Predicate Elements are the actual DOM elements that users will be able
+    // to edit. In regular Spark Edit they are called _Editables_.
+    //
+    // They are contained within Entity elements, which in Spark Edit are called
+    // _Fields_.
+    findPredicateElements: function (subject, element, allowNestedPredicates, stop) {
+      var predicates = jQuery();
+
+      // Form-type predicates
+      predicates = predicates.add(element.filter('.edit-type-form'));
+
+      // Direct-type predicates
+      var direct = element.filter('.edit-type-direct');
+      predicates = predicates.add(direct.find('.field-item'));
+      // Edge case: "title" pseudofield on pages with lists of nodes.
+      predicates = predicates.add(direct.filter('h2').find('a'));
+      // Edge case: "title" pseudofield on node pages.
+      predicates = predicates.add(direct.find('h1'));
+
+      if (!predicates.length && !stop) {
+        var parentElement = element.parent(this.options.subjectSelector);
+        if (parentElement.length) {
+          return this.findPredicateElements(subject, parentElement, allowNestedPredicates, true);
+        }
+      }
+
+      return predicates;
+    }
+  };
+})();
diff --git a/js/ajax.js b/js/ajax.js
index f2bdd3b..611cf81 100644
--- a/js/ajax.js
+++ b/js/ajax.js
@@ -8,80 +8,16 @@
 
 // Hide these in a ready to ensure that Drupal.ajax is set up first.
 $(function() {
-  Drupal.ajax.prototype.commands.edit_field_form = function(ajax, response, status) {
-    console.log('edit_field_form', ajax, response, status);
-
-    // Only apply the form immediately if this form is currently being edited.
-    if (Drupal.edit.state.editedEditable == response.id && ajax.$field.hasClass('edit-type-form')) {
-      Drupal.ajax.prototype.commands.insert(ajax, {
-        data: response.data,
-        selector: '.edit-form-container .placeholder'
-      });
-
-      // Indicate in the 'info' toolgroup that the form has loaded, but only do
-      // it after half a second to prevent it from flashing, which is bad UX.
-      setTimeout(function() {
-        Drupal.edit.toolbar.removeClass(ajax.$editable, 'info', 'loading');
-      }, 500);
-
-      // Detect changes in this form.
-      Drupal.edit.form.get(ajax.$editable)
-      .delegate(':input', 'formUpdated.edit', function() {
-        ajax.$editable
-        .data('edit-content-changed', true)
-        .trigger('edit-content-changed.edit');
-      })
-      .delegate('input', 'keypress.edit', function(event) {
-        if (event.keyCode == 13) {
-          return false;
-        }
-      });
-
-      var $submit = Drupal.edit.form.get(ajax.$editable).find('.edit-form-submit');
-      var element_settings = {
-        url : $submit.closest('form').attr('action'),
-        setClick : true,
-        event : 'click.edit',
-        progress : { type : 'throbber' },
-        // IPE-specific settings.
-        $editable : ajax.$editable,
-        $field : ajax.$field
-      };
-      var base = $submit.attr('id');
-      Drupal.ajax[base] = new Drupal.ajax(base, $submit[0], element_settings);
-
-      // Give focus to the first input in the form.
-      //$('.edit-form').find('form :input:visible:enabled:first').focus()
-    }
-    else if (Drupal.edit.state.editedEditable == response.id && ajax.$field.hasClass('edit-type-direct')) {
-      Drupal.edit.state.directEditableFormResponse = response;
-      $('#edit_backstage').append(response.data);
-
-      var $submit = $('#edit_backstage form .edit-form-submit');
-      var element_settings = {
-        url : $submit.closest('form').attr('action'),
-        setClick : true,
-        event : 'click.edit',
-        progress : { type : 'throbber' },
-        // IPE-specific settings.
-        $editable : ajax.$editable,
-        $field : ajax.$field
-      };
-      var base = $submit.attr('id');
-      Drupal.ajax[base] = new Drupal.ajax(base, $submit[0], element_settings);
-    }
-    else {
-      console.log('queueing', response);
-    }
-
-    // Animations.
-    Drupal.edit.toolbar.show(ajax.$editable, 'ops');
-    ajax.$editable.trigger('edit-form-loaded.edit');
-  };
+  // these function should never be called as they are overridden by setting the
+  // respective Drupal.ajax[{base}].commands.edit_field_form|_saved methods in
+  // create/loadForm/saveForm  in ui-editables.
+  Drupal.ajax.prototype.commands.edit_field_form = function(ajax, response, status) {};
+  Drupal.ajax.prototype.commands.edit_field_form_saved = function(ajax, response, status) {};
+  // @todo: refactor this in a similar fashion & figure out where this is
+  // needed - probably direct editables.
   Drupal.ajax.prototype.commands.edit_field_rendered_without_transformation_filters = function(ajax, response, status) {
     console.log('edit_field_rendered_without_transformation_filters', ajax, response, status);
-
-    if (Drupal.edit.state.editedEditable == response.id
+    if (Drupal.edit.state.get('editedEditable') == response.id
         && ajax.$field.hasClass('edit-type-direct')
         && ajax.$field.hasClass('edit-text-with-transformation-filters')
         )
@@ -94,31 +30,10 @@ $(function() {
 
       // Update the HTML of the editable and enable WYSIWYG editing on it.
       ajax.$editable.html(response.data);
+      // @todo: this object doesn't exist anymore.
       Drupal.edit.editables._wysiwygify(ajax.$editable);
     }
   };
-  Drupal.ajax.prototype.commands.edit_field_form_saved = function(ajax, response, status) {
-    console.log('edit_field_form_saved', ajax, response, status);
-
-    // Stop the editing.
-    Drupal.edit.editables.stopEdit(ajax.$editable);
-
-    // Response.data contains the updated rendering of the field, if any.
-    if (response.data) {
-      // Replace the old content with the new content.
-      var $field = $('.edit-field[data-edit-id="' + response.id  + '"]');
-      var $parent = $field.parent();
-      if ($field.css('display') == 'inline') {
-        $parent.html(response.data);
-      }
-      else {
-        $field.replaceWith(response.data);
-      }
-
-      // Make the freshly rendered field(s) in-place-editable again.
-      Drupal.edit.startEditableFields(Drupal.edit.findEditableFields($parent));
-    }
-  };
 });
 
 })(jQuery);
diff --git a/js/edit.js b/js/edit.js
index 30c3f75..6af96de 100644
--- a/js/edit.js
+++ b/js/edit.js
@@ -10,11 +10,6 @@ Drupal.behaviors.edit = {
   attach: function(context) {
     $('#edit_view-edit-toggles').once('edit-init', Drupal.edit.init);
     $('#edit_view-edit-toggles').once('edit-toggle', Drupal.edit.toggle.render);
-
-    // TODO: remove this; this is to make the current prototype somewhat usable.
-    $('a.edit_view-edit-toggle').click(function() {
-      $(this).trigger('click.edit');
-    });
   }
 };
 
@@ -22,29 +17,27 @@ Drupal.edit.const = {};
 Drupal.edit.const.transitionEnd = "transitionEnd.edit webkitTransitionEnd.edit transitionend.edit msTransitionEnd.edit oTransitionEnd.edit";
 
 Drupal.edit.init = function() {
-  Drupal.edit.state = {};
-  // We always begin in view mode.
-  Drupal.edit.state.isViewing = true;
-  Drupal.edit.state.fieldBeingHighlighted = [];
-  Drupal.edit.state.fieldBeingEdited = [];
-  Drupal.edit.state.higlightedEditable = null;
-  Drupal.edit.state.editedEditable = null;
-  Drupal.edit.state.queues = {};
-  Drupal.edit.state.wysiwygReady = false;
-
-  // Build inventory.
-  var IDMapper = function() { return Drupal.edit.getID($(this)); };
-  Drupal.edit.state.fields = Drupal.edit.findEditableFields().map(IDMapper);
-  console.log('Fields:', Drupal.edit.state.fields.length, ';', Drupal.edit.state.fields);
-
-  // Form preloader.
-  Drupal.edit.state.queues.preload = Drupal.edit.findEditableFields().filter('.edit-type-form').map(IDMapper);
-  console.log('Fields with (server-generated) forms:', Drupal.edit.state.queues.preload);
+  // VIE instance for Editing
+  Drupal.edit.vie = new VIE();
+  // Use our custom DOM parsing service until RDFa is available
+  Drupal.edit.vie.use(new Drupal.edit.vie.SparkEditService());
+  Drupal.edit.domService = Drupal.edit.vie.service('edit');
+
+  Drupal.edit.state = Drupal.edit.prepareStateModel();
+  Drupal.edit.state.set('queues', Drupal.edit.prepareQueues());
+
+  // Load the storage widget to get localStorage support
+  $('body').midgardStorage({
+    vie: Drupal.edit.vie,
+    editableNs: 'createeditable'
+  });
+  // TODO: Check localStorage for unsaved changes
+  // $('body').midgardStorage('checkRestore');
 
   // Initialize WYSIWYG, if any.
   if (Drupal.settings.edit.wysiwyg) {
     $(document).bind('edit-wysiwyg-ready.edit', function() {
-      Drupal.edit.state.wysiwygReady = true;
+      Drupal.edit.state.set('wysiwygReady', true);
       console.log('edit: WYSIWYG ready');
     });
     Drupal.edit.wysiwyg[Drupal.settings.edit.wysiwyg].init();
@@ -53,634 +46,88 @@ Drupal.edit.init = function() {
   // Create a backstage area.
   $(Drupal.theme('editBackstage', {})).appendTo('body');
 
+  // Instantiate FieldViews
+  Drupal.edit.domService.findSubjectElements().each(Drupal.edit.prepareFieldView);
+
+  // Instantiate overlayview
+  var overlayView = new Drupal.edit.views.OverlayView({
+    state: Drupal.edit.state
+  });
+
   // Transition between view/edit states.
-  $("a.edit_view-edit-toggle").bind('click.edit', function() {
-    var wasViewing = Drupal.edit.state.isViewing;
-    var isViewing  = Drupal.edit.state.isViewing = $(this).hasClass('edit-view');
-    // Swap active class among the two links.
+  $("a.edit_view-edit-toggle").bind('click.edit', function(event) {
+    event.preventDefault();
+
+    var isViewing = $(this).hasClass('edit-view');
+    Drupal.edit.state.set('isViewing', isViewing);
+
+    // swap active class among the two links.
     $('a.edit_view-edit-toggle').removeClass('active');
     $('a.edit_view-edit-toggle').parent().removeClass('active');
     $('a.edit_view-edit-toggle.edit-' + (isViewing ? 'view' : 'edit')).addClass('active');
     $('a.edit_view-edit-toggle.edit-' + (isViewing ? 'view' : 'edit')).parent().addClass('active');
-
-    if (wasViewing && !isViewing) {
-      $(Drupal.theme('editOverlay', {}))
-      .appendTo('body')
-      .addClass('edit-animate-slow edit-animate-invisible')
-      .bind('click.edit', Drupal.edit.clickOverlay);;
-
-      var $f = Drupal.edit.findEditableFields();
-      Drupal.edit.startEditableFields($f);
-
-      // TODO: preload forms. We could do one request per form, but that's more
-      // RTTs than needed. Instead, the server should support batch requests.
-      console.log('Preloading forms that we might need!', Drupal.edit.state.queues.preload);
-
-      // Animations. Integrate with both navbar and toolbar.
-      $('#edit_overlay').css('top', $('#navbar, #toolbar').outerHeight());
-      $('#edit_overlay').removeClass('edit-animate-invisible');
-
-      // Disable contextual links in edit mode.
-      $('.contextual-links-region')
-      .addClass('edit-contextual-links-region')
-      .removeClass('contextual-links-region');
-    }
-    else if (!wasViewing && isViewing) {
-      // Animations.
-      $('#edit_overlay')
-      .addClass('edit-animate-invisible')
-      .bind(Drupal.edit.const.transitionEnd, function(e) {
-        $('#edit_overlay, .edit-form-container, .edit-toolbar-container, #edit_modal, #edit_backstage').remove();
-      });
-
-      var $f = Drupal.edit.findEditableFields();
-      Drupal.edit.stopEditableFields($f);
-
-      // Re-enable contextual links in view mode.
-      $('.edit-contextual-links-region')
-      .addClass('contextual-links-region')
-      .removeClass('edit-contextual-links-region');
-    }
-    else {
-      // No state change.
-    }
-    return false;
   });
 };
 
-Drupal.edit.findEditableFields = function(context) {
-  return $('.edit-field.edit-allowed', context || Drupal.settings.edit.context);
-};
-
-/*
- * findEditableFields() just looks for fields that are editable, i.e. for the
- * field *wrappers*. Depending on the field, however, either the whole field wrapper
- * will be marked as editable (in this case, an inline form will be used for editing),
- * *or* a specific (field-specific even!) DOM element within that field wrapper will be
- * marked as editable.
- * This function is for finding the *editables* themselves, given the *editable fields*.
- */
-Drupal.edit.findEditablesForFields = function($fields) {
-  var $editables = $();
-
-  // type = form
-  $editables = $editables.add($fields.filter('.edit-type-form'));
-
-  // type = direct
-  var $direct = $fields.filter('.edit-type-direct');
-  $editables = $editables.add($direct.find('.field-item'));
-  // Edge case: "title" pseudofield on pages with lists of nodes.
-  $editables = $editables.add($direct.filter('h2').find('a'));
-  // Edge case: "title" pseudofield on node pages.
-  $editables = $editables.add($direct.find('h1'));
-
-  return $editables;
-};
-
-Drupal.edit.getID = function($field) {
-  return $field.data('edit-id');
-};
-
-Drupal.edit.findFieldForID = function(id, context) {
-  return $('[data-edit-id="' + id + '"]', context || $('#content'));
-};
-
-Drupal.edit.findFieldForEditable = function($editable) {
-  return $editable.filter('.edit-type-form').length ? $editable : $editable.closest('.edit-type-direct');
-};
-
-Drupal.edit.startEditableFields = function($fields) {
-  var $fields = $fields.once('edit');
-  // Ignore fields that need a WYSIWYG editor if no WYSIWYG editor is present
-  if (!Drupal.settings.edit.wysiwyg) {
-    $fields = $fields.filter(':not(.edit-type-direct-with-wysiwyg)');
-  }
-  var $editables = Drupal.edit.findEditablesForFields($fields);
-
-  $editables
-  .addClass('edit-animate-fast')
-  .addClass('edit-candidate edit-editable')
-  .bind('mouseenter.edit', function(e) {
-    var $editable = $(this);
-    Drupal.edit.util.ignoreHoveringVia(e, '.edit-toolbar-container', function() {
-      console.log('field:mouseenter');
-      if (!$editable.hasClass('edit-editing')) {
-        Drupal.edit.editables.startHighlight($editable);
-      }
-    });
-  })
-  .bind('mouseleave.edit', function(e) {
-    var $editable = $(this);
-    Drupal.edit.util.ignoreHoveringVia(e, '.edit-toolbar-container', function() {
-      console.log('field:mouseleave');
-      if (!$editable.hasClass('edit-editing')) {
-        Drupal.edit.editables.stopHighlight($editable);
-      }
-    });
-  })
-  .bind('click.edit', function() {
-    Drupal.edit.editables.startEdit($(this)); return false;
-  })
-  // Some transformations are editable-specific.
-  .map(function() {
-    $(this).data('edit-background-color', Drupal.edit.util.getBgColor($(this)));
+Drupal.edit.prepareStateModel = function () {
+  // The state of Spark Edit is handled in a Backbone model
+  Drupal.edit.StateModel = Backbone.Model.extend({
+    defaults: {
+      isViewing: true,
+      entityBeingHighlighted: [],
+      fieldBeingHighlighted: [],
+      fieldBeingEdited: [],
+      highlightedEditable: null,
+      editedEditable: null,
+      editedFieldView: null,
+      queues: {},
+      wysiwygReady: false
+    }
   });
-};
-
-Drupal.edit.stopEditableFields = function($fields) {
-  var $editables = Drupal.edit.findEditablesForFields($fields);
-
-  $fields
-  .removeClass('edit-processed');
-
-  $editables
-  .removeClass('edit-candidate edit-editable edit-highlighted edit-editing edit-belowoverlay')
-  .unbind('mouseenter.edit mouseleave.edit click.edit edit-content-changed.edit')
-  .removeAttr('contenteditable')
-  .removeData(['edit-content-original', 'edit-content-changed']);
-};
-
-Drupal.edit.clickOverlay = function(e) {
-  console.log('clicked overlay');
 
-  if (Drupal.edit.modal.get().length == 0) {
-    Drupal.edit.toolbar.get(Drupal.edit.state.fieldBeingEdited)
-    .find('a.close').trigger('click.edit');
-  }
+  // We always begin in view mode.
+  return new Drupal.edit.StateModel();
 };
 
-/*
-2. Each field MAY (if it is editable by the user) contain exactly one Editable,
-   in which the editing itself occurs, this can be either:
-     a. type=direct, here some child element of the Field element is marked as editable
-     b. type=form, here the field itself is marked as editable, upon edit, a form is used
- */
-
-// Field editables.
-Drupal.edit.editables = {
-  startHighlight: function($editable) {
-    console.log('editables.startHighlight');
-    if (Drupal.edit.toolbar.create($editable)) {
-      var label = $editable.filter('.edit-type-form').data('edit-field-label')
-        || $editable.closest('.edit-type-direct').data('edit-field-label');
-
-      Drupal.edit.toolbar.get($editable)
-      .find('.edit-toolbar:not(:has(.edit-toolgroup.info))')
-      .append(Drupal.theme('editToolgroup', {
-        classes: 'info',
-        buttons: [
-          { url: '#', label: label, classes: 'blank-button label', hasButtonRole: false },
-        ]
-      }))
-      .delegate('a.label', 'click.edit', function(e) {
-        // Clicking the label equals clicking the editable itself.
-        $editable.trigger('click.edit');
-        return false;
-      });
-    }
-
-    // Animations.
-    setTimeout(function() {
-      $editable.addClass('edit-highlighted');
-      Drupal.edit.toolbar.show($editable, 'info');
-    }, 0);
-
-    Drupal.edit.state.fieldBeingHighlighted = $editable;
-    Drupal.edit.state.higlightedEditable = Drupal.edit.getID(Drupal.edit.findFieldForEditable($editable));
-  },
-
-  stopHighlight: function($editable) {
-    console.log('editables.stopHighlight');
-    if ($editable.length == 0) {
-      return;
-    }
-
-    // Animations.
-    Drupal.edit.toolbar.remove($editable);
-    $editable.removeClass('edit-highlighted');
-
-    Drupal.edit.state.fieldBeingHighlighted = [];
-    Drupal.edit.state.highlightedEditable = null;
-  },
-
-  startEdit: function($editable) {
-    if ($editable.hasClass('edit-editing')) {
-      return;
-    }
-
-    console.log('editables.startEdit: ', $editable);
-    var self = this;
-    var $field = Drupal.edit.findFieldForEditable($editable);
-
-    // Highlight if not already highlighted.
-    if (Drupal.edit.state.fieldBeingHighlighted[0] != $editable[0]) {
-      Drupal.edit.editables.startHighlight($editable);
-    }
-
-    $editable
-    .addClass('edit-editing')
-    .bind('edit-content-changed.edit', function(e) {
-      self._buttonFieldSaveToBlue(e, $editable, $field);
-    })
-    // Some transformations are editable-specific.
-    .map(function() {
-      $(this).css('background-color', $(this).data('edit-background-color'));
-    });
-
-    // While editing, don't show *any* other field as editable.
-    $('.edit-candidate').not('.edit-editing').removeClass('edit-editable');
+Drupal.edit.prepareQueues = function () {
+  // Form preloader.
 
-    // Toolbar (already created in the highlight).
-    Drupal.edit.toolbar.get($editable)
-    .addClass('edit-editing')
-    .find('.edit-toolbar:not(:has(.edit-toolgroup.ops))')
-    .append(Drupal.theme('editToolgroup', {
-      classes: 'ops',
-      buttons: [
-        { url: '#', label: Drupal.t('Save'), classes: 'field-save save gray-button' },
-        { url: '#', label: '<span class="close"></span>', classes: 'field-close close gray-button' }
-      ]
-    }))
-    .delegate('a.field-save', 'click.edit', function(e) {
-      return self._buttonFieldSaveClicked(e, $editable, $field);
+  var queues = {
+    preload: Drupal.edit.domService.findSubjectElements().filter('.edit-type-form').map(function () {
+      return Drupal.edit.util.getID($(this));
     })
-    .delegate('a.field-close', 'click.edit', function(e) {
-      return self._buttonFieldCloseClicked(e, $editable, $field);
-    });
-
-    // Changes to $editable based on the type.
-    var callback = ($field.hasClass('edit-type-direct'))
-      ? self._updateDirectEditable
-      : self._updateFormEditable;
-    callback($editable);
-
-    // Regardless of the type, load the form for this field. We always use forms
-    // to submit the changes.
-    self._loadForm($editable, $field);
+  };
+  console.log('Fields with (server-generated) forms:', queues.preload);
+  return queues;
+};
 
-    Drupal.edit.state.fieldBeingEdited = $editable;
-    Drupal.edit.state.editedEditable = Drupal.edit.getID($field);
-  },
+Drupal.edit.prepareFieldView = function () {
+  var element = jQuery(this);
+  var fieldViewType = Drupal.edit.views.EditableFieldView;
+  if (!element.hasClass('edit-type-direct')) {
+    fieldViewType = Drupal.edit.views.FormEditableFieldView;
+  }
 
-  stopEdit: function($editable) {
-    console.log('editables.stopEdit: ', $editable);
-    var self = this;
-    var $field = Drupal.edit.findFieldForEditable($editable);
-    if ($editable.length == 0) {
+  Drupal.edit.vie.load({
+    element: element
+  }).using('edit').execute().done(function (entities) {
+    var subject = Drupal.edit.domService.getElementSubject(element);
+    var predicate = Drupal.edit.domService.getElementPredicate(element);
+    var entity = entities[0];
+    if (!entity) {
       return;
     }
 
-    $editable
-    .removeClass('edit-highlighted edit-editing edit-belowoverlay')
-    // Some transformations are editable-specific.
-    .map(function() {
-      $(this).css('background-color', '');
+    var fieldView = new fieldViewType({
+      state: Drupal.edit.state,
+      el: element,
+      model: entity,
+      predicate: predicate,
+      vie: Drupal.edit.vie
     });
-
-    // Make the other fields and entities editable again.
-    $('.edit-candidate').addClass('edit-editable');
-
-    // Changes to $editable based on the type.
-    var callback = ($field.hasClass('edit-type-direct'))
-      ? self._restoreDirectEditable
-      : self._restoreFormEditable;
-    callback($editable);
-
-    Drupal.edit.toolbar.remove($editable);
-    Drupal.edit.form.remove($editable);
-
-    Drupal.edit.state.fieldBeingEdited = [];
-    Drupal.edit.state.editedEditable = null;
-  },
-
-  _loadRerenderedProcessedText: function($editable, $field) {
-    // Indicate in the 'info' toolgroup that the form is loading.
-    Drupal.edit.toolbar.addClass($editable, 'info', 'loading');
-
-    var edit_id = Drupal.edit.getID($field);
-    var element_settings = {
-      url      : Drupal.edit.util.calcRerenderProcessedTextURL(edit_id),
-      event    : 'edit-internal-load-rerender.edit',
-      $field   : $field,
-      $editable: $editable,
-      submit   : { nocssjs : true },
-      progress : { type : null }, // No progress indicator.
-    };
-    if (Drupal.ajax.hasOwnProperty(edit_id)) {
-      delete Drupal.ajax[edit_id];
-      $editable.unbind('edit-internal-load-rerender.edit');
-    }
-    Drupal.ajax[edit_id] = new Drupal.ajax(edit_id, $editable, element_settings);
-    $editable.trigger('edit-internal-load-rerender.edit');
-  },
-
-  // Attach, activate and show the WYSIWYG editor.
-  _wysiwygify: function($editable) {
-    var $field = Drupal.edit.findFieldForEditable($editable);
-    $editable.addClass('edit-wysiwyg-attached');
-    var formatID = $field.attr('data-edit-text-format');
-    var format = Drupal.settings.aloha.formats[formatID];
-    Drupal.edit.wysiwyg[Drupal.settings.edit.wysiwyg].attach($editable, format);
-    Drupal.edit.wysiwyg[Drupal.settings.edit.wysiwyg].activate($editable);
-    Drupal.edit.toolbar.show($editable, 'wysiwyg-tabs');
-    Drupal.edit.toolbar.show($editable, 'wysiwyg');
-  },
-
-  _updateDirectEditable: function($editable) {
-    var $field = Drupal.edit.findFieldForEditable($editable);
-
-    Drupal.edit.editables._padEditable($editable, $field);
-
-    if ($field.hasClass('edit-type-direct-with-wysiwyg')) {
-      Drupal.edit.toolbar.get($editable)
-      .find('.edit-toolbar:not(:has(.edit-toolgroup.wysiwyg-tabs))')
-      .append(Drupal.theme('editToolgroup', {
-        classes: 'wysiwyg-tabs',
-        buttons: []
-      }))
-      .end()
-      .find('.edit-toolbar:not(:has(.edit-toolgroup.wysiwyg))')
-      .append(Drupal.theme('editToolgroup', {
-        classes: 'wysiwyg',
-        buttons: []
-      }));
-
-      // When transformation filters have been been applied to the processed
-      // text of this field, then we'll need to load a re-rendered version of
-      // it without the transformation filters.
-      if ($field.hasClass('edit-text-with-transformation-filters')) {
-        Drupal.edit.editables._loadRerenderedProcessedText($editable, $field);
-        // Also store the "real" original content, i.e. the transformed one.
-        $editable.data('edit-content-original-transformed', $editable.html())
-      }
-      // When no transformation filters have been applied: start WYSIWYG editing
-      // immediately!
-      else {
-        setTimeout(function() {
-          Drupal.edit.editables._wysiwygify($editable);
-        }, 0);
-      }
-    }
-    else {
-      $editable.attr('contenteditable', true);
-    }
-
-    $editable
-    .data('edit-content-original', $editable.html())
-    .data('edit-content-changed', false);
-
-    // Detect content changes ourselves only when not using a WYSIWYG editor.
-    var markContentChanged = function() {
-      $editable.data('edit-content-changed', true);
-      $editable.trigger('edit-content-changed.edit');
-    };
-    if (!$field.hasClass('edit-type-direct-with-wysiwyg')) {
-      // We cannot use Drupal.behaviors.formUpdated here because we're not dealing
-      // with a form!
-      $editable
-      .bind('blur.edit keyup.edit paste.edit', function() {
-        if ($editable.html() != $editable.data('edit-content-original')) {
-          markContentChanged();
-        }
-      })
-      // Disallow return/enter key when editing titles.
-      .bind('keypress.edit', function(event) {
-        if (event.keyCode == 13) {
-          return false;
-        }
-      });
-    }
-    else {
-      $editable.bind('edit-wysiwyg-content-changed.edit', function() {
-        markContentChanged();
-      });
-    }
-  },
-
-  _restoreDirectEditable: function($editable) {
-    var $field = Drupal.edit.findFieldForEditable($editable);
-
-    Drupal.edit.editables._padEditable($editable, $field);
-
-    if ($field.hasClass('edit-type-direct-with-wysiwyg') && $editable.hasClass('edit-wysiwyg-attached')) {
-      $editable.removeClass('edit-wysiwyg-attached');
-      Drupal.edit.wysiwyg[Drupal.settings.edit.wysiwyg].detach($editable);
-
-      // Work-around for major AE bug. See:
-      //  - http://drupal.org/node/1725032
-      //  - https://github.com/alohaeditor/Aloha-Editor/issues/693.
-      // Also unbind to make sure this doesn't break anything when using
-      // this version of edit.js with a fixed version of Aloha Editor.
-      $editable
-      .unbind('click.edit')
-      .bind('click.edit', function() {
-        Drupal.edit.editables.startEdit($(this)); return false;
-      });
-    }
-    else {
-      $editable
-      .removeAttr('contenteditable')
-      .unbind('keypress.edit');
-    }
-
-    Drupal.edit.editables._unpadEditable($editable, $field);
-
-    $editable
-    .removeData(['edit-content-original', 'edit-content-changed', 'edit-content-original-transformed'])
-    .unbind('blur.edit keyup.edit paste.edit edit-content-changed.edit');
-
-    // Not only clean up the changes to $editable, but also clean up the
-    // backstage area, where we hid the form that we used to send the changes.
-    $('#edit_backstage form').remove();
-  },
-
-  _padEditable: function($editable, $field) {
-    // 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 ($editable[0].style.width === "") {
-      $editable
-      .data('edit-width-empty', true)
-      .addClass('edit-animate-disable-width')
-      .css('width', $editable.width());
-    }
-    // 2) Add padding; use animations.
-    var posProp = Drupal.edit.util.getPositionProperties($editable);
-    var $toolbar = Drupal.edit.toolbar.get($editable);
-    setTimeout(function() {
-      // Re-enable width animations (padding changes affect width too!).
-      $editable.removeClass('edit-animate-disable-width');
-
-      // The toolbar must move to the top and the left.
-      var $hf = $toolbar.find('.edit-toolbar-heightfaker');
-      $hf.css({ bottom: '6px', left: '-5px' });
-      // When using a WYSIWYG editor, the width of the toolbar must match the
-      // width of the editable.
-      if ($field.hasClass('edit-type-direct-with-wysiwyg')) {
-        $hf.css({ width: $editable.width() + 10 });
-      }
-
-      // Pad the editable.
-      $editable
-      .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);
-  },
-
-  _unpadEditable: function($editable, $field) {
-    // 1) Set the empty width again.
-    if ($editable.data('edit-width-empty') === true) {
-      console.log('restoring width');
-      $editable
-      .addClass('edit-animate-disable-width')
-      .css('width', '');
-    }
-    // 2) Remove padding; use animations (these will run simultaneously with)
-    // the fading out of the toolbar as its gets removed).
-    var posProp = Drupal.edit.util.getPositionProperties($editable);
-    var $toolbar = Drupal.edit.toolbar.get($editable);
-    setTimeout(function() {
-      // Re-enable width animations (padding changes affect width too!).
-      $editable.removeClass('edit-animate-disable-width');
-
-      // Move the toolbar back to its original position.
-      var $hf = $toolbar.find('.edit-toolbar-heightfaker');
-      $hf.css({ bottom: '1px', left: '' });
-      // When using a WYSIWYG editor, restore the width of the toolbar.
-      if ($field.hasClass('edit-type-direct-with-wysiwyg')) {
-        $hf.css({ width: '' });
-      }
-
-      // Unpad the editable.
-      $editable
-      .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);
-  },
-
-  // Creates a form container; when the $editable is inline, it will inherit CSS
-  // properties from the toolbar container, so the toolbar must already exist.
-  _updateFormEditable: function($editable) {
-    if (Drupal.edit.form.create($editable)) {
-      $editable
-      .addClass('edit-belowoverlay')
-      .removeClass('edit-highlighted edit-editable');
-
-      Drupal.edit.form.get($editable)
-      .find('.edit-form')
-      .addClass('edit-editable edit-highlighted edit-editing')
-      .css('background-color', $editable.data('edit-background-color'));
-    }
-  },
-
-  _restoreFormEditable: function($editable) {
-    // No need to do anything here; all of the field HTML will be overwritten
-    // with the freshly rendered version from the server anyway!
-  },
-
-  _loadForm: function($editable, $field) {
-    var edit_id = Drupal.edit.getID($field);
-    var element_settings = {
-      url      : Drupal.edit.util.calcFormURLForField(edit_id),
-      event    : 'edit-internal.edit',
-      $field   : $field,
-      $editable: $editable,
-      submit   : { nocssjs : ($field.hasClass('edit-type-direct')) },
-      progress : { type : null }, // No progress indicator.
-    };
-    if (Drupal.ajax.hasOwnProperty(edit_id)) {
-      delete Drupal.ajax[edit_id];
-      $editable.unbind('edit-internal.edit');
-    }
-    Drupal.ajax[edit_id] = new Drupal.ajax(edit_id, $editable, element_settings);
-    $editable.trigger('edit-internal.edit');
-  },
-
-  _buttonFieldSaveToBlue: function(e, $editable, $field) {
-    Drupal.edit.toolbar.get($editable)
-    .find('a.save').addClass('blue-button').removeClass('gray-button');
-  },
-
-  _buttonFieldSaveClicked: function(e, $editable, $field) {
-    // type = form
-    if ($field.hasClass('edit-type-form')) {
-      Drupal.edit.form.get($field).find('form')
-      .find('.edit-form-submit').trigger('click.edit').end();
-    }
-    // type = direct
-    else if ($field.hasClass('edit-type-direct')) {
-      $editable.blur();
-
-      var wysiwyg = $field.hasClass('edit-type-direct-with-wysiwyg')
-          && $editable.hasClass('edit-wysiwyg-attached');
-
-      // When using WYSIWYG editing, first detach the WYSIWYG editor to ensure
-      // the content has been cleaned up before saving it. (Otherwise,
-      // annotations and infrastructure created by the WYSIWYG editor could also
-      // get saved).
-      if (wysiwyg) {
-        $editable.removeClass('edit-wysiwyg-attached');
-        Drupal.edit.wysiwyg[Drupal.settings.edit.wysiwyg].detach($editable);
-      }
-
-      // We trim the title because otherwise whitespace in the raw HTML ends
-      // up in the title as well.
-      // TRICKY: Drupal core does not trim the title, so in theory this is
-      // out of line with Drupal core's behavior.
-      var value = (wysiwyg)
-        ? $.trim($editable.html())
-        : $.trim($editable.text());
-      $('#edit_backstage form')
-      .find(':input[type!="hidden"][type!="submit"]').val(value).end()
-      .find('.edit-form-submit').trigger('click.edit');
-    }
-    return false;
-  },
-
-  _buttonFieldCloseClicked: function(e, $editable, $field) {
-    // Content not changed: stop editing field.
-    if (!$editable.data('edit-content-changed')) {
-      // Restore to original content. When dealing with processed text, it's
-      // possible that one or more transformation filters are used. Then, the
-      // "real" original content (i.e. the transformed one) is stored separately
-      // from the "original content" that we use to detect changes.
-      if (typeof $editable.data('edit-content-original-transformed') !== 'undefined') {
-        $editable.html($editable.data('edit-content-original-transformed'));
-      }
-
-      Drupal.edit.editables.stopEdit($editable);
-    }
-    // Content changed: show modal.
-    else {
-      Drupal.edit.modal.create(
-        Drupal.t('You have unsaved changes'),
-        Drupal.theme('editButtons', { 'buttons' : [
-          { url: '#', classes: 'gray-button discard', label: Drupal.t('Discard changes') },
-          { url: '#', classes: 'blue-button save', label: Drupal.t('Save') }
-        ]}),
-        $editable
-      );
-      setTimeout(Drupal.edit.modal.show, 0);
-    };
-    return false;
-  }
+  });
 };
 
 })(jQuery);
+
+$ = jQuery;
diff --git a/js/editable.js b/js/editable.js
new file mode 100644
index 0000000..daa4198
--- /dev/null
+++ b/js/editable.js
@@ -0,0 +1,56 @@
+(function (jQuery, undefined) {
+  // # Create.js editing widget for Spark
+  //
+  // This widget inherits from the Create.js editable widget to accommodate
+  // for the fact that Spark is using custom data attributes and not RDFa
+  // to communicate editable fields.
+  jQuery.widget('Drupal.createEditable', jQuery.Midgard.midgardEditable, {
+    _create: function () {
+      this.vie = this.options.vie;
+
+      this.options.domService = 'edit';
+      this.options.predicateSelector = '*'; //'.edit-field.edit-allowed';
+
+      this.options.editors.direct = {
+        widget: 'editWidget',
+        options: {}
+      };
+      this.options.editors.directWysiwyg = {
+        widget: 'alohaWidget',
+        options: {}
+      };
+      this.options.editors.form = {
+        widget: 'drupalFormWidget',
+        options: {}
+      };
+
+      jQuery.Midgard.midgardEditable.prototype._create.call(this);
+    },
+
+      /*
+    findEditableElements: function (callback) {
+      jQuery.Midgard.midgardEditable.prototype.findEditableElements.call(this, function (elements) {
+        console.log(elements);
+        callback(elements);
+      });
+      var model = this.options.model;
+      var fields = Drupal.edit.util.findEditableFields(this.element).andSelf().filter(function () {
+        return Drupal.edit.util.getElementSubject(jQuery(this)) == model.getSubjectUri();
+      });
+      Drupal.edit.util.findEditablesForFields(fields).each(callback);
+    },
+    getElementPredicate: function (element) {
+       return Drupal.edit.util.getElementPredicate(jQuery(element));
+    },*/
+
+    _editorName: function (data) {
+      if (Drupal.settings.edit.wysiwyg && jQuery(this.element).hasClass('edit-type-direct')) {
+        if (jQuery(this.element).hasClass('edit-type-direct-with-wysiwyg')) {
+          return 'directWysiwyg';
+        }
+        return 'direct';
+      }
+      return 'form';
+    }
+  });
+})(jQuery);
diff --git a/js/formwidget.js b/js/formwidget.js
new file mode 100644
index 0000000..b1fbbe8
--- /dev/null
+++ b/js/formwidget.js
@@ -0,0 +1,56 @@
+(function ($, undefined) {
+  // # Drupal form-based editing widget for Create.js
+  $.widget('Drupal.drupalFormWidget', $.Create.editWidget, {
+    options: {
+      editorOptions: {},
+      disabled: true
+    },
+    // @todo: add a callback to do this in an proper async fashion.
+    enable: function () {
+      console.log('Drupal.drupalFormWidget.enable');
+      this.options.disabled = false;
+      this.loadForm();
+    },
+
+    loadForm: function () {
+      // Create the form asynchronously.
+      // @todo: use a different "factory" depending on editable type.
+      Drupal.edit.form.create(this.element, function($editable, $field) {
+        $editable
+          .addClass('edit-belowoverlay')
+          .removeClass('edit-highlighted edit-editable');
+
+        Drupal.edit.form.get($editable)
+        .find('.edit-form')
+        .addClass('edit-editable edit-highlighted edit-editing')
+        .css('background-color', $editable.data('edit-background-color'));
+      });
+    },
+
+    disable: function () {
+      console.log('Drupal.drupalFormWidget.disable');
+      this.options.disabled = true;
+      // @todo: handle this better on the basis of the editable type.
+      // Currently we stuff forms into two places ...
+      Drupal.edit.form.get(this.element).remove();
+      $('#edit_backstage form').remove();
+
+      // Revert the changes to classes applied in the the enable/loadForm
+      // methods above.
+      this.element
+        .removeClass('edit-belowoverlay')
+        .addClass('edit-highlighted edit-editable');
+    },
+
+    _initialize: function () {
+      var self = this;
+      $(this.element).bind('focus', function (event) {
+        self.options.activated();
+      });
+
+      $(this.element).bind('blur', function (event) {
+        self.options.deactivated();
+      });
+    }
+  });
+})(jQuery);
diff --git a/js/lib/backbone.js b/js/lib/backbone.js
new file mode 100644
index 0000000..3373c95
--- /dev/null
+++ b/js/lib/backbone.js
@@ -0,0 +1,1431 @@
+//     Backbone.js 0.9.2
+
+//     (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+//     Backbone may be freely distributed under the MIT license.
+//     For all details and documentation:
+//     http://backbonejs.org
+
+(function(){
+
+  // Initial Setup
+  // -------------
+
+  // Save a reference to the global object (`window` in the browser, `global`
+  // on the server).
+  var root = this;
+
+  // Save the previous value of the `Backbone` variable, so that it can be
+  // restored later on, if `noConflict` is used.
+  var previousBackbone = root.Backbone;
+
+  // Create a local reference to slice/splice.
+  var slice = Array.prototype.slice;
+  var splice = Array.prototype.splice;
+
+  // The top-level namespace. All public Backbone classes and modules will
+  // be attached to this. Exported for both CommonJS and the browser.
+  var Backbone;
+  if (typeof exports !== 'undefined') {
+    Backbone = exports;
+  } else {
+    Backbone = root.Backbone = {};
+  }
+
+  // Current version of the library. Keep in sync with `package.json`.
+  Backbone.VERSION = '0.9.2';
+
+  // Require Underscore, if we're on the server, and it's not already present.
+  var _ = root._;
+  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
+
+  // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
+  var $ = root.jQuery || root.Zepto || root.ender;
+
+  // Set the JavaScript library that will be used for DOM manipulation and
+  // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
+  // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
+  // alternate JavaScript library (or a mock library for testing your views
+  // outside of a browser).
+  Backbone.setDomLibrary = function(lib) {
+    $ = lib;
+  };
+
+  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
+  // to its previous owner. Returns a reference to this Backbone object.
+  Backbone.noConflict = function() {
+    root.Backbone = previousBackbone;
+    return this;
+  };
+
+  // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
+  // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
+  // set a `X-Http-Method-Override` header.
+  Backbone.emulateHTTP = false;
+
+  // Turn on `emulateJSON` to support legacy servers that can't deal with direct
+  // `application/json` requests ... will encode the body as
+  // `application/x-www-form-urlencoded` instead and will send the model in a
+  // form param named `model`.
+  Backbone.emulateJSON = false;
+
+  // Backbone.Events
+  // -----------------
+
+  // Regular expression used to split event strings
+  var eventSplitter = /\s+/;
+
+  // A module that can be mixed in to *any object* in order to provide it with
+  // custom events. You may bind with `on` or remove with `off` callback functions
+  // to an event; trigger`-ing an event fires all callbacks in succession.
+  //
+  //     var object = {};
+  //     _.extend(object, Backbone.Events);
+  //     object.on('expand', function(){ alert('expanded'); });
+  //     object.trigger('expand');
+  //
+  var Events = Backbone.Events = {
+
+    // Bind one or more space separated events, `events`, to a `callback`
+    // function. Passing `"all"` will bind the callback to all events fired.
+    on: function(events, callback, context) {
+
+      var calls, event, node, tail, list;
+      if (!callback) return this;
+      events = events.split(eventSplitter);
+      calls = this._callbacks || (this._callbacks = {});
+
+      // Create an immutable callback list, allowing traversal during
+      // modification.  The tail is an empty object that will always be used
+      // as the next node.
+      while (event = events.shift()) {
+        list = calls[event];
+        node = list ? list.tail : {};
+        node.next = tail = {};
+        node.context = context;
+        node.callback = callback;
+        calls[event] = {tail: tail, next: list ? list.next : node};
+      }
+
+      return this;
+    },
+
+    // Remove one or many callbacks. If `context` is null, removes all callbacks
+    // with that function. If `callback` is null, removes all callbacks for the
+    // event. If `events` is null, removes all bound callbacks for all events.
+    off: function(events, callback, context) {
+      var event, calls, node, tail, cb, ctx;
+
+      // No events, or removing *all* events.
+      if (!(calls = this._callbacks)) return;
+      if (!(events || callback || context)) {
+        delete this._callbacks;
+        return this;
+      }
+
+      // Loop through the listed events and contexts, splicing them out of the
+      // linked list of callbacks if appropriate.
+      events = events ? events.split(eventSplitter) : _.keys(calls);
+      while (event = events.shift()) {
+        node = calls[event];
+        delete calls[event];
+        if (!node || !(callback || context)) continue;
+        // Create a new list, omitting the indicated callbacks.
+        tail = node.tail;
+        while ((node = node.next) !== tail) {
+          cb = node.callback;
+          ctx = node.context;
+          if ((callback && cb !== callback) || (context && ctx !== context)) {
+            this.on(event, cb, ctx);
+          }
+        }
+      }
+
+      return this;
+    },
+
+    // Trigger one or many events, firing all bound callbacks. Callbacks are
+    // passed the same arguments as `trigger` is, apart from the event name
+    // (unless you're listening on `"all"`, which will cause your callback to
+    // receive the true name of the event as the first argument).
+    trigger: function(events) {
+      var event, node, calls, tail, args, all, rest;
+      if (!(calls = this._callbacks)) return this;
+      all = calls.all;
+      events = events.split(eventSplitter);
+      rest = slice.call(arguments, 1);
+
+      // For each event, walk through the linked list of callbacks twice,
+      // first to trigger the event, then to trigger any `"all"` callbacks.
+      while (event = events.shift()) {
+        if (node = calls[event]) {
+          tail = node.tail;
+          while ((node = node.next) !== tail) {
+            node.callback.apply(node.context || this, rest);
+          }
+        }
+        if (node = all) {
+          tail = node.tail;
+          args = [event].concat(rest);
+          while ((node = node.next) !== tail) {
+            node.callback.apply(node.context || this, args);
+          }
+        }
+      }
+
+      return this;
+    }
+
+  };
+
+  // Aliases for backwards compatibility.
+  Events.bind   = Events.on;
+  Events.unbind = Events.off;
+
+  // Backbone.Model
+  // --------------
+
+  // Create a new model, with defined attributes. A client id (`cid`)
+  // is automatically generated and assigned for you.
+  var Model = Backbone.Model = function(attributes, options) {
+    var defaults;
+    attributes || (attributes = {});
+    if (options && options.parse) attributes = this.parse(attributes);
+    if (defaults = getValue(this, 'defaults')) {
+      attributes = _.extend({}, defaults, attributes);
+    }
+    if (options && options.collection) this.collection = options.collection;
+    this.attributes = {};
+    this._escapedAttributes = {};
+    this.cid = _.uniqueId('c');
+    this.changed = {};
+    this._silent = {};
+    this._pending = {};
+    this.set(attributes, {silent: true});
+    // Reset change tracking.
+    this.changed = {};
+    this._silent = {};
+    this._pending = {};
+    this._previousAttributes = _.clone(this.attributes);
+    this.initialize.apply(this, arguments);
+  };
+
+  // Attach all inheritable methods to the Model prototype.
+  _.extend(Model.prototype, Events, {
+
+    // A hash of attributes whose current and previous value differ.
+    changed: null,
+
+    // A hash of attributes that have silently changed since the last time
+    // `change` was called.  Will become pending attributes on the next call.
+    _silent: null,
+
+    // A hash of attributes that have changed since the last `'change'` event
+    // began.
+    _pending: null,
+
+    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+    // CouchDB users may want to set this to `"_id"`.
+    idAttribute: 'id',
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // Return a copy of the model's `attributes` object.
+    toJSON: function(options) {
+      return _.clone(this.attributes);
+    },
+
+    // Get the value of an attribute.
+    get: function(attr) {
+      return this.attributes[attr];
+    },
+
+    // Get the HTML-escaped value of an attribute.
+    escape: function(attr) {
+      var html;
+      if (html = this._escapedAttributes[attr]) return html;
+      var val = this.get(attr);
+      return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
+    },
+
+    // Returns `true` if the attribute contains a value that is not null
+    // or undefined.
+    has: function(attr) {
+      return this.get(attr) != null;
+    },
+
+    // Set a hash of model attributes on the object, firing `"change"` unless
+    // you choose to silence it.
+    set: function(key, value, options) {
+      var attrs, attr, val;
+
+      // Handle both `"key", value` and `{key: value}` -style arguments.
+      if (_.isObject(key) || key == null) {
+        attrs = key;
+        options = value;
+      } else {
+        attrs = {};
+        attrs[key] = value;
+      }
+
+      // Extract attributes and options.
+      options || (options = {});
+      if (!attrs) return this;
+      if (attrs instanceof Model) attrs = attrs.attributes;
+      if (options.unset) for (attr in attrs) attrs[attr] = void 0;
+
+      // Run validation.
+      if (!this._validate(attrs, options)) return false;
+
+      // Check for changes of `id`.
+      if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+      var changes = options.changes = {};
+      var now = this.attributes;
+      var escaped = this._escapedAttributes;
+      var prev = this._previousAttributes || {};
+
+      // For each `set` attribute...
+      for (attr in attrs) {
+        val = attrs[attr];
+
+        // If the new and current value differ, record the change.
+        if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
+          delete escaped[attr];
+          (options.silent ? this._silent : changes)[attr] = true;
+        }
+
+        // Update or delete the current value.
+        options.unset ? delete now[attr] : now[attr] = val;
+
+        // If the new and previous value differ, record the change.  If not,
+        // then remove changes for this attribute.
+        if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
+          this.changed[attr] = val;
+          if (!options.silent) this._pending[attr] = true;
+        } else {
+          delete this.changed[attr];
+          delete this._pending[attr];
+        }
+      }
+
+      // Fire the `"change"` events.
+      if (!options.silent) this.change(options);
+      return this;
+    },
+
+    // Remove an attribute from the model, firing `"change"` unless you choose
+    // to silence it. `unset` is a noop if the attribute doesn't exist.
+    unset: function(attr, options) {
+      (options || (options = {})).unset = true;
+      return this.set(attr, null, options);
+    },
+
+    // Clear all attributes on the model, firing `"change"` unless you choose
+    // to silence it.
+    clear: function(options) {
+      (options || (options = {})).unset = true;
+      return this.set(_.clone(this.attributes), options);
+    },
+
+    // Fetch the model from the server. If the server's representation of the
+    // model differs from its current attributes, they will be overriden,
+    // triggering a `"change"` event.
+    fetch: function(options) {
+      options = options ? _.clone(options) : {};
+      var model = this;
+      var success = options.success;
+      options.success = function(resp, status, xhr) {
+        if (!model.set(model.parse(resp, xhr), options)) return false;
+        if (success) success(model, resp);
+      };
+      options.error = Backbone.wrapError(options.error, model, options);
+      return (this.sync || Backbone.sync).call(this, 'read', this, options);
+    },
+
+    // Set a hash of model attributes, and sync the model to the server.
+    // If the server returns an attributes hash that differs, the model's
+    // state will be `set` again.
+    save: function(key, value, options) {
+      var attrs, current;
+
+      // Handle both `("key", value)` and `({key: value})` -style calls.
+      if (_.isObject(key) || key == null) {
+        attrs = key;
+        options = value;
+      } else {
+        attrs = {};
+        attrs[key] = value;
+      }
+      options = options ? _.clone(options) : {};
+
+      // If we're "wait"-ing to set changed attributes, validate early.
+      if (options.wait) {
+        if (!this._validate(attrs, options)) return false;
+        current = _.clone(this.attributes);
+      }
+
+      // Regular saves `set` attributes before persisting to the server.
+      var silentOptions = _.extend({}, options, {silent: true});
+      if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
+        return false;
+      }
+
+      // After a successful server-side save, the client is (optionally)
+      // updated with the server-side state.
+      var model = this;
+      var success = options.success;
+      options.success = function(resp, status, xhr) {
+        var serverAttrs = model.parse(resp, xhr);
+        if (options.wait) {
+          delete options.wait;
+          serverAttrs = _.extend(attrs || {}, serverAttrs);
+        }
+        if (!model.set(serverAttrs, options)) return false;
+        if (success) {
+          success(model, resp);
+        } else {
+          model.trigger('sync', model, resp, options);
+        }
+      };
+
+      // Finish configuring and sending the Ajax request.
+      options.error = Backbone.wrapError(options.error, model, options);
+      var method = this.isNew() ? 'create' : 'update';
+      var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
+      if (options.wait) this.set(current, silentOptions);
+      return xhr;
+    },
+
+    // Destroy this model on the server if it was already persisted.
+    // Optimistically removes the model from its collection, if it has one.
+    // If `wait: true` is passed, waits for the server to respond before removal.
+    destroy: function(options) {
+      options = options ? _.clone(options) : {};
+      var model = this;
+      var success = options.success;
+
+      var triggerDestroy = function() {
+        model.trigger('destroy', model, model.collection, options);
+      };
+
+      if (this.isNew()) {
+        triggerDestroy();
+        return false;
+      }
+
+      options.success = function(resp) {
+        if (options.wait) triggerDestroy();
+        if (success) {
+          success(model, resp);
+        } else {
+          model.trigger('sync', model, resp, options);
+        }
+      };
+
+      options.error = Backbone.wrapError(options.error, model, options);
+      var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
+      if (!options.wait) triggerDestroy();
+      return xhr;
+    },
+
+    // Default URL for the model's representation on the server -- if you're
+    // using Backbone's restful methods, override this to change the endpoint
+    // that will be called.
+    url: function() {
+      var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
+      if (this.isNew()) return base;
+      return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
+    },
+
+    // **parse** converts a response into the hash of attributes to be `set` on
+    // the model. The default implementation is just to pass the response along.
+    parse: function(resp, xhr) {
+      return resp;
+    },
+
+    // Create a new model with identical attributes to this one.
+    clone: function() {
+      return new this.constructor(this.attributes);
+    },
+
+    // A model is new if it has never been saved to the server, and lacks an id.
+    isNew: function() {
+      return this.id == null;
+    },
+
+    // Call this method to manually fire a `"change"` event for this model and
+    // a `"change:attribute"` event for each changed attribute.
+    // Calling this will cause all objects observing the model to update.
+    change: function(options) {
+      options || (options = {});
+      var changing = this._changing;
+      this._changing = true;
+
+      // Silent changes become pending changes.
+      for (var attr in this._silent) this._pending[attr] = true;
+
+      // Silent changes are triggered.
+      var changes = _.extend({}, options.changes, this._silent);
+      this._silent = {};
+      for (var attr in changes) {
+        this.trigger('change:' + attr, this, this.get(attr), options);
+      }
+      if (changing) return this;
+
+      // Continue firing `"change"` events while there are pending changes.
+      while (!_.isEmpty(this._pending)) {
+        this._pending = {};
+        this.trigger('change', this, options);
+        // Pending and silent changes still remain.
+        for (var attr in this.changed) {
+          if (this._pending[attr] || this._silent[attr]) continue;
+          delete this.changed[attr];
+        }
+        this._previousAttributes = _.clone(this.attributes);
+      }
+
+      this._changing = false;
+      return this;
+    },
+
+    // Determine if the model has changed since the last `"change"` event.
+    // If you specify an attribute name, determine if that attribute has changed.
+    hasChanged: function(attr) {
+      if (!arguments.length) return !_.isEmpty(this.changed);
+      return _.has(this.changed, attr);
+    },
+
+    // Return an object containing all the attributes that have changed, or
+    // false if there are no changed attributes. Useful for determining what
+    // parts of a view need to be updated and/or what attributes need to be
+    // persisted to the server. Unset attributes will be set to undefined.
+    // You can also pass an attributes object to diff against the model,
+    // determining if there *would be* a change.
+    changedAttributes: function(diff) {
+      if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
+      var val, changed = false, old = this._previousAttributes;
+      for (var attr in diff) {
+        if (_.isEqual(old[attr], (val = diff[attr]))) continue;
+        (changed || (changed = {}))[attr] = val;
+      }
+      return changed;
+    },
+
+    // Get the previous value of an attribute, recorded at the time the last
+    // `"change"` event was fired.
+    previous: function(attr) {
+      if (!arguments.length || !this._previousAttributes) return null;
+      return this._previousAttributes[attr];
+    },
+
+    // Get all of the attributes of the model at the time of the previous
+    // `"change"` event.
+    previousAttributes: function() {
+      return _.clone(this._previousAttributes);
+    },
+
+    // Check if the model is currently in a valid state. It's only possible to
+    // get into an *invalid* state if you're using silent changes.
+    isValid: function() {
+      return !this.validate(this.attributes);
+    },
+
+    // Run validation against the next complete set of model attributes,
+    // returning `true` if all is well. If a specific `error` callback has
+    // been passed, call that instead of firing the general `"error"` event.
+    _validate: function(attrs, options) {
+      if (options.silent || !this.validate) return true;
+      attrs = _.extend({}, this.attributes, attrs);
+      var error = this.validate(attrs, options);
+      if (!error) return true;
+      if (options && options.error) {
+        options.error(this, error, options);
+      } else {
+        this.trigger('error', this, error, options);
+      }
+      return false;
+    }
+
+  });
+
+  // Backbone.Collection
+  // -------------------
+
+  // Provides a standard collection class for our sets of models, ordered
+  // or unordered. If a `comparator` is specified, the Collection will maintain
+  // its models in sort order, as they're added and removed.
+  var Collection = Backbone.Collection = function(models, options) {
+    options || (options = {});
+    if (options.model) this.model = options.model;
+    if (options.comparator) this.comparator = options.comparator;
+    this._reset();
+    this.initialize.apply(this, arguments);
+    if (models) this.reset(models, {silent: true, parse: options.parse});
+  };
+
+  // Define the Collection's inheritable methods.
+  _.extend(Collection.prototype, Events, {
+
+    // The default model for a collection is just a **Backbone.Model**.
+    // This should be overridden in most cases.
+    model: Model,
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // The JSON representation of a Collection is an array of the
+    // models' attributes.
+    toJSON: function(options) {
+      return this.map(function(model){ return model.toJSON(options); });
+    },
+
+    // Add a model, or list of models to the set. Pass **silent** to avoid
+    // firing the `add` event for every new model.
+    add: function(models, options) {
+      var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
+      options || (options = {});
+      models = _.isArray(models) ? models.slice() : [models];
+
+      // Begin by turning bare objects into model references, and preventing
+      // invalid models or duplicate models from being added.
+      for (i = 0, length = models.length; i < length; i++) {
+        if (!(model = models[i] = this._prepareModel(models[i], options))) {
+          throw new Error("Can't add an invalid model to a collection");
+        }
+        cid = model.cid;
+        id = model.id;
+        if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
+          dups.push(i);
+          continue;
+        }
+        cids[cid] = ids[id] = model;
+      }
+
+      // Remove duplicates.
+      i = dups.length;
+      while (i--) {
+        models.splice(dups[i], 1);
+      }
+
+      // Listen to added models' events, and index models for lookup by
+      // `id` and by `cid`.
+      for (i = 0, length = models.length; i < length; i++) {
+        (model = models[i]).on('all', this._onModelEvent, this);
+        this._byCid[model.cid] = model;
+        if (model.id != null) this._byId[model.id] = model;
+      }
+
+      // Insert models into the collection, re-sorting if needed, and triggering
+      // `add` events unless silenced.
+      this.length += length;
+      index = options.at != null ? options.at : this.models.length;
+      splice.apply(this.models, [index, 0].concat(models));
+      if (this.comparator) this.sort({silent: true});
+      if (options.silent) return this;
+      for (i = 0, length = this.models.length; i < length; i++) {
+        if (!cids[(model = this.models[i]).cid]) continue;
+        options.index = i;
+        model.trigger('add', model, this, options);
+      }
+      return this;
+    },
+
+    // Remove a model, or a list of models from the set. Pass silent to avoid
+    // firing the `remove` event for every model removed.
+    remove: function(models, options) {
+      var i, l, index, model;
+      options || (options = {});
+      models = _.isArray(models) ? models.slice() : [models];
+      for (i = 0, l = models.length; i < l; i++) {
+        model = this.getByCid(models[i]) || this.get(models[i]);
+        if (!model) continue;
+        delete this._byId[model.id];
+        delete this._byCid[model.cid];
+        index = this.indexOf(model);
+        this.models.splice(index, 1);
+        this.length--;
+        if (!options.silent) {
+          options.index = index;
+          model.trigger('remove', model, this, options);
+        }
+        this._removeReference(model);
+      }
+      return this;
+    },
+
+    // Add a model to the end of the collection.
+    push: function(model, options) {
+      model = this._prepareModel(model, options);
+      this.add(model, options);
+      return model;
+    },
+
+    // Remove a model from the end of the collection.
+    pop: function(options) {
+      var model = this.at(this.length - 1);
+      this.remove(model, options);
+      return model;
+    },
+
+    // Add a model to the beginning of the collection.
+    unshift: function(model, options) {
+      model = this._prepareModel(model, options);
+      this.add(model, _.extend({at: 0}, options));
+      return model;
+    },
+
+    // Remove a model from the beginning of the collection.
+    shift: function(options) {
+      var model = this.at(0);
+      this.remove(model, options);
+      return model;
+    },
+
+    // Get a model from the set by id.
+    get: function(id) {
+      if (id == null) return void 0;
+      return this._byId[id.id != null ? id.id : id];
+    },
+
+    // Get a model from the set by client id.
+    getByCid: function(cid) {
+      return cid && this._byCid[cid.cid || cid];
+    },
+
+    // Get the model at the given index.
+    at: function(index) {
+      return this.models[index];
+    },
+
+    // Return models with matching attributes. Useful for simple cases of `filter`.
+    where: function(attrs) {
+      if (_.isEmpty(attrs)) return [];
+      return this.filter(function(model) {
+        for (var key in attrs) {
+          if (attrs[key] !== model.get(key)) return false;
+        }
+        return true;
+      });
+    },
+
+    // Force the collection to re-sort itself. You don't need to call this under
+    // normal circumstances, as the set will maintain sort order as each item
+    // is added.
+    sort: function(options) {
+      options || (options = {});
+      if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
+      var boundComparator = _.bind(this.comparator, this);
+      if (this.comparator.length == 1) {
+        this.models = this.sortBy(boundComparator);
+      } else {
+        this.models.sort(boundComparator);
+      }
+      if (!options.silent) this.trigger('reset', this, options);
+      return this;
+    },
+
+    // Pluck an attribute from each model in the collection.
+    pluck: function(attr) {
+      return _.map(this.models, function(model){ return model.get(attr); });
+    },
+
+    // When you have more items than you want to add or remove individually,
+    // you can reset the entire set with a new list of models, without firing
+    // any `add` or `remove` events. Fires `reset` when finished.
+    reset: function(models, options) {
+      models  || (models = []);
+      options || (options = {});
+      for (var i = 0, l = this.models.length; i < l; i++) {
+        this._removeReference(this.models[i]);
+      }
+      this._reset();
+      this.add(models, _.extend({silent: true}, options));
+      if (!options.silent) this.trigger('reset', this, options);
+      return this;
+    },
+
+    // Fetch the default set of models for this collection, resetting the
+    // collection when they arrive. If `add: true` is passed, appends the
+    // models to the collection instead of resetting.
+    fetch: function(options) {
+      options = options ? _.clone(options) : {};
+      if (options.parse === undefined) options.parse = true;
+      var collection = this;
+      var success = options.success;
+      options.success = function(resp, status, xhr) {
+        collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
+        if (success) success(collection, resp);
+      };
+      options.error = Backbone.wrapError(options.error, collection, options);
+      return (this.sync || Backbone.sync).call(this, 'read', this, options);
+    },
+
+    // Create a new instance of a model in this collection. Add the model to the
+    // collection immediately, unless `wait: true` is passed, in which case we
+    // wait for the server to agree.
+    create: function(model, options) {
+      var coll = this;
+      options = options ? _.clone(options) : {};
+      model = this._prepareModel(model, options);
+      if (!model) return false;
+      if (!options.wait) coll.add(model, options);
+      var success = options.success;
+      options.success = function(nextModel, resp, xhr) {
+        if (options.wait) coll.add(nextModel, options);
+        if (success) {
+          success(nextModel, resp);
+        } else {
+          nextModel.trigger('sync', model, resp, options);
+        }
+      };
+      model.save(null, options);
+      return model;
+    },
+
+    // **parse** converts a response into a list of models to be added to the
+    // collection. The default implementation is just to pass it through.
+    parse: function(resp, xhr) {
+      return resp;
+    },
+
+    // Proxy to _'s chain. Can't be proxied the same way the rest of the
+    // underscore methods are proxied because it relies on the underscore
+    // constructor.
+    chain: function () {
+      return _(this.models).chain();
+    },
+
+    // Reset all internal state. Called when the collection is reset.
+    _reset: function(options) {
+      this.length = 0;
+      this.models = [];
+      this._byId  = {};
+      this._byCid = {};
+    },
+
+    // Prepare a model or hash of attributes to be added to this collection.
+    _prepareModel: function(model, options) {
+      options || (options = {});
+      if (!(model instanceof Model)) {
+        var attrs = model;
+        options.collection = this;
+        model = new this.model(attrs, options);
+        if (!model._validate(model.attributes, options)) model = false;
+      } else if (!model.collection) {
+        model.collection = this;
+      }
+      return model;
+    },
+
+    // Internal method to remove a model's ties to a collection.
+    _removeReference: function(model) {
+      if (this == model.collection) {
+        delete model.collection;
+      }
+      model.off('all', this._onModelEvent, this);
+    },
+
+    // Internal method called every time a model in the set fires an event.
+    // Sets need to update their indexes when models change ids. All other
+    // events simply proxy through. "add" and "remove" events that originate
+    // in other collections are ignored.
+    _onModelEvent: function(event, model, collection, options) {
+      if ((event == 'add' || event == 'remove') && collection != this) return;
+      if (event == 'destroy') {
+        this.remove(model, options);
+      }
+      if (model && event === 'change:' + model.idAttribute) {
+        delete this._byId[model.previous(model.idAttribute)];
+        this._byId[model.id] = model;
+      }
+      this.trigger.apply(this, arguments);
+    }
+
+  });
+
+  // Underscore methods that we want to implement on the Collection.
+  var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
+    'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
+    'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
+    'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
+    'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
+
+  // Mix in each Underscore method as a proxy to `Collection#models`.
+  _.each(methods, function(method) {
+    Collection.prototype[method] = function() {
+      return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
+    };
+  });
+
+  // Backbone.Router
+  // -------------------
+
+  // Routers map faux-URLs to actions, and fire events when routes are
+  // matched. Creating a new one sets its `routes` hash, if not set statically.
+  var Router = Backbone.Router = function(options) {
+    options || (options = {});
+    if (options.routes) this.routes = options.routes;
+    this._bindRoutes();
+    this.initialize.apply(this, arguments);
+  };
+
+  // Cached regular expressions for matching named param parts and splatted
+  // parts of route strings.
+  var namedParam    = /:\w+/g;
+  var splatParam    = /\*\w+/g;
+  var escapeRegExp  = /[-[\]{}()+?.,\\^$|#\s]/g;
+
+  // Set up all inheritable **Backbone.Router** properties and methods.
+  _.extend(Router.prototype, Events, {
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // Manually bind a single named route to a callback. For example:
+    //
+    //     this.route('search/:query/p:num', 'search', function(query, num) {
+    //       ...
+    //     });
+    //
+    route: function(route, name, callback) {
+      Backbone.history || (Backbone.history = new History);
+      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
+      if (!callback) callback = this[name];
+      Backbone.history.route(route, _.bind(function(fragment) {
+        var args = this._extractParameters(route, fragment);
+        callback && callback.apply(this, args);
+        this.trigger.apply(this, ['route:' + name].concat(args));
+        Backbone.history.trigger('route', this, name, args);
+      }, this));
+      return this;
+    },
+
+    // Simple proxy to `Backbone.history` to save a fragment into the history.
+    navigate: function(fragment, options) {
+      Backbone.history.navigate(fragment, options);
+    },
+
+    // Bind all defined routes to `Backbone.history`. We have to reverse the
+    // order of the routes here to support behavior where the most general
+    // routes can be defined at the bottom of the route map.
+    _bindRoutes: function() {
+      if (!this.routes) return;
+      var routes = [];
+      for (var route in this.routes) {
+        routes.unshift([route, this.routes[route]]);
+      }
+      for (var i = 0, l = routes.length; i < l; i++) {
+        this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
+      }
+    },
+
+    // Convert a route string into a regular expression, suitable for matching
+    // against the current location hash.
+    _routeToRegExp: function(route) {
+      route = route.replace(escapeRegExp, '\\$&')
+                   .replace(namedParam, '([^\/]+)')
+                   .replace(splatParam, '(.*?)');
+      return new RegExp('^' + route + '$');
+    },
+
+    // Given a route, and a URL fragment that it matches, return the array of
+    // extracted parameters.
+    _extractParameters: function(route, fragment) {
+      return route.exec(fragment).slice(1);
+    }
+
+  });
+
+  // Backbone.History
+  // ----------------
+
+  // Handles cross-browser history management, based on URL fragments. If the
+  // browser does not support `onhashchange`, falls back to polling.
+  var History = Backbone.History = function() {
+    this.handlers = [];
+    _.bindAll(this, 'checkUrl');
+  };
+
+  // Cached regex for cleaning leading hashes and slashes .
+  var routeStripper = /^[#\/]/;
+
+  // Cached regex for detecting MSIE.
+  var isExplorer = /msie [\w.]+/;
+
+  // Has the history handling already been started?
+  History.started = false;
+
+  // Set up all inheritable **Backbone.History** properties and methods.
+  _.extend(History.prototype, Events, {
+
+    // The default interval to poll for hash changes, if necessary, is
+    // twenty times a second.
+    interval: 50,
+
+    // Gets the true hash value. Cannot use location.hash directly due to bug
+    // in Firefox where location.hash will always be decoded.
+    getHash: function(windowOverride) {
+      var loc = windowOverride ? windowOverride.location : window.location;
+      var match = loc.href.match(/#(.*)$/);
+      return match ? match[1] : '';
+    },
+
+    // Get the cross-browser normalized URL fragment, either from the URL,
+    // the hash, or the override.
+    getFragment: function(fragment, forcePushState) {
+      if (fragment == null) {
+        if (this._hasPushState || forcePushState) {
+          fragment = window.location.pathname;
+          var search = window.location.search;
+          if (search) fragment += search;
+        } else {
+          fragment = this.getHash();
+        }
+      }
+      if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
+      return fragment.replace(routeStripper, '');
+    },
+
+    // Start the hash change handling, returning `true` if the current URL matches
+    // an existing route, and `false` otherwise.
+    start: function(options) {
+      if (History.started) throw new Error("Backbone.history has already been started");
+      History.started = true;
+
+      // Figure out the initial configuration. Do we need an iframe?
+      // Is pushState desired ... is it available?
+      this.options          = _.extend({}, {root: '/'}, this.options, options);
+      this._wantsHashChange = this.options.hashChange !== false;
+      this._wantsPushState  = !!this.options.pushState;
+      this._hasPushState    = !!(this.options.pushState && window.history && window.history.pushState);
+      var fragment          = this.getFragment();
+      var docMode           = document.documentMode;
+      var oldIE             = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
+
+      if (oldIE) {
+        this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
+        this.navigate(fragment);
+      }
+
+      // Depending on whether we're using pushState or hashes, and whether
+      // 'onhashchange' is supported, determine how we check the URL state.
+      if (this._hasPushState) {
+        $(window).bind('popstate', this.checkUrl);
+      } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
+        $(window).bind('hashchange', this.checkUrl);
+      } else if (this._wantsHashChange) {
+        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
+      }
+
+      // Determine if we need to change the base url, for a pushState link
+      // opened by a non-pushState browser.
+      this.fragment = fragment;
+      var loc = window.location;
+      var atRoot  = loc.pathname == this.options.root;
+
+      // If we've started off with a route from a `pushState`-enabled browser,
+      // but we're currently in a browser that doesn't support it...
+      if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
+        this.fragment = this.getFragment(null, true);
+        window.location.replace(this.options.root + '#' + this.fragment);
+        // Return immediately as browser will do redirect to new url
+        return true;
+
+      // Or if we've started out with a hash-based route, but we're currently
+      // in a browser where it could be `pushState`-based instead...
+      } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
+        this.fragment = this.getHash().replace(routeStripper, '');
+        window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
+      }
+
+      if (!this.options.silent) {
+        return this.loadUrl();
+      }
+    },
+
+    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
+    // but possibly useful for unit testing Routers.
+    stop: function() {
+      $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
+      clearInterval(this._checkUrlInterval);
+      History.started = false;
+    },
+
+    // Add a route to be tested when the fragment changes. Routes added later
+    // may override previous routes.
+    route: function(route, callback) {
+      this.handlers.unshift({route: route, callback: callback});
+    },
+
+    // Checks the current URL to see if it has changed, and if it has,
+    // calls `loadUrl`, normalizing across the hidden iframe.
+    checkUrl: function(e) {
+      var current = this.getFragment();
+      if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
+      if (current == this.fragment) return false;
+      if (this.iframe) this.navigate(current);
+      this.loadUrl() || this.loadUrl(this.getHash());
+    },
+
+    // Attempt to load the current URL fragment. If a route succeeds with a
+    // match, returns `true`. If no defined routes matches the fragment,
+    // returns `false`.
+    loadUrl: function(fragmentOverride) {
+      var fragment = this.fragment = this.getFragment(fragmentOverride);
+      var matched = _.any(this.handlers, function(handler) {
+        if (handler.route.test(fragment)) {
+          handler.callback(fragment);
+          return true;
+        }
+      });
+      return matched;
+    },
+
+    // Save a fragment into the hash history, or replace the URL state if the
+    // 'replace' option is passed. You are responsible for properly URL-encoding
+    // the fragment in advance.
+    //
+    // The options object can contain `trigger: true` if you wish to have the
+    // route callback be fired (not usually desirable), or `replace: true`, if
+    // you wish to modify the current URL without adding an entry to the history.
+    navigate: function(fragment, options) {
+      if (!History.started) return false;
+      if (!options || options === true) options = {trigger: options};
+      var frag = (fragment || '').replace(routeStripper, '');
+      if (this.fragment == frag) return;
+
+      // If pushState is available, we use it to set the fragment as a real URL.
+      if (this._hasPushState) {
+        if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
+        this.fragment = frag;
+        window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
+
+      // If hash changes haven't been explicitly disabled, update the hash
+      // fragment to store history.
+      } else if (this._wantsHashChange) {
+        this.fragment = frag;
+        this._updateHash(window.location, frag, options.replace);
+        if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
+          // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
+          // When replace is true, we don't want this.
+          if(!options.replace) this.iframe.document.open().close();
+          this._updateHash(this.iframe.location, frag, options.replace);
+        }
+
+      // If you've told us that you explicitly don't want fallback hashchange-
+      // based history, then `navigate` becomes a page refresh.
+      } else {
+        window.location.assign(this.options.root + fragment);
+      }
+      if (options.trigger) this.loadUrl(fragment);
+    },
+
+    // Update the hash location, either replacing the current entry, or adding
+    // a new one to the browser history.
+    _updateHash: function(location, fragment, replace) {
+      if (replace) {
+        location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
+      } else {
+        location.hash = fragment;
+      }
+    }
+  });
+
+  // Backbone.View
+  // -------------
+
+  // Creating a Backbone.View creates its initial element outside of the DOM,
+  // if an existing element is not provided...
+  var View = Backbone.View = function(options) {
+    this.cid = _.uniqueId('view');
+    this._configure(options || {});
+    this._ensureElement();
+    this.initialize.apply(this, arguments);
+    this.delegateEvents();
+  };
+
+  // Cached regex to split keys for `delegate`.
+  var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+  // List of view options to be merged as properties.
+  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
+
+  // Set up all inheritable **Backbone.View** properties and methods.
+  _.extend(View.prototype, Events, {
+
+    // The default `tagName` of a View's element is `"div"`.
+    tagName: 'div',
+
+    // jQuery delegate for element lookup, scoped to DOM elements within the
+    // current view. This should be prefered to global lookups where possible.
+    $: function(selector) {
+      return this.$el.find(selector);
+    },
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // **render** is the core function that your view should override, in order
+    // to populate its element (`this.el`), with the appropriate HTML. The
+    // convention is for **render** to always return `this`.
+    render: function() {
+      return this;
+    },
+
+    // Remove this view from the DOM. Note that the view isn't present in the
+    // DOM by default, so calling this method may be a no-op.
+    remove: function() {
+      this.$el.remove();
+      return this;
+    },
+
+    // For small amounts of DOM Elements, where a full-blown template isn't
+    // needed, use **make** to manufacture elements, one at a time.
+    //
+    //     var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
+    //
+    make: function(tagName, attributes, content) {
+      var el = document.createElement(tagName);
+      if (attributes) $(el).attr(attributes);
+      if (content) $(el).html(content);
+      return el;
+    },
+
+    // Change the view's element (`this.el` property), including event
+    // re-delegation.
+    setElement: function(element, delegate) {
+      if (this.$el) this.undelegateEvents();
+      this.$el = (element instanceof $) ? element : $(element);
+      this.el = this.$el[0];
+      if (delegate !== false) this.delegateEvents();
+      return this;
+    },
+
+    // Set callbacks, where `this.events` is a hash of
+    //
+    // *{"event selector": "callback"}*
+    //
+    //     {
+    //       'mousedown .title':  'edit',
+    //       'click .button':     'save'
+    //       'click .open':       function(e) { ... }
+    //     }
+    //
+    // pairs. Callbacks will be bound to the view, with `this` set properly.
+    // Uses event delegation for efficiency.
+    // Omitting the selector binds the event to `this.el`.
+    // This only works for delegate-able events: not `focus`, `blur`, and
+    // not `change`, `submit`, and `reset` in Internet Explorer.
+    delegateEvents: function(events) {
+      if (!(events || (events = getValue(this, 'events')))) return;
+      this.undelegateEvents();
+      for (var key in events) {
+        var method = events[key];
+        if (!_.isFunction(method)) method = this[events[key]];
+        if (!method) throw new Error('Method "' + events[key] + '" does not exist');
+        var match = key.match(delegateEventSplitter);
+        var eventName = match[1], selector = match[2];
+        method = _.bind(method, this);
+        eventName += '.delegateEvents' + this.cid;
+        if (selector === '') {
+          this.$el.bind(eventName, method);
+        } else {
+          this.$el.delegate(selector, eventName, method);
+        }
+      }
+    },
+
+    // Clears all callbacks previously bound to the view with `delegateEvents`.
+    // You usually don't need to use this, but may wish to if you have multiple
+    // Backbone views attached to the same DOM element.
+    undelegateEvents: function() {
+      this.$el.unbind('.delegateEvents' + this.cid);
+    },
+
+    // Performs the initial configuration of a View with a set of options.
+    // Keys with special meaning *(model, collection, id, className)*, are
+    // attached directly to the view.
+    _configure: function(options) {
+      if (this.options) options = _.extend({}, this.options, options);
+      for (var i = 0, l = viewOptions.length; i < l; i++) {
+        var attr = viewOptions[i];
+        if (options[attr]) this[attr] = options[attr];
+      }
+      this.options = options;
+    },
+
+    // Ensure that the View has a DOM element to render into.
+    // If `this.el` is a string, pass it through `$()`, take the first
+    // matching element, and re-assign it to `el`. Otherwise, create
+    // an element from the `id`, `className` and `tagName` properties.
+    _ensureElement: function() {
+      if (!this.el) {
+        var attrs = getValue(this, 'attributes') || {};
+        if (this.id) attrs.id = this.id;
+        if (this.className) attrs['class'] = this.className;
+        this.setElement(this.make(this.tagName, attrs), false);
+      } else {
+        this.setElement(this.el, false);
+      }
+    }
+
+  });
+
+  // The self-propagating extend function that Backbone classes use.
+  var extend = function (protoProps, classProps) {
+    var child = inherits(this, protoProps, classProps);
+    child.extend = this.extend;
+    return child;
+  };
+
+  // Set up inheritance for the model, collection, and view.
+  Model.extend = Collection.extend = Router.extend = View.extend = extend;
+
+  // Backbone.sync
+  // -------------
+
+  // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
+  var methodMap = {
+    'create': 'POST',
+    'update': 'PUT',
+    'delete': 'DELETE',
+    'read':   'GET'
+  };
+
+  // Override this function to change the manner in which Backbone persists
+  // models to the server. You will be passed the type of request, and the
+  // model in question. By default, makes a RESTful Ajax request
+  // to the model's `url()`. Some possible customizations could be:
+  //
+  // * Use `setTimeout` to batch rapid-fire updates into a single request.
+  // * Send up the models as XML instead of JSON.
+  // * Persist models via WebSockets instead of Ajax.
+  //
+  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
+  // as `POST`, with a `_method` parameter containing the true HTTP method,
+  // as well as all requests with the body as `application/x-www-form-urlencoded`
+  // instead of `application/json` with the model in a param named `model`.
+  // Useful when interfacing with server-side languages like **PHP** that make
+  // it difficult to read the body of `PUT` requests.
+  Backbone.sync = function(method, model, options) {
+    var type = methodMap[method];
+
+    // Default options, unless specified.
+    options || (options = {});
+
+    // Default JSON-request options.
+    var params = {type: type, dataType: 'json'};
+
+    // Ensure that we have a URL.
+    if (!options.url) {
+      params.url = getValue(model, 'url') || urlError();
+    }
+
+    // Ensure that we have the appropriate request data.
+    if (!options.data && model && (method == 'create' || method == 'update')) {
+      params.contentType = 'application/json';
+      params.data = JSON.stringify(model.toJSON());
+    }
+
+    // For older servers, emulate JSON by encoding the request into an HTML-form.
+    if (Backbone.emulateJSON) {
+      params.contentType = 'application/x-www-form-urlencoded';
+      params.data = params.data ? {model: params.data} : {};
+    }
+
+    // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
+    // And an `X-HTTP-Method-Override` header.
+    if (Backbone.emulateHTTP) {
+      if (type === 'PUT' || type === 'DELETE') {
+        if (Backbone.emulateJSON) params.data._method = type;
+        params.type = 'POST';
+        params.beforeSend = function(xhr) {
+          xhr.setRequestHeader('X-HTTP-Method-Override', type);
+        };
+      }
+    }
+
+    // Don't process data on a non-GET request.
+    if (params.type !== 'GET' && !Backbone.emulateJSON) {
+      params.processData = false;
+    }
+
+    // Make the request, allowing the user to override any Ajax options.
+    return $.ajax(_.extend(params, options));
+  };
+
+  // Wrap an optional error callback with a fallback error event.
+  Backbone.wrapError = function(onError, originalModel, options) {
+    return function(model, resp) {
+      resp = model === originalModel ? resp : model;
+      if (onError) {
+        onError(originalModel, resp, options);
+      } else {
+        originalModel.trigger('error', originalModel, resp, options);
+      }
+    };
+  };
+
+  // Helpers
+  // -------
+
+  // Shared empty constructor function to aid in prototype-chain creation.
+  var ctor = function(){};
+
+  // Helper function to correctly set up the prototype chain, for subclasses.
+  // Similar to `goog.inherits`, but uses a hash of prototype properties and
+  // class properties to be extended.
+  var inherits = function(parent, protoProps, staticProps) {
+    var child;
+
+    // The constructor function for the new subclass is either defined by you
+    // (the "constructor" property in your `extend` definition), or defaulted
+    // by us to simply call the parent's constructor.
+    if (protoProps && protoProps.hasOwnProperty('constructor')) {
+      child = protoProps.constructor;
+    } else {
+      child = function(){ parent.apply(this, arguments); };
+    }
+
+    // Inherit class (static) properties from parent.
+    _.extend(child, parent);
+
+    // Set the prototype chain to inherit from `parent`, without calling
+    // `parent`'s constructor function.
+    ctor.prototype = parent.prototype;
+    child.prototype = new ctor();
+
+    // Add prototype properties (instance properties) to the subclass,
+    // if supplied.
+    if (protoProps) _.extend(child.prototype, protoProps);
+
+    // Add static properties to the constructor function, if supplied.
+    if (staticProps) _.extend(child, staticProps);
+
+    // Correctly set child's `prototype.constructor`.
+    child.prototype.constructor = child;
+
+    // Set a convenience property in case the parent's prototype is needed later.
+    child.__super__ = parent.prototype;
+
+    return child;
+  };
+
+  // Helper function to get a value from a Backbone object as a property
+  // or as a function.
+  var getValue = function(object, prop) {
+    if (!(object && object[prop])) return null;
+    return _.isFunction(object[prop]) ? object[prop]() : object[prop];
+  };
+
+  // Throw an error when a URL is needed, and none is supplied.
+  var urlError = function() {
+    throw new Error('A "url" property or function must be specified');
+  };
+
+}).call(this);
diff --git a/js/lib/create.js b/js/lib/create.js
new file mode 100644
index 0000000..c16d347
--- /dev/null
+++ b/js/lib/create.js
@@ -0,0 +1,7 @@
+//     Create.js 1.0.0alpha4 - 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(e,t){"use strict";e.widget("Midgard.midgardCreate",{options:{toolbar:"full",saveButton:null,state:"browse",highlight:!0,highlightColor:"#67cc08",editorWidgets:{"default":"hallo"},editorOptions:{hallo:{widget:"halloWidget"}},collectionWidgets:{"default":"midgardCollectionAdd"},url:function(){},storagePrefix:"node",workflows:{url:null},notifications:{},vie:null,domService:"rdfa",stanbolUrl:null,dbPediaUrl:null,tags:!1,buttonContainer:".create-ui-toolbar-statustoolarea .create-ui-statustools",templates:{buttonContent:'<%= label %> <i class="icon-<%= icon %>"></i>',button:'<li id="<%= id %>"><a class="create-ui-btn"><%= buttonContent %></a></li>'},localize:function(e,t){return window.midgardCreate.localize(e,t)},language:null},_create:function(){this.vie=this._setupVIE(this.options),this.domService=this.vie.service(this.options.domService);var t=this;window.setTimeout(function(){t._checkSession()},10),this.options.language||(this.options.language=e("html").attr("lang")),this._enableToolbar(),this._saveButton(),this._editButton(),this._prepareStorage(),this.element.midgardWorkflows&&this.element.midgardWorkflows(this.options.workflows),this.element.midgardNotifications&&this.element.midgardNotifications(this.options.notifications)},destroy:function(){this.element.midgardStorage("destroy"),this.element.midgardToolbar("destroy"),this.domService.findSubjectElements(this.element).each(function(){e(this).midgardEditable("destroy")}),this.element.midgardWorkflows&&this.element.midgardWorkflows("destroy"),this.element.midgardNotifications&&this.element.midgardNotifications("destroy"),this.options.tags&&this.element.midgardTags("destroy"),e.Widget.prototype.destroy.call(this)},_setupVIE:function(e){var t;return e.vie?t=e.vie:t=new VIE,!t.hasService(this.options.domService)&&this.options.domService==="rdfa"&&t.use(new t.RdfaService),!t.hasService("stanbol")&&e.stanbolUrl&&t.use(new t.StanbolService({proxyDisabled:!0,url:e.stanbolUrl})),!t.hasService("dbpedia")&&e.dbPediaUrl&&t.use(new t.DBPediaService({proxyDisabled:!0,url:e.dbPediaUrl})),t},_prepareStorage:function(){this.element.midgardStorage({vie:this.vie,url:this.options.url,localize:this.options.localize,language:this.options.language});var t=this;this.element.bind("midgardstoragesave",function(){e("#midgardcreate-save a").html(_.template(t.options.templates.buttonContent,{label:t.options.localize("Saving",t.options.language),icon:"upload"}))}),this.element.bind("midgardstoragesaved midgardstorageerror",function(){e("#midgardcreate-save a").html(_.template(t.options.templates.buttonContent,{label:t.options.localize("Save",t.options.language),icon:"ok"}))})},_init:function(){this.setState(this.options.state)},setState:function(e){this._setOption("state",e),e==="edit"?this._enableEdit():this._disableEdit(),this._setEditButtonState(e)},setToolbar:function(e){this.options.toolbar=e,this.element.midgardToolbar("setDisplay",e)},showNotification:function(e){if(this.element.midgardNotifications)return this.element.midgardNotifications("create",e)},configureEditor:function(e,t,n){this.options.editorOptions[e]={widget:t,options:n}},setEditorForContentType:function(e,n){if(this.options.editorOptions[n]===t&&n!==null)throw new Error("No editor "+n+" configured");this.options.editorWidgets[e]=n},setEditorForProperty:function(e,n){if(this.options.editorOptions[n]===t&&n!==null)throw new Error("No editor "+n+" configured");this.options.editorWidgets[e]=n},_checkSession:function(){if(!window.sessionStorage)return;var e=this.options.storagePrefix+"Midgard.create.toolbar";window.sessionStorage.getItem(e)&&this.setToolbar(window.sessionStorage.getItem(e));var t=this.options.storagePrefix+"Midgard.create.state";window.sessionStorage.getItem(t)&&this.setState(window.sessionStorage.getItem(t)),this.element.bind("midgardcreatestatechange",function(e,n){window.sessionStorage.setItem(t,n.state)})},_saveButton:function(){if(this.options.saveButton)return this.options.saveButton;var t=this;return e(this.options.buttonContainer,this.element).append(e(_.template(this.options.templates.button,{id:"midgardcreate-save",buttonContent:_.template(this.options.templates.buttonContent,{label:t.options.localize("Save",t.options.language),icon:"ok"})}))),this.options.saveButton=e("#midgardcreate-save",this.element),this.options.saveButton.hide(),this.options.saveButton},_editButton:function(){var t=this;e(this.options.buttonContainer,this.element).append(e(_.template(this.options.templates.button,{id:"midgardcreate-edit",buttonContent:""}))),e("#midgardcreate-edit",this.element).bind("click",function(){if(t.options.state==="edit"){t.setState("browse");return}t.setState("edit")})},_setEditButtonState:function(t){var n=this,r={edit:_.template(this.options.templates.buttonContent,{label:n.options.localize("Cancel",n.options.language),icon:"remove"}),browse:_.template(this.options.templates.buttonContent,{label:n.options.localize("Edit",n.options.language),icon:"edit"})},i=e("#midgardcreate-edit a",this.element);if(!i)return;t==="edit"&&i.addClass("selected"),i.html(r[t])},_enableToolbar:function(){var e=this;this.element.bind("midgardtoolbarstatechange",function(t,n){e.setToolbar(n.display),window.sessionStorage&&window.sessionStorage.setItem(e.options.storagePrefix+"Midgard.create.toolbar",n.display)}),this.element.midgardToolbar({display:this.options.toolbar,vie:this.vie})},_enableEdit:function(){this._setOption("state","edit");var t=this,n={toolbarState:t.options.toolbar,disabled:!1,vie:t.vie,domService:t.options.domService,widgets:t.options.editorWidgets,editors:t.options.editorOptions,collectionWidgets:t.options.collectionWidgets,localize:t.options.localize,language:t.options.language};t.options.enableEditor&&(n.enableEditor=t.options.enableEditor),t.options.disableEditor&&(n.disableEditor=t.options.disableEditor),this.domService.findSubjectElements(this.element).each(function(){var r=this;if(t.options.highlight){var i=function(n,i){if(!e(i.element).is(":visible"))return;if(i.entityElement.get(0)!==r)return;i.element.stop(!0,!0),i.element.effect("highlight",{color:t.options.highlightColor},3e3)};e(this).bind("midgardeditableenableproperty",i)}e(this).bind("midgardeditabledisable",function(){e(this).unbind("midgardeditableenableproperty",i)}),t.options.tags&&e(this).bind("midgardeditableenable",function(n,i){if(n.target!==r)return;e(this).midgardTags({vie:t.vie,entityElement:i.entityElement,entity:i.instance,localize:t.options.localize,language:t.options.language})}),e(this).midgardEditable(n)}),this._trigger("statechange",null,{state:"edit"})},_disableEdit:function(){var t=this,n={disabled:!0,vie:t.vie,domService:t.options.domService,editorOptions:t.options.editorOptions,localize:t.options.localize,language:t.options.language};this.domService.findSubjectElements(this.element).each(function(){e(this).midgardEditable(n),e(this).removeClass("ui-state-disabled")}),this._setOption("state","browse"),this._trigger("statechange",null,{state:"browse"})}})})(jQuery),function(e,t){"use strict";e.widget("Midgard.midgardCollectionAdd",{options:{editingWidgets:null,collection:null,model:null,definition:null,view:null,disabled:!1,vie:null,editableOptions:null,templates:{button:'<button class="btn"><i class="icon-<%= icon %>"></i> <%= label %></button>'}},_create:function(){this.addButtons=[];var e=this;if(!e.options.collection.localStorage)try{e.options.collection.url=e.options.model.url()}catch(t){window.console&&console.log(t)}e.options.collection.bind("add",function(t){t.primaryCollection=e.options.collection,e.options.vie.entities.add(t),t.collection=e.options.collection}),e.options.collection.bind("add remove reset",e.checkCollectionConstraints,e),e._bindCollectionView(e.options.view)},_bindCollectionView:function(e){var t=this;e.bind("add",function(e){e.$el.effect("slide",function(){t._makeEditable(e)})})},_makeEditable:function(e){this.options.editableOptions.disabled=this.options.disabled,this.options.editableOptions.model=e.model,e.$el.midgardEditable(this.options.editableOptions)},_init:function(){if(this.options.disabled){this.disable();return}this.enable()},hideButtons:function(){_.each(this.addButtons,function(e){e.hide()})},showButtons:function(){_.each(this.addButtons,function(e){e.show()})},checkCollectionConstraints:function(){if(this.options.disabled)return;if(!this.options.view.canAdd()){this.hideButtons();return}if(!this.options.definition){this.showButtons();return}if(!this.options.definition.max||this.options.definition.max===-1){this.showButtons();return}if(this.options.collection.length<this.options.definition.max){this.showButtons();return}this.hideButtons()},enable:function(){var t=this,n=e(_.template(this.options.templates.button,{icon:"plus",label:this.options.editableOptions.localize("Add",this.options.editableOptions.language)})).button();n.addClass("midgard-create-add"),n.click(function(){t.addItem(n)}),e(t.options.view.el).after(n),t.addButtons.push(n),t.checkCollectionConstraints()},disable:function(){_.each(this.addButtons,function(e){e.remove()}),this.addButtons=[]},_getTypeActions:function(e){var t=this,n=[];return _.each(this.options.definition.range,function(r){var i=t.options.collection.vie.namespaces.uri(r);if(!t.options.view.canAdd(i))return;n.push({name:r,label:r,cb:function(){t.options.collection.add({"@type":r},e)},className:"create-ui-btn"})}),n},addItem:function(n,r){r===t&&(r={});var i=_.extend({},r,{validate:!1}),s={};if(this.options.definition&&this.options.definition.range){if(this.options.definition.range.length!==1){e("body").midgardNotifications("create",{bindTo:n,gravity:"L",body:this.options.editableOptions.localize("Choose type to add",this.options.editableOptions.language),timeout:0,actions:this._getTypeActions(i)});return}s["@type"]=this.options.definition.range[0]}this.options.collection.add(s,i)}})}(jQuery),function(e,t){"use strict";e.widget("Midgard.midgardCollectionAddBetween",e.Midgard.midgardCollectionAdd,{_bindCollectionView:function(e){var t=this;e.bind("add",function(e){t._makeEditable(e),t._refreshButtons()}),e.bind("remove",function(){t._refreshButtons()})},_refreshButtons:function(){var e=this;window.setTimeout(function(){e.disable(),e.enable()},1)},prepareButton:function(t){var n=this,r=e(_.template(this.options.templates.button,{icon:"plus",label:""})).button();return r.addClass("midgard-create-add"),r.click(function(){n.addItem(r,{at:t})}),r},enable:function(){var t=this,n=t.prepareButton(0);e(t.options.view.el).prepend(n),t.addButtons.push(n),e.each(t.options.view.entityViews,function(n,r){var i=t.options.collection.indexOf(r.model),s=t.prepareButton(i+1);e(r.el).append(s),t.addButtons.push(s)}),this.checkCollectionConstraints()},disable:function(){var t=this;e.each(t.addButtons,function(e,t){t.remove()}),t.addButtons=[]}})}(jQuery),function(e,t){"use strict";e.widget("Midgard.midgardEditable",{options:{editables:[],collections:[],model:null,editors:{hallo:{widget:"halloWidget",options:{}}},widgets:{"default":"hallo"},collectionWidgets:{"default":"midgardCollectionAdd"},toolbarState:"full",vie:null,domService:"rdfa",predicateSelector:"[property]",disabled:!1,localize:function(e,t){return window.midgardCreate.localize(e,t)},language:null},_create:function(){this.vie=this.options.vie,this.domService=this.vie.service(this.options.domService);if(!this.options.model){var e=this;this.vie.load({element:this.element}).from(this.options.domService).execute().done(function(t){e.options.model=t[0]})}},_init:function(){if(this.options.disabled){this.disable();return}this.enable()},findEditableElements:function(t){this.domService.findPredicateElements(this.options.model.id,e(this.options.predicateSelector,this.element),!1).each(t)},enable:function(){var t=this;if(!this.options.model)return;this.findEditableElements(function(){return t._enableProperty(e(this))}),this._trigger("enable",null,{instance:this.options.model,entityElement:this.element}),_.each(this.domService.views,function(e){if(e instanceof t.vie.view.Collection&&t.options.model===e.owner){var n=e.collection.predicate,r=t.enableCollection({model:t.options.model,collection:e.collection,property:n,definition:t.getAttributeDefinition(n),view:e,element:e.el,vie:t.vie,editableOptions:t.options});t.options.collections.push(r)}})},disable:function(){var t=this;e.each(this.options.editables,function(n,r){t.disableEditor({widget:t,editable:r,entity:t.options.model,element:e(this)})}),this.options.editables=[],e.each(this.options.collections,function(e,n){t.disableCollection({widget:t,model:t.options.model,element:n,vie:t.vie,editableOptions:t.options})}),this.options.collections=[],this._trigger("disable",null,{instance:this.options.model,entityElement:this.element})},getElementPredicate:function(e){return this.domService.getElementPredicate(e)},_enableProperty:function(e){var t=this,n=this.getElementPredicate(e);if(!n)return!0;if(this.options.model.get(n)instanceof Array)return!0;var r=this.enableEditor({widget:this,element:e,entity:this.options.model,property:n,vie:this.vie,modified:function(r){var i={};i[n]=r,t.options.model.set(i,{silent:!0}),t._trigger("changed",null,{property:n,instance:t.options.model,element:e,entityElement:t.element})},activated:function(){t._trigger("activated",null,{property:n,instance:t.options.model,element:e,entityElement:t.element})},deactivated:function(){t._trigger("deactivated",null,{property:n,instance:t.options.model,element:e,entityElement:t.element})}});r&&this._trigger("enableproperty",null,{editable:r,property:n,instance:this.options.model,element:e,entityElement:this.element}),this.options.editables.push(r)},_editorName:function(e){if(this.options.widgets[e.property]!==t)return this.options.widgets[e.property];var n="default",r=this.getAttributeDefinition(e.property);return r&&(n=r.range[0]),this.options.widgets[n]!==t?this.options.widgets[n]:this.options.widgets["default"]},_editorWidget:function(e){return this.options.editors[e].widget},_editorOptions:function(e){return this.options.editors[e].options},getAttributeDefinition:function(e){var t=this.options.model.get("@type");if(!t)return;if(!t.attributes)return;return t.attributes.get(e)},enableEditor:function(t){var n=this._editorName(t);if(n===null)return;var r=this._editorWidget(n);t.editorOptions=this._editorOptions(n),t.toolbarState=this.options.toolbarState,t.disabled=!1;if(typeof e(t.element)[r]!="function")throw new Error(r+" widget is not available");return e(t.element)[r](t),e(t.element).data("createWidgetName",r),e(t.element)},disableEditor:function(t){var n=e(t.element).data("createWidgetName");t.disabled=!0,n&&(e(t.element)[n](t),e(t.element).removeClass("ui-state-disabled"))},collectionWidgetName:function(e){if(this.options.collectionWidgets[e.property]!==t)return this.options.collectionWidgets[e.property];var n="default",r=this.getAttributeDefinition(e.property);return r&&(n=r.range[0]),this.options.collectionWidgets[n]!==t?this.options.collectionWidgets[n]:this.options.collectionWidgets["default"]},enableCollection:function(t){var n=this.collectionWidgetName(t);if(n===null)return;t.disabled=!1;if(typeof e(t.element)[n]!="function")throw new Error(n+" widget is not available");return e(t.element)[n](t),e(t.element).data("createCollectionWidgetName",n),e(t.element)},disableCollection:function(t){var n=e(t.element).data("createCollectionWidgetName");if(n===null)return;t.disabled=!0,n&&(e(t.element)[n](t),e(t.element).removeClass("ui-state-disabled"))}})}(jQuery),function(e,t){"use strict";e.widget("Create.editWidget",{options:{disabled:!1,vie:null},enable:function(){this.element.attr("contenteditable","true")},disable:function(e){this.element.attr("contenteditable","false")},_create:function(){this._registerWidget(),this._initialize()},_init:function(){if(this.options.disabled){this.disable();return}this.enable()},_initialize:function(){var t=this,n=this.element.html();this.element.bind("blur keyup paste",function(r){if(t.options.disabled)return;var i=e(this).html();n!==i&&(n=i,t.options.modified(i))})},_registerWidget:function(){this.element.data("createWidgetName",this.widgetName)}})}(jQuery),function(e,t){"use strict";e.widget("Create.alohaWidget",e.Create.editWidget,{enable:function(){this._initialize(),this.options.disabled=!1},disable:function(){Aloha.jQuery(this.options.element.get(0)).mahalo(),this.options.disabled=!0},_initialize:function(){var e=this.options,t,n=Aloha.jQuery(e.element.get(0)).aloha();_.each(Aloha.editables,function(e){e.obj.get(0)===n.get(0)&&(t=e)});if(!t)return;t.vieEntity=e.entity,Aloha.bind("aloha-editable-activated",function(n,r){if(r.editable!==t)return;e.activated()}),Aloha.bind("aloha-editable-deactivated",function(n,r){if(r.editable!==t)return;e.deactivated()}),Aloha.bind("aloha-smart-content-changed",function(n,r){if(r.editable!==t)return;if(!r.editable.isModified())return!0;e.modified(r.editable.getContents()),r.editable.setUnmodified()})}})}(jQuery),function(e,t){"use strict";e.widget("Create.halloWidget",e.Create.editWidget,{options:{editorOptions:{},disabled:!0,toolbarState:"full",vie:null,entity:null},enable:function(){e(this.element).hallo({editable:!0}),this.options.disabled=!1},disable:function(){e(this.element).hallo({editable:!1}),this.options.disabled=!0},_initialize:function(){e(this.element).hallo(this.getHalloOptions());var t=this;e(this.element).bind("halloactivated",function(e,n){t.options.activated()}),e(this.element).bind("hallodeactivated",function(e,n){t.options.deactivated()}),e(this.element).bind("hallomodified",function(e,n){t.options.modified(n.content),n.editable.setUnmodified()}),e(document).bind("midgardtoolbarstatechange",function(e,n){if(n.display===t.options.toolbarState)return;t.options.toolbarState=n.display;var r=t.getHalloOptions();t.element.hallo("changeToolbar",r.parentElement,r.toolbar,!0)})},getHalloOptions:function(){var t={plugins:{halloformat:{},halloblock:{},hallolists:{},hallolink:{},halloimage:{entity:this.options.entity}},buttonCssClass:"create-ui-btn-small",placeholder:"["+this.options.property+"]"};return typeof this.element.annotate=="function"&&this.options.vie.services.stanbol&&(t.plugins.halloannotate={vie:this.options.vie}),this.options.toolbarState==="full"?(t.parentElement=e(".create-ui-toolbar-dynamictoolarea .create-ui-tool-freearea"),t.toolbar="halloToolbarFixed"):(t.parentElement="body",t.toolbar="halloToolbarContextual"),_.extend(t,this.options.editorOptions)}})}(jQuery),function(e,t){"use strict";e.widget("Create.redactorWidget",e.Create.editWidget,{editor:null,options:{editorOptions:{},disabled:!0},enable:function(){e(this.element).redactor(this.getRedactorOptions()),this.options.disabled=!1},disable:function(){e(this.element).destroyEditor(),this.options.disabled=!0},_initialize:function(){var t=this;e(this.element).bind("focus",function(e){t.options.activated()})},getRedactorOptions:function(){var t=this,n={keyupCallback:function(n,r){t.options.modified(e(t.element).getCode())},execCommandCallback:function(n,r){t.options.modified(e(t.element).getCode())}};return _.extend(t.options.editorOptions,n)}})}(jQuery),function(e,t){"use strict";var n=[],r=function(t,r){var i={class_prefix:"midgardNotifications",timeout:3e3,auto_show:!0,body:"",bindTo:null,gravity:"T",effects:{onShow:function(e,t){e.animate({opacity:"show"},600,t)},onHide:function(e,t){e.animate({opacity:"hide"},600,t)}},actions:[],callbacks:{}},s={},o={},u=null,a=null,f=null,l=t,c=null,h={constructor:function(e){s=_.extend(i,e||{}),o={container:s.class_prefix+"-container",item:{wrapper:s.class_prefix+"-item",arrow:s.class_prefix+"-arrow",disregard:s.class_prefix+"-disregard",content:s.class_prefix+"-content",actions:s.class_prefix+"-actions",action:s.class_prefix+"-action"}},this._generate()},getId:function(){return a},getElement:function(){return u},_generate:function(){var t=this,r,i,f=null;u=r=e('<div class="'+o.item.wrapper+'-outer"/>'),r.css({display:"none"}),i=e('<div class="'+o.item.wrapper+'-inner"/>'),i.appendTo(r);if(s.bindTo){r.addClass(o.item.wrapper+"-binded");var h=e('<div class="'+o.item.arrow+'"/>');h.appendTo(r)}else r.addClass(o.item.wrapper+"-normal");f=e('<div class="'+o.item.content+'"/>'),f.html(s.body),f.appendTo(i);if(s.actions.length){var p=e('<div class="'+o.item.actions+'"/>');p.appendTo(i),e.each(s.actions,function(n,r){var i=e('<button name="'+r.name+'" class="button-'+r.name+'">'+r.label+"</button>").button();i.bind("click",function(e){c?r.cb(e,c,t):r.cb(e,t)}),r.className&&i.addClass(r.className),p.append(i)})}u.bind("click",function(e){s.callbacks.onClick?s.callbacks.onClick(e,t):c||t.close()}),s.auto_show&&this.show(),this._setPosition(),a=n.push(this),l.append(u)},_calculatePositionForGravity:function(e,t,n,r){e.find("."+o.item.arrow).addClass(o.item.arrow+"_"+t);switch(t){case"TL":return{left:n.left,top:n.top+n.height+"px"};case"TR":return{left:n.left+n.width-r.width+"px",top:n.top+n.height+"px"};case"BL":return{left:n.left+"px",top:n.top-r.height+"px"};case"BR":return{left:n.left+n.width-r.width+"px",top:n.top-r.height+"px"};case"LT":return{left:n.left+n.width+"px",top:n.top+"px"};case"LB":return{left:n.left+n.width+"px",top:n.top+n.height-r.height+"px"};case"RT":return{left:n.left-r.width+"px",top:n.top+"px"};case"RB":return{left:n.left-r.width+"px",top:n.top+n.height-r.height+"px"};case"T":return{left:n.left+n.width/2-r.width/2+"px",top:n.top+n.height+"px"};case"R":return{left:n.left-r.width+"px",top:n.top+n.height/2-r.height/2+"px"};case"B":return{left:n.left+n.width/2-r.width/2+"px",top:n.top-r.height+"px"};case"L":return{left:n.left+n.width+"px",top:n.top+n.height/2-r.height/2+"px"}}},_isFixed:function(e){if(e===document)return!1;if(e.css("position")==="fixed")return!0;var t=e.offsetParent();return t.get(0)===e.get(0)?!1:this._isFixed(t)},_setPosition:function(){var t;if(s.bindTo){var r={width:u.width()?u.width():280,height:u.height()?u.height():109};f=e(s.bindTo);var i={},o={width:f.outerWidth(),height:f.outerHeight()};this._isFixed(f)?(i.position="fixed",o.left=f.offset().left,o.top=f.position().top):(i.position="absolute",o.left=f.offset().left,o.top=f.offset().top),t=this._calculatePositionForGravity(u,s.gravity,o,r),i.top=t.top,i.left=t.left,u.css(i);return}s.position||(s.position="top right");var a=e(".create-ui-toolbar-wrapper").outerHeight(!0)+6;t={position:"fixed"};var l,c=function(t){var n=0;return e.each(t,function(e,t){if(!t)return;n+=t.getElement().height()}),n};s.position.match(/top/)&&(t.top=a+c(n)+"px"),s.position.match(/bottom/)&&(t.bottom=n.length-1*l.height()+l.height()+10+"px"),s.position.match(/right/)&&(t.right="20px"),s.position.match(/left/)&&(t.left="20px"),u.css(t)},show:function(){var t=this,n,r,i,o,a,f;s.callbacks.beforeShow&&s.callbacks.beforeShow(t);if(s.bindTo){var l=e(s.bindTo);n=e(window).scrollTop(),r=e(window).scrollTop()+e(window).height(),o=parseFloat(u.offset().top,10),a=l.offset().top,f=l.outerHeight(),a<o&&(o=a),i=parseFloat(u.offset().top,10)+u.height(),a+f>i&&(i=a+f)}s.timeout>0&&!s.actions.length&&window.setTimeout(function(){t.close()},s.timeout),s.bindTo&&(o<n||o>r)||i<n||i>r?e("html, body").stop().animate({scrollTop:o},500,"easeInOutExpo",function(){s.effects.onShow(u,function(){s.callbacks.afterShow&&s.callbacks.afterShow(t)})}):s.effects.onShow(u,function(){s.callbacks.afterShow&&s.callbacks.afterShow(t)})},close:function(){var e=this;s.callbacks.beforeClose&&s.callbacks.beforeClose(e),s.effects.onHide(u,function(){s.callbacks.afterClose&&s.callbacks.afterClose(e),e.destroy()})},destroy:function(){var t=this;e.each(n,function(e,r){r&&r.getId()==t.getId()&&delete n[e]}),e(u).remove()},setStory:function(e){c=e},setName:function(e){u.addClass(o.item.wrapper+"-custom-"+e),this.name=e}};return h.constructor(r),delete h.constructor,h},i=function(t,n){var i={},s={},o={},u={},a=null,f=null,l=null,c=null,h={constructor:function(e){s=_.extend(i,e||{})},setStoryline:function(t){var n={content:"",actions:[],show_actions:!0,notification:{},back:null,back_label:null,forward:null,forward_label:null,beforeShow:null,afterShow:null,beforeClose:null,afterClose:null};o={},c=null,a=null,f=null,l=null;var r=this;return e.each(t,function(t,i){var s=e.extend({},n,i);s.name=t;var u=e.extend({},n.notification,i.notification||{});u.body=s.content,u.auto_show=!1,s.actions.length&&(u.delay=0),u.callbacks={beforeShow:function(e){s.beforeShow&&s.beforeShow(e,r)},afterShow:function(e){s.afterShow&&s.afterShow(e,r)},beforeClose:function(e){s.beforeClose&&s.beforeClose(e,r)},afterClose:function(e){s.afterClose&&s.afterClose(e,r),a=e.name}},u.actions=[];if(s.show_actions){if(s.back){var c=s.back_label;c||(c="Back"),u.actions.push({name:"back",label:c,cb:function(e,t,n){t.previous()}})}if(s.forward){var h=s.forward_label;h||(h="Back"),u.actions.push({name:"forward",label:h,cb:function(e,t,n){t.next()}})}s.actions.length&&e.each(s.actions,function(e,t){u.actions.push(s.actions[e])})}f||(f=t),l=t,s.notification=u,o[t]=s}),o},start:function(){this._showNotification(o[f])},stop:function(){c.close(),c=null,a=null},next:function(){c.close();if(o[c.name].forward){var e=o[c.name].forward;this._showNotification(o[e])}else this._showNotification(o[l])},previous:function(){if(a){c.close();if(o[c.name].back){var e=o[c.name].back;this._showNotification(o[e])}else this._showNotification(o[a])}else this.stop()},_showNotification:function(t){return c=new r(e("body"),t.notification),c.setStory(this),c.setName(t.name),c.show(),c}};return h.constructor(t),delete h.constructor,n&&h.setStoryline(n),h},s={start:{content:"Welcome to CreateJS tutorial!",forward:"toolbar_toggle",forward_label:"Start tutorial",actions:[{name:"quit",label:"Quit",cb:function(e,t,n){t.stop()}}]},toolbar_toggle:{content:"This is the CreateJS toolbars toggle button.<br />You can hide and show the full toolbar by clicking here.<br />Try it now.",forward:"edit_button",show_actions:!1,afterShow:function(t,n){e("body").bind("midgardtoolbarstatechange",function(t,r){r.display=="full"&&(n.next(),e("body").unbind("midgardtoolbarstatechange"))})},notification:{bindTo:"#midgard-bar-hidebutton",timeout:0,gravity:"TL"}},edit_button:{content:"This is the edit button.<br />Try it now.",show_actions:!1,afterShow:function(t,n){e("body").bind("midgardcreatestatechange",function(t,r){r.state=="edit"&&(n.next(),e("body").unbind("midgardcreatestatechange"))})},notification:{bindTo:".ui-button[for=midgardcreate-edit]",timeout:0,gravity:"TL"}},end:{content:"Thank you for watching!<br />Happy content editing times await you!"}};e.widget("Midgard.midgardNotifications",{options:{notification_defaults:{class_prefix:"midgardNotifications",position:"top right"}},_create:function(){this.classes={container:this.options.notification_defaults.class_prefix+"-container"},e("."+this.classes.container,this.element).length?(this.container=e("."+this.classes.container,this.element),this._parseFromDOM()):(this.container=e('<div class="'+this.classes.container+'" />'),this.element.append(this.container))},destroy:function(){this.container.remove(),e.Widget.prototype.destroy.call(this)},_init:function(){},_parseFromDOM:function(e){},showStory:function(e,t){var n=new i(e,t);return n.start(),n},create:function(t){t=e.extend({},this.options.notification_defaults,t||{});var n=new r(this.container,t);return n.show(),n},showTutorial:function(){this.showStory({},s)}})}(jQuery),function(e,t){"use strict";e.widget("Midgard.midgardStorage",{saveEnabled:!0,options:{localStorage:!1,removeLocalstorageOnIgnore:!0,vie:null,url:"",autoSave:!1,autoSaveInterval:5e3,saveReferencedNew:!1,saveReferencedChanged:!1,editableNs:"midgardeditable",editSelector:"#midgardcreate-edit a",saveSelector:"#midgardcreate-save",localize:function(e,t){return window.midgardCreate.localize(e,t)},language:null},_create:function(){var t=this;this.changedModels=[],window.localStorage&&(this.options.localStorage=!0),this.vie=this.options.vie,this.vie.entities.bind("add",function(e){e.url=t.options.url,e.toJSON=e.toJSONLD}),e(t.options.saveSelector).click(function(){t.saveRemote({success:function(){e(t.options.saveSelector).button({disabled:!0})},error:function(){}})}),t._bindEditables(),t.options.autoSave&&t._autoSave()},_autoSave:function(){var t=this;t.saveEnabled=!0;var n=function(){if(!t.saveEnabled)return;if(t.changedModels.length===0)return;t.saveRemote({success:function(){e(t.options.saveSelector).button({disabled:!0})},error:function(){},silent:!0})},r=window.setInterval(n,t.options.autoSaveInterval);this.element.bind("startPreventSave",function(){r&&(window.clearInterval(r),r=null),t.disableSave()}),this.element.bind("stopPreventSave",function(){r||(r=window.setInterval(n,t.options.autoSaveInterval)),t.enableSave()})},enableSave:function(){this.saveEnabled=!0},disableSave:function(){this.saveEnabled=!1},_bindEditables:function(){var t=this;this.restorables=[];var n;t.element.bind(t.options.editableNs+"changed",function(n,r){_.indexOf(t.changedModels,r.instance)===-1&&t.changedModels.push(r.instance),t._saveLocal(r.instance),e(t.options.saveSelector).button({disabled:!1})}),t.element.bind(t.options.editableNs+"disable",function(n,r){t._restoreLocal(r.instance),e(t.options.saveSelector).hide()}),t.element.bind(t.options.editableNs+"enable",function(n,r){e(t.options.saveSelector).button({disabled:!0}),e(t.options.saveSelector).show(),r.instance._originalAttributes||(r.instance._originalAttributes=_.clone(r.instance.attributes)),!r.instance.isNew()&&t._checkLocal(r.instance)&&t.restorables.push(r.instance)}),t.element.bind("midgardcreatestatechange",function(e,r){if(r.state==="browse"||t.restorables.length===0){t.restorables=[],n&&n.close();return}n=t.checkRestore()}),t.element.bind("midgardstorageloaded",function(n,r){_.indexOf(t.changedModels,r.instance)===-1&&t.changedModels.push(r.instance),e(t.options.saveSelector).button({disabled:!1})})},checkRestore:function(){var t=this;if(t.restorables.length===0)return;var n;t.restorables.length===1?n=_.template(t.options.localize("localModification",t.options.language),{label:t.restorables[0].getSubjectUri()}):n=_.template(t.options.localize("localModifications",t.options.language),{number:t.restorables.length});var r=e("body").midgardNotifications("create",{bindTo:t.options.editSelector,gravity:"TR",body:n,timeout:0,actions:[{name:"restore",label:t.options.localize("Restore",t.options.language),cb:function(){_.each(t.restorables,function(e){t._readLocal(e)}),t.restorables=[]},className:"create-ui-btn"},{name:"ignore",label:t.options.localize("Ignore",t.options.language),cb:function(e,n){t.options.removeLocalstorageOnIgnore&&_.each(t.restorables,function(e){t._removeLocal(e)}),n.close(),t.restorables=[]},className:"create-ui-btn"}]});return r},saveRemote:function(t){var n=this;if(n.changedModels.length===0)return;n._trigger("save",null,{models:n.changedModels});var r,i=n.changedModels.length;i>1?r=_.template(n.options.localize("saveSuccessMultiple",n.options.language),{number:i}):r=_.template(n.options.localize("saveSuccess",n.options.language),{label:n.changedModels[0].getSubjectUri()}),n.disableSave(),_.each(n.changedModels,function(s){_.each(s.attributes,function(e,t){if(!e||!e.isCollection)return;e.each(function(e){if(n.changedModels.indexOf(e)!==-1)return;if(e.isNew()&&n.options.saveReferencedNew)return e.save();if(e.hasChanged()&&n.options.saveReferencedChanged)return e.save()})}),s.save(null,_.extend({},t,{success:function(){s._originalAttributes=_.clone(s.attributes),n._removeLocal(s),window.setTimeout(function(){n.changedModels.splice(n.changedModels.indexOf(s),1)},0),i--,i<=0&&(n._trigger("saved",null,{}),t.success(),e("body").midgardNotifications("create",{body:r}),n.enableSave())},error:function(r,i){t.error(),e("body").midgardNotifications("create",{body:_.template(n.options.localize("saveError",n.options.language),{error:i.responseText||""}),timeout:0}),n._trigger("error",null,{instance:s})}}))})},_saveLocal:function(e){if(!this.options.localStorage)return;if(e.isNew()){if(!e.primaryCollection)return;return this._saveLocalReferences(e.primaryCollection.subject,e.primaryCollection.predicate,e)}window.localStorage.setItem(e.getSubjectUri(),JSON.stringify(e.toJSONLD()))},_getReferenceId:function(e,t){return e.id+":"+t},_saveLocalReferences:function(e,t,n){if(!this.options.localStorage)return;if(!e||!t)return;var r=this,i=e+":"+t,s=n.toJSONLD();if(window.localStorage.getItem(i)){var o=JSON.parse(window.localStorage.getItem(i)),u=_.pluck(o,"@").indexOf(s["@"]);u!==-1?o[u]=s:o.push(s),window.localStorage.setItem(i,JSON.stringify(o));return}
+window.localStorage.setItem(i,JSON.stringify([s]))},_checkLocal:function(e){if(!this.options.localStorage)return!1;var t=window.localStorage.getItem(e.getSubjectUri());return t?!0:!1},_readLocal:function(e){if(!this.options.localStorage)return;var t=window.localStorage.getItem(e.getSubjectUri());if(!t)return;e._originalAttributes||(e._originalAttributes=_.clone(e.attributes));var n=JSON.parse(t),r=this.vie.entities.addOrUpdate(n,{overrideAttributes:!0});this._trigger("loaded",null,{instance:r})},_readLocalReferences:function(e,t,n){if(!this.options.localStorage)return;var r=this._getReferenceId(e,t),i=window.localStorage.getItem(r);if(!i)return;n.add(JSON.parse(i))},_restoreLocal:function(e){var t=this;if(!e)return;_.each(e.attributes,function(e,n){if(e instanceof t.vie.Collection){var r=[];e.forEach(function(e){e.isNew()&&r.push(e)}),e.remove(r)}});if(!e.changedAttributes()){e._originalAttributes&&e.set(e._originalAttributes);return}e.set(e.previousAttributes())},_removeLocal:function(e){if(!this.options.localStorage)return;window.localStorage.removeItem(e.getSubjectUri())}})}(jQuery),function(e,t){"use strict";e.widget("Midgard.midgardTags",{enhanced:!1,options:{vie:null,entity:null,element:null,entityElement:null,parentElement:".create-ui-tool-metadataarea",predicate:"skos:related",templates:{button:'<button class="create-ui-btn"><i class="icon-<%= icon %>"></i> <%= label %></button>',contentArea:'<div class="dropdown-menu"></div>',tags:'<div class="create-ui-tags <%= type %>Tags"><h3><%= label %></h3><input type="text" class="tags" value="" /></div>'},localize:function(e,t){return window.midgardCreate.localize(e,t)},language:null},_init:function(){var t=this;this.vie=this.options.vie,this.entity=this.options.entity,this.element=this.options.element,e(this.options.entityElement).bind("midgardeditableactivated",function(e,n){if(n.instance!==t.options.entity)return;t._renderWidget(),t.loadTags()}),e(this.options.entityElement).bind("midgardeditablechanged",function(e,n){if(n.instance!==t.options.entity)return;t.enhanced=!1}),this._listenAnnotate(this.options.entityElement)},_normalizeSubject:function(e){return this.entity.isReference(e)?e:(e.substr(0,7)!=="http://"&&(e="urn:tag:"+e),e=this.entity.toReference(e),e)},_tagLabel:function(e){return e=this.entity.fromReference(e),e.substr(0,8)==="urn:tag:"&&(e=e.substr(8,e.length-1)),e.substring(0,7)=="http://"&&(e=e.substr(e.lastIndexOf("/")+1,e.length-1),e=e.replace(/_/g," ")),e},addTag:function(e,n,r){n===t&&(n=this._tagLabel(e)),e=this._normalizeSubject(e),r&&!this.entity.isReference(r)&&(r=this.entity.toReference(r));var i=this.vie.entities.addOrUpdate({"@subject":e,"rdfs:label":n,"@type":r}),s=this.options.entity.get(this.options.predicate);s?s.isCollection||(s=new this.vie.Collection(_.map(s,function(e){return e.isEntity?e:{"@subject":e}})),s.vie=this.options.vie,this.options.entity.set(this.options.predicate,s)):(s=new this.vie.Collection,s.vie=this.options.vie,this.options.entity.set(this.options.predicate,s)),s.addOrUpdate(i),this.options.entityElement.trigger("midgardeditablechanged",{instance:this.options.entity})},removeTag:function(e){var t=this.options.entity.get(this.options.predicate);if(!t)return;e=this._normalizeSubject(e);var n=t.get(e);if(!n)return;t.remove(e),this.options.entityElement.trigger("midgardeditablechanged",{instance:this.options.entity})},_listenAnnotate:function(e){var t=this;e.bind("annotateselect",function(e,n){t.addTag(n.linkedEntity.uri,n.linkedEntity.label,n.linkedEntity.type[0])}),e.bind("annotateremove",function(e,n){t.removeTag(n.linkedEntity.uri)})},_prepareEditor:function(t){var n=e(_.template(this.options.templates.contentArea,{})),r=e(_.template(this.options.templates.tags,{type:"article",label:this.options.localize("Item tags",this.options.language)})),i=e(_.template(this.options.templates.tags,{type:"suggested",label:this.options.localize("Suggested tags",this.options.language)}));e("input",r).attr("id","articleTags-"+this.entity.cid),e("input",i).attr("id","suggestedTags-"+this.entity.cid),n.append(r),n.append(i),n.hide();var s=t.position();return n.css("position","absolute"),n.css("left",s.left),n},_renderWidget:function(){var t=this,n=this.entity.getSubject(),r=e(_.template(this.options.templates.button,{icon:"tags",label:this.options.localize("Tags",this.options.language)})),i=e(this.options.parentElement);i.empty(),i.append(r),i.show();var s=this._prepareEditor(r);r.after(s),this.articleTags=e(".articleTags input",s).tagsInput({width:"auto",height:"auto",onAddTag:function(e){t.addTag(e)},onRemoveTag:function(e){t.removeTag(e)},defaultText:this.options.localize("add a tag",this.options.language)});var o=function(){var n=e.trim(e(this).text());t.articleTags.addTag(n),t.suggestedTags.removeTag(n)};this.suggestedTags=e(".suggestedTags input",s).tagsInput({width:"auto",height:"auto",interactive:!1,onAddTag:function(t){e(".suggestedTags .tag span",s).unbind("click",o),e(".suggestedTags .tag span",s).bind("click",o)},onRemoveTag:function(t){e(".suggestedTags .tag span",s).unbind("click",o),e(".suggestedTags .tag span",s).bind("click",o)},remove:!1}),r.bind("click",function(){s.toggle()})},loadTags:function(){var t=this,n=this.entity.get(this.options.predicate);n&&(_.isString(n)?t.articleTags.addTag(t._tagLabel(n)):n.isCollection?n.each(function(e){t.articleTags.addTag(e.get("rdfs:label"))}):_.each(n,function(e){t.articleTags.addTag(t._tagLabel(e))})),this.vie.services.stanbol?t.enhance():e(".suggestedTags",t.element).hide()},_getLabelLang:function(e){if(!_.isArray(e))return null;var t;return _.each(e,function(e){e["@language"]==="en"&&(t=e["@value"])}),t},_addEnhancement:function(e){if(!e.isEntity)return;var t=this._getLabelLang(e.get("rdfs:label"));if(!t)return;var n=this.options.entity.get(this.options.predicate);if(n&&n.isCollection&&n.indexOf(e)!==-1)return;this.suggestedTags.addTag(t)},enhance:function(){if(this.enhanced)return;this.enhanced=!0;var t=this;this.vie.analyze({element:e("[property]",this.options.entityElement)}).using(["stanbol"]).execute().success(function(e){_.each(e,function(e){t._addEnhancement(e)})}).fail(function(e){})}})}(jQuery),function(e,t){"use strict";e.widget("Midgard.midgardToolbar",{options:{display:"full",templates:{minimized:'<div class="create-ui-logo"><a class="create-ui-toggle" id="create-ui-toggle-toolbar"></a></div>',full:'<div class="create-ui-toolbar-wrapper"><div class="create-ui-toolbar-toolarea"><%= dynamic %><%= status %></div></div>',toolcontainer:'<div class="create-ui-toolbar-<%= name %>toolarea"><ul class="create-ui-<%= name %>tools"><%= content %></ul></div>',toolarea:'<li class="create-ui-tool-<%= name %>area"></li>'}},_create:function(){this.element.append(this._getMinimized()),this.element.append(this._getFull());var t=this;e(".create-ui-toggle",this.element).click(function(){t.options.display==="full"?t.setDisplay("minimized"):t.setDisplay("full")}),e(this.element).bind("midgardcreatestatechange",function(e,n){n.state=="browse"&&(t._clearWorkflows(),t._clearMetadata())}),e(this.element).bind("midgardworkflowschanged",function(n,r){t._clearWorkflows(),r.workflows.length&&r.workflows.each(function(n){var i=e("body").data().midgardWorkflows.prepareItem(r.instance,n,function(e,n){t._clearWorkflows();if(e)return});e(".create-ui-tool-workflowarea",t.element).append(i)})})},_init:function(){this.setDisplay(this.options.display)},setDisplay:function(e){if(e===this.options.display)return;e==="minimized"?(this.hide(),this.options.display="minimized"):(this.show(),this.options.display="full"),this._trigger("statechange",null,this.options)},hide:function(){e("div.create-ui-toolbar-wrapper").fadeToggle("fast","linear")},show:function(){e("div.create-ui-toolbar-wrapper").fadeToggle("fast","linear")},_getMinimized:function(){return e(_.template(this.options.templates.minimized,{}))},_getFull:function(){return e(_.template(this.options.templates.full,{dynamic:_.template(this.options.templates.toolcontainer,{name:"dynamic",content:_.template(this.options.templates.toolarea,{name:"metadata"})+_.template(this.options.templates.toolarea,{name:"workflow"})+_.template(this.options.templates.toolarea,{name:"free"})}),status:_.template(this.options.templates.toolcontainer,{name:"status",content:""})}))},_clearWorkflows:function(){e(".create-ui-tool-workflowarea",this.element).empty()},_clearMetadata:function(){e(".create-ui-tool-metadataarea",this.element).empty()}})}(jQuery),function(e,t){"use strict";e.widget("Midgard.midgardWorkflows",{options:{url:function(e){},templates:{button:'<button class="create-ui-btn" id="<%= id %>"><%= label %></button>'},renderers:{button:function(t,n,r,i){var s="midgardcreate-workflow_"+n.get("name"),o=e(_.template(this.options.templates.button,{id:s,label:n.get("label")})).button();return o.bind("click",function(e){r(t,n,i)}),o}},action_types:{backbone_save:function(e,t,n){var r=e.url,i=e.clone();i.url=r;var s=t.get("action");s.url&&(e.url=s.url),i.save(null,{success:function(t){e.url=r,e.change(),n(null,e)},error:function(t,i){e.url=r,e.change(),n(i,e)}})},backbone_destroy:function(e,t,n){var r=e.url,i=e.clone();i.url=r;var s=t.get("action");s.url&&(e.url=s.url),e.destroy({success:function(t){e.url=r,e.change(),n(null,t)},error:function(t,i){e.url=r,e.change(),n(i,e)}})},http:function(t,n,r){var i=n.get("action");if(!i.url)return r("No action url defined!");var s={};i.http&&(s=i.http);var o=e.extend({url:i.url,type:"POST",data:t.toJSON(),success:function(){t.fetch({success:function(e){r(null,e)},error:function(e,t){r(t,e)}})}},s);e.ajax(o)}}},_init:function(){this._renderers={},this._action_types={},this._parseRenderersAndTypes(),this._last_instance=null,this.ModelWorkflowModel=Backbone.Model.extend({defaults:{name:"",label:"",type:"button",action:{type:"backbone_save"}}}),this.workflows={};var t=this;e(this.element).bind("midgardeditableactivated",function(e,n){t._fetchWorkflows(n.instance)})},_fetchWorkflows:function(e){var t=this;if(e.isNew()){t._trigger("changed",null,{instance:e,workflows:[]});return}if(t._last_instance==e){t.workflows[e.cid]&&t._trigger("changed",null,{instance:e,workflows:t.workflows[e.cid]});return}t._last_instance=e;if(t.workflows[e.cid]){t._trigger("changed",null,{instance:e,workflows:t.workflows[e.cid]});return}if(t.options.url)t._fetchModelWorkflows(e);else{var n=new(t._generateCollectionFor(e))([],{});t._trigger("changed",null,{instance:e,workflows:n})}},_parseRenderersAndTypes:function(){var t=this;e.each(this.options.renderers,function(e,n){t.setRenderer(e,n)}),e.each(this.options.action_types,function(e,n){t.setActionType(e,n)})},setRenderer:function(e,t){this._renderers[e]=t},getRenderer:function(e){return this._renderers[e]?this._renderers[e]:!1},setActionType:function(e,t){this._action_types[e]=t},getActionType:function(e){return this._action_types[e]},prepareItem:function(e,t,n){var r=this,i=this.getRenderer(t.get("type")),s=this.getActionType(t.get("action").type);return i.call(this,e,t,s,function(i,s){delete r.workflows[e.cid],r._last_instance=null,t.get("action").type!=="backbone_destroy"&&r._fetchModelWorkflows(e),n(i,s)})},_generateCollectionFor:function(e){var t={model:this.ModelWorkflowModel};return this.options.url&&(t.url=this.options.url(e)),Backbone.Collection.extend(t)},_fetchModelWorkflows:function(e){if(e.isNew())return;var t=this;t.workflows[e.cid]=new(this._generateCollectionFor(e))([],{}),t.workflows[e.cid].fetch({success:function(n){t.workflows[e.cid].reset(n.models),t._trigger("changed",null,{instance:e,workflows:t.workflows[e.cid]})},error:function(e,t){}})}})}(jQuery),window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.bg={Save:"Запази",Saving:"Запазване",Cancel:"Откажи",Edit:"Редактирай",localModification:'Елементът "<%= label %>" има локални модификации',localModifications:"<%= number %> елемента на тази страница имат локални модификации",Restore:"Възстанови",Ignore:"Игнорирай",saveSuccess:'Елементът "<%= label %>" беше успешно запазен',saveSuccessMultiple:"<%= number %> елемента бяха успешно запазени",saveError:"Възника грешка при запазване<br /><%= error %>","Item tags":"Етикети на елемента","Suggested tags":"Препоръчани етикети",Tags:"Етикети","add a tag":"добави етикет",Add:"Добави","Choose type to add":"Избери тип за добавяне"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.cs={Save:"Uložit",Saving:"Probíhá ukládání",Cancel:"Zrušit",Edit:"Upravit",localModification:'Blok "<%= label %>" obsahuje lokální změny',localModifications:"<%= number %> bloků na této stránce má lokální změny",Restore:"Aplikovat lokální změny",Ignore:"Zahodit lokální změny",saveSuccess:'Blok "<%= label %>" byl úspěšně uložen',saveSuccessMultiple:"<%= number %> bloků bylo úspěšně uloženo",saveError:"Při ukládání došlo k chybě<br /><%= error %>","Item tags":"Štítky bloku","Suggested tags":"Navrhované štítky",Tags:"Štítky","add a tag":"Přidat štítek",Add:"Přidat","Choose type to add":"Vyberte typ k přidání"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.da={Save:"Gem",Saving:"Gemmer",Cancel:"Annullér",Edit:"Rediger",localModification:'Element "<%= label %>" har lokale ændringer',localModifications:"<%= number %> elementer på denne side har lokale ændringer",Restore:"Gendan",Ignore:"Ignorer",saveSuccess:'Element "<%= label %>" er gemt',saveSuccessMultiple:"<%= number %> elementer er gemt",saveError:"Der opstod en fejl under lagring<br /><%= error %>","Item tags":"Element tags","Suggested tags":"Foreslåede tags",Tags:"Tags","add a tag":"tilføj et tag",Add:"Tilføj","Choose type to add":"Vælg type der skal tilføjes"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.de={Save:"Speichern",Saving:"Speichert",Cancel:"Abbrechen",Edit:"Bearbeiten",localModification:'Das Dokument "<%= label %>" auf dieser Seite hat lokale Änderungen',localModifications:"<%= number %> Dokumente auf dieser Seite haben lokale Änderungen",Restore:"Wiederherstellen",Ignore:"Ignorieren",saveSuccess:'Dokument "<%= label %>" erfolgreich gespeichert',saveSuccessMultiple:"<%= number %> Dokumente erfolgreich gespeichert",saveError:"Fehler beim Speichern<br /><%= error %>","Item tags":"Schlagwörter des Dokuments","Suggested tags":"Schlagwortvorschläge",Tags:"Schlagwörter","add a tag":"Neues Schlagwort",Add:"Hinzufügen","Choose type to add":"Typ zum Hinzufügen wählen"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.en={Save:"Save",Saving:"Saving",Cancel:"Cancel",Edit:"Edit",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<br /><%= error %>","Item tags":"Item tags","Suggested tags":"Suggested tags",Tags:"Tags","add a tag":"add a tag",Add:"Add","Choose type to add":"Choose type to add"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.es={Save:"Guardar",Saving:"Guardando",Cancel:"Cancelar",Edit:"Editar",localModification:'El elemento "<%= label %>" tiene modificaciones locales',localModifications:"<%= number %> elementos en la página tienen modificaciones locales",Restore:"Restaurar",Ignore:"Ignorar",saveSuccess:'El elemento "<%= label %>" se guardó exitosamente',saveSuccessMultiple:"<%= number %> elementos se guardaron exitosamente",saveError:"Ha ocurrido un error cuando se guardaban los datos<br /><%= error %>","Item tags":"Etiquetas de los elementos","Suggested tags":"Etiquetas sugeridas",Tags:"Etiquetas","add a tag":"añadir una etiqueta",Add:"Añadir","Choose type to add":"Escoge el tipo a añadir"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.fi={Save:"Tallenna",Saving:"Tallennetaan",Cancel:"Peruuta",Edit:"Muokkaa",localModification:'Dokumentilla "<%= label %>" on paikallisia muutoksia',localModifications:"<%= number %> dokumenttia sivulla omaa paikallisia muutoksia",Restore:"Palauta",Ignore:"Poista",saveSuccess:'Dokumentti "<%= label %>" tallennettu',saveSuccessMultiple:"<%= number %> dokumenttia tallennettu",saveError:"Virhe tallennettaessa<br /><%= error %>","Item tags":"Avainsanat","Suggested tags":"Ehdotukset",Tags:"Avainsanat","add a tag":"lisää avainsana",Add:"Lisää","Choose type to add":"Mitä haluat lisätä?"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.fr={Save:"Sauver",Saving:"En cours",Cancel:"Annuler",Edit:"Editer",localModification:'Objet "<%= label %>" sur cette page ont des modifications locales',localModifications:"<%= number %> élements sur cette page ont des modifications locales",Restore:"Récupérer",Ignore:"Ignorer",saveSuccess:'"<%= label %>" est sauvegardé avec succès',saveSuccessMultiple:"<%= number %> éléments ont été sauvegardé avec succès",saveError:"Une erreur est survenue durant la sauvegarde:<br /><%= error %>","Item tags":"Tags des objets","Suggested tags":"Tags suggérés",Tags:"Tags","add a tag":"ajouter un tag",Add:"Ajouter","Choose type to add":"Choisir le type à ajouter"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.it={Save:"Salva",Saving:"Salvataggio",Cancel:"Cancella",Edit:"Modifica",localModification:'Articolo "<%= label %>" in questa pagina hanno modifiche locali',localModifications:"<%= number %> articoli in questa pagina hanno modifiche locali",Restore:"Ripristina",Ignore:"Ignora",saveSuccess:'Articolo "<%= label %>" salvato con successo',saveSuccessMultiple:"<%= number %> articoli salvati con successo",saveError:"Errore durante il salvataggio<br /><%= error %>","Item tags":"Tags articolo","Suggested tags":"Tags suggerite",Tags:"Tags","add a tag":"Aggiungi una parola chiave",Add:"Aggiungi","Choose type to add":"Scegli il tipo da aggiungere"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.nl={Save:"Opslaan",Saving:"Bezig met opslaan",Cancel:"Annuleren",Edit:"Bewerken",localModification:'Items "<%= label %>" op de pagina heeft lokale wijzigingen',localModifications:"<%= number %> items op de pagina hebben lokale wijzigingen",Restore:"Herstellen",Ignore:"Negeren",saveSuccess:'Item "<%= label %>" succesvol opgeslagen',saveSuccessMultiple:"<%= number %> items succesvol opgeslagen",saveError:"Fout opgetreden bij het opslaan<br /><%= error %>","Item tags":"Item tags","Suggested tags":"Tag suggesties",Tags:"Tags","add a tag":"tag toevoegen",Add:"Toevoegen","Choose type to add":"Kies type om toe te voegen"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.no={Save:"Lagre",Saving:"Lagrer",Cancel:"Avbryt",Edit:"Rediger",localModification:'Element "<%= label %>" på denne siden er modifisert lokalt',localModifications:"<%= number %> elementer på denne siden er modifisert lokalt",Restore:"Gjenopprett",Ignore:"Ignorer",saveSuccess:'Element "<%= label %>" ble lagret',saveSuccessMultiple:"<%= number %> elementer ble lagret",saveError:"En feil oppstod under lagring<br /><%= error %>","Item tags":"Element-tagger","Suggested tags":"Anbefalte tagger",Tags:"Tagger","add a tag":"legg til tagg",Add:"Legg til","Choose type to add":"Velg type å legge til"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.pl={Save:"Zapisz",Saving:"Zapisuję",Cancel:"Anuluj",Edit:"Edytuj",localModification:'Artykuł "<%= label %>" posiada lokalne modyfikacje',localModifications:"<%= number %> artykułów na tej stronie posiada lokalne modyfikacje",Restore:"Przywróć",Ignore:"Ignoruj",saveSuccess:'Artykuł "<%= label %>" został poprawnie zapisany',saveSuccessMultiple:"<%= number %> artykułów zostało poprawnie zapisanych",saveError:"Wystąpił błąd podczas zapisywania<br /><%= error %>","Item tags":"Tagi artykułów","Suggested tags":"Sugerowane tagi",Tags:"Tagi","add a tag":"dodaj tag",Add:"Dodaj","Choose type to add":"Wybierz typ do dodania"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.pt_BR={Save:"Salvar",Saving:"Salvando",Cancel:"Cancelar",Edit:"Editar",localModification:'Item "<%= label %>" nesta página possuem modificações locais',localModifications:"<%= number %> itens nesta página possuem modificações locais",Restore:"Restaurar",Ignore:"Ignorar",saveSuccess:'Item "<%= label %>" salvo com sucesso',saveSuccessMultiple:"<%= number %> itens salvos com sucesso",saveError:"Erro ocorrido ao salvar<br /><%= error %>","Item tags":"Tags de item","Suggested tags":"Tags sugeridas",Tags:"Tags","add a tag":"adicionar uma tag",Add:"Adicionar","Choose type to add":"Selecione o tipo para adicionar"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.locale===undefined&&(window.midgardCreate.locale={}),window.midgardCreate.locale.ru={Save:"Сохранить",Saving:"Сохраняю",Cancel:"Отмена",Edit:"Редактировать",localModification:'В запись "<%= label %>" внесены несохранённые изменения',localModifications:"В записи на этой странице (<%= number %> шт.) внесены несохранённые изменения",Restore:"Восстановить",Ignore:"Игнорировать",saveSuccess:'Запись "<%= label %>" была успешно сохранена',saveSuccessMultiple:" Записи (<%= number %> шт.) были успешно сохранены",saveError:"Во время сохранения произошла ошибка<br /><%= error %>","Item tags":"Теги записей","Suggested tags":"Предлагаемые теги",Tags:"Теги","add a tag":"добавить тег",Add:"Добавить","Choose type to add":"Выбрать тип для добавления"},window.midgardCreate===undefined&&(window.midgardCreate={}),window.midgardCreate.localize=function(e,t){return window.midgardCreate.locale?window.midgardCreate.locale[t]&&window.midgardCreate.locale[t][e]?window.midgardCreate.locale[t][e]:window.midgardCreate.locale.en[e]?window.midgardCreate.locale.en[e]:e:e};
\ No newline at end of file
diff --git a/js/lib/underscore.js b/js/lib/underscore.js
new file mode 100644
index 0000000..ad3a39a
--- /dev/null
+++ b/js/lib/underscore.js
@@ -0,0 +1,5 @@
+//     Underscore.js 1.4.2
+//     http://underscorejs.org
+//     (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
+//     Underscore may be freely distributed under the MIT license.
+(function(){var e=this,t=e._,n={},r=Array.prototype,i=Object.prototype,s=Function.prototype,o=r.push,u=r.slice,a=r.concat,f=r.unshift,l=i.toString,c=i.hasOwnProperty,h=r.forEach,p=r.map,d=r.reduce,v=r.reduceRight,m=r.filter,g=r.every,y=r.some,b=r.indexOf,w=r.lastIndexOf,E=Array.isArray,S=Object.keys,x=s.bind,T=function(e){if(e instanceof T)return e;if(!(this instanceof T))return new T(e);this._wrapped=e};typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(exports=module.exports=T),exports._=T):e._=T,T.VERSION="1.4.2";var N=T.each=T.forEach=function(e,t,r){if(e==null)return;if(h&&e.forEach===h)e.forEach(t,r);else if(e.length===+e.length){for(var i=0,s=e.length;i<s;i++)if(t.call(r,e[i],i,e)===n)return}else for(var o in e)if(T.has(e,o)&&t.call(r,e[o],o,e)===n)return};T.map=T.collect=function(e,t,n){var r=[];return e==null?r:p&&e.map===p?e.map(t,n):(N(e,function(e,i,s){r[r.length]=t.call(n,e,i,s)}),r)},T.reduce=T.foldl=T.inject=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(d&&e.reduce===d)return r&&(t=T.bind(t,r)),i?e.reduce(t,n):e.reduce(t);N(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.reduceRight=T.foldr=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(v&&e.reduceRight===v)return r&&(t=T.bind(t,r)),arguments.length>2?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=T.keys(e);s=o.length}N(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.find=T.detect=function(e,t,n){var r;return C(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},T.filter=T.select=function(e,t,n){var r=[];return e==null?r:m&&e.filter===m?e.filter(t,n):(N(e,function(e,i,s){t.call(n,e,i,s)&&(r[r.length]=e)}),r)},T.reject=function(e,t,n){var r=[];return e==null?r:(N(e,function(e,i,s){t.call(n,e,i,s)||(r[r.length]=e)}),r)},T.every=T.all=function(e,t,r){t||(t=T.identity);var i=!0;return e==null?i:g&&e.every===g?e.every(t,r):(N(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),!!i)};var C=T.some=T.any=function(e,t,r){t||(t=T.identity);var i=!1;return e==null?i:y&&e.some===y?e.some(t,r):(N(e,function(e,s,o){if(i||(i=t.call(r,e,s,o)))return n}),!!i)};T.contains=T.include=function(e,t){var n=!1;return e==null?n:b&&e.indexOf===b?e.indexOf(t)!=-1:(n=C(e,function(e){return e===t}),n)},T.invoke=function(e,t){var n=u.call(arguments,2);return T.map(e,function(e){return(T.isFunction(t)?t:e[t]).apply(e,n)})},T.pluck=function(e,t){return T.map(e,function(e){return e[t]})},T.where=function(e,t){return T.isEmpty(t)?[]:T.filter(e,function(e){for(var n in t)if(t[n]!==e[n])return!1;return!0})},T.max=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);if(!t&&T.isEmpty(e))return-Infinity;var r={computed:-Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},T.min=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);if(!t&&T.isEmpty(e))return Infinity;var r={computed:Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o<r.computed&&(r={value:e,computed:o})}),r.value},T.shuffle=function(e){var t,n=0,r=[];return N(e,function(e){t=T.random(n++),r[n-1]=r[t],r[t]=e}),r};var k=function(e){return T.isFunction(e)?e:function(t){return t[e]}};T.sortBy=function(e,t,n){var r=k(t);return T.pluck(T.map(e,function(e,t,i){return{value:e,index:t,criteria:r.call(n,e,t,i)}}).sort(function(e,t){var n=e.criteria,r=t.criteria;if(n!==r){if(n>r||n===void 0)return 1;if(n<r||r===void 0)return-1}return e.index<t.index?-1:1}),"value")};var L=function(e,t,n,r){var i={},s=k(t);return N(e,function(t,o){var u=s.call(n,t,o,e);r(i,u,t)}),i};T.groupBy=function(e,t,n){return L(e,t,n,function(e,t,n){(T.has(e,t)?e[t]:e[t]=[]).push(n)})},T.countBy=function(e,t,n){return L(e,t,n,function(e,t,n){T.has(e,t)||(e[t]=0),e[t]++})},T.sortedIndex=function(e,t,n,r){n=n==null?T.identity:k(n);var i=n.call(r,t),s=0,o=e.length;while(s<o){var u=s+o>>>1;n.call(r,e[u])<i?s=u+1:o=u}return s},T.toArray=function(e){return e?e.length===+e.length?u.call(e):T.values(e):[]},T.size=function(e){return e.length===+e.length?e.length:T.keys(e).length},T.first=T.head=T.take=function(e,t,n){return t!=null&&!n?u.call(e,0,t):e[0]},T.initial=function(e,t,n){return u.call(e,0,e.length-(t==null||n?1:t))},T.last=function(e,t,n){return t!=null&&!n?u.call(e,Math.max(e.length-t,0)):e[e.length-1]},T.rest=T.tail=T.drop=function(e,t,n){return u.call(e,t==null||n?1:t)},T.compact=function(e){return T.filter(e,function(e){return!!e})};var A=function(e,t,n){return N(e,function(e){T.isArray(e)?t?o.apply(n,e):A(e,t,n):n.push(e)}),n};T.flatten=function(e,t){return A(e,t,[])},T.without=function(e){return T.difference(e,u.call(arguments,1))},T.uniq=T.unique=function(e,t,n,r){var i=n?T.map(e,n,r):e,s=[],o=[];return N(i,function(n,r){if(t?!r||o[o.length-1]!==n:!T.contains(o,n))o.push(n),s.push(e[r])}),s},T.union=function(){return T.uniq(a.apply(r,arguments))},T.intersection=function(e){var t=u.call(arguments,1);return T.filter(T.uniq(e),function(e){return T.every(t,function(t){return T.indexOf(t,e)>=0})})},T.difference=function(e){var t=a.apply(r,u.call(arguments,1));return T.filter(e,function(e){return!T.contains(t,e)})},T.zip=function(){var e=u.call(arguments),t=T.max(T.pluck(e,"length")),n=new Array(t);for(var r=0;r<t;r++)n[r]=T.pluck(e,""+r);return n},T.object=function(e,t){var n={};for(var r=0,i=e.length;r<i;r++)t?n[e[r]]=t[r]:n[e[r][0]]=e[r][1];return n},T.indexOf=function(e,t,n){if(e==null)return-1;var r=0,i=e.length;if(n){if(typeof n!="number")return r=T.sortedIndex(e,t),e[r]===t?r:-1;r=n<0?Math.max(0,i+n):n}if(b&&e.indexOf===b)return e.indexOf(t,n);for(;r<i;r++)if(e[r]===t)return r;return-1},T.lastIndexOf=function(e,t,n){if(e==null)return-1;var r=n!=null;if(w&&e.lastIndexOf===w)return r?e.lastIndexOf(t,n):e.lastIndexOf(t);var i=r?n:e.length;while(i--)if(e[i]===t)return i;return-1},T.range=function(e,t,n){arguments.length<=1&&(t=e||0,e=0),n=arguments[2]||1;var r=Math.max(Math.ceil((t-e)/n),0),i=0,s=new Array(r);while(i<r)s[i++]=e,e+=n;return s};var O=function(){};T.bind=function(t,n){var r,i;if(t.bind===x&&x)return x.apply(t,u.call(arguments,1));if(!T.isFunction(t))throw new TypeError;return i=u.call(arguments,2),r=function(){if(this instanceof r){O.prototype=t.prototype;var e=new O,s=t.apply(e,i.concat(u.call(arguments)));return Object(s)===s?s:e}return t.apply(n,i.concat(u.call(arguments)))}},T.bindAll=function(e){var t=u.call(arguments,1);return t.length==0&&(t=T.functions(e)),N(t,function(t){e[t]=T.bind(e[t],e)}),e},T.memoize=function(e,t){var n={};return t||(t=T.identity),function(){var r=t.apply(this,arguments);return T.has(n,r)?n[r]:n[r]=e.apply(this,arguments)}},T.delay=function(e,t){var n=u.call(arguments,2);return setTimeout(function(){return e.apply(null,n)},t)},T.defer=function(e){return T.delay.apply(T,[e,1].concat(u.call(arguments,1)))},T.throttle=function(e,t){var n,r,i,s,o,u,a=T.debounce(function(){o=s=!1},t);return function(){n=this,r=arguments;var f=function(){i=null,o&&(u=e.apply(n,r)),a()};return i||(i=setTimeout(f,t)),s?o=!0:(s=!0,u=e.apply(n,r)),a(),u}},T.debounce=function(e,t,n){var r,i;return function(){var s=this,o=arguments,u=function(){r=null,n||(i=e.apply(s,o))},a=n&&!r;return clearTimeout(r),r=setTimeout(u,t),a&&(i=e.apply(s,o)),i}},T.once=function(e){var t=!1,n;return function(){return t?n:(t=!0,n=e.apply(this,arguments),e=null,n)}},T.wrap=function(e,t){return function(){var n=[e];return o.apply(n,arguments),t.apply(this,n)}},T.compose=function(){var e=arguments;return function(){var t=arguments;for(var n=e.length-1;n>=0;n--)t=[e[n].apply(this,t)];return t[0]}},T.after=function(e,t){return e<=0?t():function(){if(--e<1)return t.apply(this,arguments)}},T.keys=S||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)T.has(e,n)&&(t[t.length]=n);return t},T.values=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push(e[n]);return t},T.pairs=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push([n,e[n]]);return t},T.invert=function(e){var t={};for(var n in e)T.has(e,n)&&(t[e[n]]=n);return t},T.functions=T.methods=function(e){var t=[];for(var n in e)T.isFunction(e[n])&&t.push(n);return t.sort()},T.extend=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]=t[n]}),e},T.pick=function(e){var t={},n=a.apply(r,u.call(arguments,1));return N(n,function(n){n in e&&(t[n]=e[n])}),t},T.omit=function(e){var t={},n=a.apply(r,u.call(arguments,1));for(var i in e)T.contains(n,i)||(t[i]=e[i]);return t},T.defaults=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]==null&&(e[n]=t[n])}),e},T.clone=function(e){return T.isObject(e)?T.isArray(e)?e.slice():T.extend({},e):e},T.tap=function(e,t){return t(e),e};var M=function(e,t,n,r){if(e===t)return e!==0||1/e==1/t;if(e==null||t==null)return e===t;e instanceof T&&(e=e._wrapped),t instanceof T&&(t=t._wrapped);var i=l.call(e);if(i!=l.call(t))return!1;switch(i){case"[object String]":return e==String(t);case"[object Number]":return e!=+e?t!=+t:e==0?1/e==1/t:e==+t;case"[object Date]":case"[object Boolean]":return+e==+t;case"[object RegExp]":return e.source==t.source&&e.global==t.global&&e.multiline==t.multiline&&e.ignoreCase==t.ignoreCase}if(typeof e!="object"||typeof t!="object")return!1;var s=n.length;while(s--)if(n[s]==e)return r[s]==t;n.push(e),r.push(t);var o=0,u=!0;if(i=="[object Array]"){o=e.length,u=o==t.length;if(u)while(o--)if(!(u=M(e[o],t[o],n,r)))break}else{var a=e.constructor,f=t.constructor;if(a!==f&&!(T.isFunction(a)&&a instanceof a&&T.isFunction(f)&&f instanceof f))return!1;for(var c in e)if(T.has(e,c)){o++;if(!(u=T.has(t,c)&&M(e[c],t[c],n,r)))break}if(u){for(c in t)if(T.has(t,c)&&!(o--))break;u=!o}}return n.pop(),r.pop(),u};T.isEqual=function(e,t){return M(e,t,[],[])},T.isEmpty=function(e){if(e==null)return!0;if(T.isArray(e)||T.isString(e))return e.length===0;for(var t in e)if(T.has(e,t))return!1;return!0},T.isElement=function(e){return!!e&&e.nodeType===1},T.isArray=E||function(e){return l.call(e)=="[object Array]"},T.isObject=function(e){return e===Object(e)},N(["Arguments","Function","String","Number","Date","RegExp"],function(e){T["is"+e]=function(t){return l.call(t)=="[object "+e+"]"}}),T.isArguments(arguments)||(T.isArguments=function(e){return!!e&&!!T.has(e,"callee")}),typeof /./!="function"&&(T.isFunction=function(e){return typeof e=="function"}),T.isFinite=function(e){return T.isNumber(e)&&isFinite(e)},T.isNaN=function(e){return T.isNumber(e)&&e!=+e},T.isBoolean=function(e){return e===!0||e===!1||l.call(e)=="[object Boolean]"},T.isNull=function(e){return e===null},T.isUndefined=function(e){return e===void 0},T.has=function(e,t){return c.call(e,t)},T.noConflict=function(){return e._=t,this},T.identity=function(e){return e},T.times=function(e,t,n){for(var r=0;r<e;r++)t.call(n,r)},T.random=function(e,t){return t==null&&(t=e,e=0),e+(0|Math.random()*(t-e+1))};var _={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"}};_.unescape=T.invert(_.escape);var D={escape:new RegExp("["+T.keys(_.escape).join("")+"]","g"),unescape:new RegExp("("+T.keys(_.unescape).join("|")+")","g")};T.each(["escape","unescape"],function(e){T[e]=function(t){return t==null?"":(""+t).replace(D[e],function(t){return _[e][t]})}}),T.result=function(e,t){if(e==null)return null;var n=e[t];return T.isFunction(n)?n.call(e):n},T.mixin=function(e){N(T.functions(e),function(t){var n=T[t]=e[t];T.prototype[t]=function(){var e=[this._wrapped];return o.apply(e,arguments),F.call(this,n.apply(T,e))}})};var P=0;T.uniqueId=function(e){var t=P++;return e?e+t:t},T.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var H=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n","	":"t","\u2028":"u2028","\u2029":"u2029"},j=/\\|'|\r|\n|\t|\u2028|\u2029/g;T.template=function(e,t,n){n=T.defaults({},n,T.templateSettings);var r=new RegExp([(n.escape||H).source,(n.interpolate||H).source,(n.evaluate||H).source].join("|")+"|$","g"),i=0,s="__p+='";e.replace(r,function(t,n,r,o,u){s+=e.slice(i,u).replace(j,function(e){return"\\"+B[e]}),s+=n?"'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'":r?"'+\n((__t=("+r+"))==null?'':__t)+\n'":o?"';\n"+o+"\n__p+='":"",i=u+t.length}),s+="';\n",n.variable||(s="with(obj||{}){\n"+s+"}\n"),s="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+s+"return __p;\n";try{var o=new Function(n.variable||"obj","_",s)}catch(u){throw u.source=s,u}if(t)return o(t,T);var a=function(e){return o.call(this,e,T)};return a.source="function("+(n.variable||"obj")+"){\n"+s+"}",a},T.chain=function(e){return T(e).chain()};var F=function(e){return this._chain?T(e).chain():e};T.mixin(T),N(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];T.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],F.call(this,n)}}),N(["concat","join","slice"],function(e){var t=r[e];T.prototype[e]=function(){return F.call(this,t.apply(this._wrapped,arguments))}}),T.extend(T.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);
\ No newline at end of file
diff --git a/js/lib/vie.js b/js/lib/vie.js
new file mode 100644
index 0000000..41279f5
--- /dev/null
+++ b/js/lib/vie.js
@@ -0,0 +1,6943 @@
+/*
+
+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('<http://dbpedia.org/property/capitalOf>')` 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 
+// <a href="Namespace.html">Namespaces documentation</a>.
+    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 
+// <a href="Type.html">Types documentation</a>.
+    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 = "<http://dbpedia.org/ontology/Person>";
+//     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 = "<http://dbpedia.org/ontology/Person>";
+//     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); 
+//          --> <http://dbpedia.org/ontology/Person>
+//     VIE.Util.toUri(scurie, ns);
+//          --> <http://dbpedia.org/ontology/Person>
+    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 = "<http://dbpedia.org/ontology/Person>";
+//     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 <a href="http://schema.org/">schema.org</a> 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("<http://schema.org/Thing>");
+        
+        var baseNSBefore = (baseNS)? baseNS : vie.namespaces.base();
+        vie.namespaces.base(baseNS);
+        
+        var datatypeMapping = {
+            'DataType': 'xsd:anyType',
+            'Boolean' : 'xsd:boolean',
+            'Date'    : 'xsd:date',
+            'DateTime': 'xsd:dateTime',
+            'Time'    : 'xsd:time',
+            'Float'   : 'xsd:float',
+            'Integer' : 'xsd:integer',
+            'Number'  : 'xsd:anySimpleType',
+            'Text'    : 'xsd:string',
+            'URL'     : 'xsd:anyURI'
+        };
+        
+        var dataTypeHelper = function (ancestors, id) {
+            var type = vie.types.add(id, [{'id' : 'value', 'range' : datatypeMapping[id]}]);
+            
+            for (var i = 0; i < ancestors.length; i++) {
+                var supertype = (vie.types.get(ancestors[i]))? vie.types.get(ancestors[i]) :
+                    dataTypeHelper.call(vie, SchemaOrg.datatypes[ancestors[i]].supertypes, ancestors[i]);
+                type.inherit(supertype);
+            }
+            return type;
+        };
+        
+        for (var dt in SchemaOrg.datatypes) {
+            if (!vie.types.get(dt)) {
+                var ancestors = SchemaOrg.datatypes[dt].supertypes;
+                dataTypeHelper.call(vie, ancestors, dt);
+            }
+        }
+
+        var metadataHelper = function (definition) {
+            var metadata = {};
+
+            if (definition.label) {
+              metadata.label = definition.label;
+            }
+
+            if (definition.url) {
+              metadata.url = definition.url;
+            }
+
+            if (definition.comment) {
+              metadata.comment = definition.comment;
+            }
+
+            if (definition.metadata) {
+              metadata = _.extend(metadata, definition.metadata);
+            }
+            return metadata;
+        };
+        
+        var typeProps = function (id) {
+            var props = [];
+            _.each(SchemaOrg.types[id].specific_properties, function (pId) {
+                var property = SchemaOrg.properties[pId];
+                props.push({
+                    'id'    : property.id,
+                    'range' : property.ranges,
+                    'min'   : property.min,
+                    'max'   : property.max,
+                    'metadata': metadataHelper(property)
+                });
+            });
+            return props;
+        };
+        
+        var typeHelper = function (ancestors, id, props, metadata) {
+            var type = vie.types.add(id, props, metadata);
+           
+            for (var i = 0; i < ancestors.length; i++) {
+                var supertype = (vie.types.get(ancestors[i]))? vie.types.get(ancestors[i]) :
+                    typeHelper.call(vie, SchemaOrg.types[ancestors[i]].supertypes, ancestors[i], typeProps.call(vie, ancestors[i]));
+                type.inherit(supertype);
+            }
+            if (id === "Thing" && !type.isof("owl:Thing")) {
+                type.inherit("owl:Thing");
+            }
+            return type;
+        };
+       
+        _.each(SchemaOrg.types, function (typeDef) {
+            if (vie.types.get(typeDef.id)) {
+                return;
+            }
+            var ancestors = typeDef.supertypes;
+            var metadata = metadataHelper(typeDef);
+            typeHelper.call(vie, ancestors, typeDef.id, typeProps.call(vie, typeDef.id), metadata);
+        });
+
+        /* set the namespace to either the old value or the provided baseNS value */
+        vie.namespaces.base(baseNSBefore);
+    },
+
+// ### VIE.Util.getEntityTypeUnion(entity)
+// This generates a entity-specific VIE type that is a subtype of all the
+// types of the entity. This makes it easier to deal with attribute definitions
+// specific to an entity because they're merged to a single list. This custom
+// type is transient, meaning that it won't be automatilly added to the entity
+// or the VIE type registry.
+    getEntityTypeUnion : function(entity) {
+      var vie = entity.vie;
+      return new vie.Type('Union').inherit(entity.get('@type'));
+    },
+
+// ### VIE.Util.getFormSchemaForType(type)
+// This creates a [Backbone Forms](https://github.com/powmedia/backbone-forms)
+// -compatible form schema for any VIE Type.
+    getFormSchemaForType : function(type, allowNested) {
+      var schema = {};
+
+      // Generate a schema
+      _.each(type.attributes.toArray(), function (attribute) {
+        var key = VIE.Util.toCurie(attribute.id, false, attribute.vie.namespaces);
+        schema[key] = VIE.Util.getFormSchemaForAttribute(attribute);
+      });
+
+      // Clean up unknown attribute types
+      _.each(schema, function (field, id) {
+        if (!field.type) {
+          delete schema[id];
+        }
+
+        if (field.type === 'URL') {
+          field.type = 'Text';
+          field.dataType = 'url';
+        }
+
+        if (field.type === 'List' && !field.listType) {
+          delete schema[id];
+        }
+
+        if (!allowNested) {
+          if (field.type === 'NestedModel' || field.listType === 'NestedModel') {
+            delete schema[id];
+          }
+        }
+      });
+
+      return schema;
+    },
+
+/// ### VIE.Util.getFormSchemaForAttribute(attribute)
+    getFormSchemaForAttribute : function(attribute) {
+      var primaryType = attribute.range[0];
+      var schema = {};
+
+      var getWidgetForType = function (type) {
+        switch (type) {
+          case 'xsd:anySimpleType':
+          case 'xsd:float':
+          case 'xsd:integer':
+            return 'Number';
+          case 'xsd:string':
+            return 'Text';
+          case 'xsd:date':
+            return 'Date';
+          case 'xsd:dateTime':
+            return 'DateTime';
+          case 'xsd:boolean':
+            return 'Checkbox';
+          case 'xsd:anyURI':
+            return 'URL';
+          default:
+            var typeType = attribute.vie.types.get(type);
+            if (!typeType) {
+              return null;
+            }
+            if (typeType.attributes.get('value')) {
+              // Convert to proper xsd type
+              return getWidgetForType(typeType.attributes.get('value').range[0]);
+            }
+            return 'NestedModel';
+        }
+      };
+
+      // TODO: Generate a nicer label
+      schema.title = VIE.Util.toCurie(attribute.id, false, attribute.vie.namespaces);
+
+      // TODO: Handle attributes linking to other VIE entities
+
+      if (attribute.min > 0) {
+        schema.validators = ['required'];
+      }
+
+      if (attribute.max > 1) {
+        schema.type = 'List';
+        schema.listType = getWidgetForType(primaryType);
+        if (schema.listType === 'NestedModel') {
+          schema.nestedModelType = primaryType;
+        }
+        return schema;
+      }
+
+      schema.type = getWidgetForType(primaryType);
+      if (schema.type === 'NestedModel') {
+        schema.nestedModelType = primaryType;
+      }
+      return schema;
+    },
+
+// ### VIE.Util.getFormSchema(entity)
+// This creates a [Backbone Forms](https://github.com/powmedia/backbone-forms)
+// -compatible form schema for any VIE Entity. The form schema creation
+// utilizes type information attached to the entity.
+// **Parameters**:
+// *{```Entity```}* **entity** An instance of VIE ```Entity```.
+// **Throws**:
+// *nothing*..
+// **Returns**:
+// *{object}* a JavaScript object representation of the form schema
+    getFormSchema : function(entity) {
+      if (!entity || !entity.isEntity) {
+        return {};
+      }
+
+      var unionType = VIE.Util.getEntityTypeUnion(entity);
+      var schema = VIE.Util.getFormSchemaForType(unionType, true);
+
+      // Handle nested models
+      _.each(schema, function (property, id) {
+        if (property.type !== 'NestedModel' && property.listType !== 'NestedModel') {
+          return;
+        }
+        schema[id].model = entity.vie.getTypedEntityClass(property.nestedModelType);
+      });
+
+      return schema;
+    },
+
+// ### VIE.Util.xsdDateTime(date)
+// This transforms a ```Date``` instance into an xsd:DateTime format.  
+// **Parameters**:  
+// *{```Date```}* **date** An instance of a javascript ```Date```.  
+// **Throws**: 
+// *nothing*..  
+// **Returns**: 
+// *{string}* A string representation of the dateTime in the xsd:dateTime format.
+    xsdDateTime : function(date) {
+        function pad(n) {
+            var s = n.toString();
+            return s.length < 2 ? '0'+s : s;
+        }
+
+        var yyyy = date.getFullYear();
+        var mm1  = pad(date.getMonth()+1);
+        var dd   = pad(date.getDate());
+        var hh   = pad(date.getHours());
+        var mm2  = pad(date.getMinutes());
+        var ss   = pad(date.getSeconds());
+
+        return yyyy +'-' +mm1 +'-' +dd +'T' +hh +':' +mm2 +':' +ss;
+    },
+
+// ### VIE.Util.extractLanguageString(entity, attrs, langs)
+// This method extracts a literal string from an entity, searching through the given attributes and languages.  
+// **Parameters**:  
+// *{```VIE.Entity```}* **entity** An instance of a VIE.Entity.  
+// *{```array|string```}* **attrs** Either a string or an array of possible attributes.  
+// *{```array|string```}* **langs** Either a string or an array of possible languages.  
+// **Throws**: 
+// *nothing*..  
+// **Returns**: 
+// *{string|undefined}* The string that was found at the attribute with the wanted language, undefined if nothing could be found.
+// **Example usage**: 
+//
+//          var attrs = ["name", "rdfs:label"];
+//          var langs = ["en", "de"];
+//          VIE.Util.extractLanguageString(someEntity, attrs, langs); // "Barack Obama";
+    extractLanguageString : function(entity, attrs, langs) {
+        var p, attr, name, i, n;
+        if (entity && typeof entity !== "string") {
+            attrs = (_.isArray(attrs))? attrs : [ attrs ];
+            langs = (_.isArray(langs))? langs : [ langs ];
+            for (p = 0; p < attrs.length; p++) {
+                for (var l = 0; l < langs.length; l++) {
+                    var lang = langs[l];
+                    attr = attrs[p];
+                    if (entity.has(attr)) {
+                        name = entity.get(attr);
+                        name = (_.isArray(name))? name : [ name ];
+                        for (i = 0; i < name.length; i++) {
+                            n = name[i];
+                            if (n.isEntity) {
+                                n = VIE.Util.extractLanguageString(n, attrs, lang);
+                            } else if (typeof n === "string") {
+                                n = n;
+                            } else {
+                                n = "";
+                            }
+                            if (n && n.indexOf('@' + lang) > -1) {
+                                return n.replace(/"/g, "").replace(/@[a-z]+/, '').trim();
+                            }
+                        }
+                    }
+                }
+            }
+            /* let's do this again in case we haven't found a name but are dealing with
+            broken data where no language is given */
+            for (p = 0; p < attrs.length; p++) {
+                attr = attrs[p];
+                if (entity.has(attr)) {
+                    name = entity.get(attr);
+                    name = (_.isArray(name))? name : [ name ];
+                    for (i = 0; i < name.length; i++) {
+                        n = name[i];
+                        if (n.isEntity) {
+                            n = VIE.Util.extractLanguageString(n, attrs, []);
+                        }
+                        if (n && (typeof n === "string") && n.indexOf('@') === -1) {
+                            return n.replace(/"/g, "").replace(/@[a-z]+/, '').trim();
+                        }
+                    }
+                }
+            }
+        }
+        return undefined;
+    },
+    
+// ### VIE.Util.transformationRules(service)
+// This returns a default set of rdfQuery rules that transform semantic data into the
+// VIE entity types.  
+// **Parameters**:  
+// *{object}* **service** An instance of a vie.service.  
+// **Throws**: 
+// *nothing*..  
+// **Returns**: 
+// *{array}* An array of rules with 'left' and 'right' side.
+    transformationRules : function (service) {
+        var res = [
+            // rule(s) to transform a dbpedia:Person into a VIE:Person
+             {
+                'left' : [
+                    '?subject a dbpedia:Person',
+                    '?subject rdfs:label ?label'
+                 ],
+                 'right': function(ns){
+                     return function(){
+                         return [
+                             jQuery.rdf.triple(this.subject.toString(),
+                                 'a',
+                                 '<' + ns.base() + 'Person>', {
+                                     namespaces: ns.toObj()
+                                 }),
+                             jQuery.rdf.triple(this.subject.toString(),
+                                 '<' + ns.base() + 'name>',
+                                 this.label, {
+                                     namespaces: ns.toObj()
+                                 })
+                             ];
+                     };
+                 }(service.vie.namespaces)
+             },
+             // rule(s) to transform a foaf:Person into a VIE:Person
+             {
+             'left' : [
+                     '?subject a foaf:Person',
+                     '?subject rdfs:label ?label'
+                  ],
+                  'right': function(ns){
+                      return function(){
+                          return [
+                              jQuery.rdf.triple(this.subject.toString(),
+                                  'a',
+                                  '<' + ns.base() + 'Person>', {
+                                      namespaces: ns.toObj()
+                                  }),
+                              jQuery.rdf.triple(this.subject.toString(),
+                                  '<' + ns.base() + 'name>',
+                                  this.label, {
+                                      namespaces: ns.toObj()
+                                  })
+                              ];
+                      };
+                  }(service.vie.namespaces)
+              },
+             // rule(s) to transform a dbpedia:Place into a VIE:Place
+             {
+                 'left' : [
+                     '?subject a dbpedia:Place',
+                     '?subject rdfs:label ?label'
+                  ],
+                  'right': function(ns) {
+                      return function() {
+                          return [
+                          jQuery.rdf.triple(this.subject.toString(),
+                              'a',
+                              '<' + ns.base() + 'Place>', {
+                                  namespaces: ns.toObj()
+                              }),
+                          jQuery.rdf.triple(this.subject.toString(),
+                                  '<' + ns.base() + 'name>',
+                              this.label.toString(), {
+                                  namespaces: ns.toObj()
+                              })
+                          ];
+                      };
+                  }(service.vie.namespaces)
+              },
+             // rule(s) to transform a dbpedia:City into a VIE:City
+              {
+                 'left' : [
+                     '?subject a dbpedia:City',
+                     '?subject rdfs:label ?label',
+                     '?subject dbpedia:abstract ?abs',
+                     '?subject dbpedia:country ?country'
+                  ],
+                  'right': function(ns) {
+                      return function() {
+                          return [
+                          jQuery.rdf.triple(this.subject.toString(),
+                              'a',
+                              '<' + ns.base() + 'City>', {
+                                  namespaces: ns.toObj()
+                              }),
+                          jQuery.rdf.triple(this.subject.toString(),
+                                  '<' + ns.base() + 'name>',
+                              this.label.toString(), {
+                                  namespaces: ns.toObj()
+                              }),
+                          jQuery.rdf.triple(this.subject.toString(),
+                                  '<' + ns.base() + 'description>',
+                              this.abs.toString(), {
+                                  namespaces: ns.toObj()
+                              }),
+                          jQuery.rdf.triple(this.subject.toString(),
+                                  '<' + ns.base() + 'containedIn>',
+                              this.country.toString(), {
+                                  namespaces: ns.toObj()
+                              })
+                          ];
+                      };
+                  }(service.vie.namespaces)
+              }
+        ];
+        return res;
+    },
+    
+    getAdditionalRules : function (service) {
+
+        var mapping = {
+            Work : "CreativeWork",
+            Film : "Movie",
+            TelevisionEpisode : "TVEpisode",
+            TelevisionShow : "TVSeries", // not listed as equivalent class on dbpedia.org
+            Website : "WebPage",
+            Painting : "Painting",
+            Sculpture : "Sculpture",
+    
+            Event : "Event",
+            SportsEvent : "SportsEvent",
+            MusicFestival : "Festival",
+            FilmFestival : "Festival",
+    
+            Place : "Place",
+            Continent : "Continent",
+            Country : "Country",
+            City : "City",
+            Airport : "Airport",
+            Station : "TrainStation", // not listed as equivalent class on dbpedia.org
+            Hospital : "GovernmentBuilding",
+            Mountain : "Mountain",
+            BodyOfWater : "BodyOfWater",
+    
+            Company : "Organization",
+            Person : "Person"
+        };
+
+        var additionalRules = [];
+        _.each(mapping, function (map, key) {
+            var tripple = {
+                'left' : [ '?subject a dbpedia:' + key, '?subject rdfs:label ?label' ],
+                'right' : function(ns) {
+                    return function() {
+                        return [ jQuery.rdf.triple(this.subject.toString(), 'a', '<' + ns.base() + map + '>', {
+                            namespaces : ns.toObj()
+                        }), jQuery.rdf.triple(this.subject.toString(), '<' + ns.base() + 'name>', this.label.toString(), {
+                            namespaces : ns.toObj()
+                        }) ];
+                    };
+                }(service.vie.namespaces)
+            };
+            additionalRules.push(tripple);
+        });
+        return additionalRules;
+    }
+};
+//     VIE - Vienna IKS Editables
+//     (c) 2011 Henri Bergius, IKS Consortium
+//     (c) 2011 Sebastian Germesin, IKS Consortium
+//     (c) 2011 Szaby Grünwald, IKS Consortium
+//     VIE may be freely distributed under the MIT license.
+//     For all details and documentation:
+//     http://viejs.org/
+
+// ## VIE Entities
+// 
+// In VIE there are two low-level model types for storing data.
+// **Collections** and **Entities**. Considering `var v = new VIE();` a VIE instance,
+// `v.entities` is a Collection with `VIE Entity` objects in it. 
+// VIE internally uses JSON-LD to store entities.
+//
+// Each Entity has a few special attributes starting with an `@`. VIE has an API
+// for correctly using these attributes, so in order to stay compatible with later 
+// versions of the library, possibly using a later version of JSON-LD, use the API
+// to interact with your entities.
+// 
+// * `@subject` stands for the identifier of the entity. Use `e.getSubject()` 
+// * `@type` stores the explicit entity types. VIE internally handles Type hierarchy,
+// which basically enables to define subtypes and supertypes. Every entity has 
+// the type 'owl:Thing'. Read more about Types in <a href="Type.html">VIE.Type</a>.
+// * `@context` stores namespace definitions used in the entity. Read more about 
+// Namespaces in <a href="Namespace.html">VIE Namespaces</a>.
+VIE.prototype.Entity = function(attrs, opts) {
+
+    attrs = (attrs)? attrs : {};
+    opts = (opts)? opts : {};
+
+    var self = this;
+
+    if (attrs['@type'] !== undefined) {
+        attrs['@type'] = (_.isArray(attrs['@type']))? attrs['@type'] : [ attrs['@type'] ];
+        attrs['@type'] = _.map(attrs['@type'], function(val){
+            if (!self.vie.types.get(val)) {
+                //if there is no such type -> add it and let it inherit from "owl:Thing"
+                self.vie.types.add(val).inherit("owl:Thing");
+            }
+            return self.vie.types.get(val).id;
+        });
+        attrs['@type'] = (attrs['@type'].length === 1)? attrs['@type'][0] : attrs['@type'];
+    } else {
+        // provide "owl:Thing" as the default type if none was given
+        attrs['@type'] = self.vie.types.get("owl:Thing").id;
+    }
+
+    //the following provides full seamless namespace support
+    //for attributes. It should not matter, if you
+    //query for `model.get('name')` or `model.get('foaf:name')`
+    //or even `model.get('http://xmlns.com/foaf/0.1/name');`
+    //However, if we just overwrite `set()` and `get()`, this
+    //raises a lot of side effects, so we need to expand
+    //the attributes before we create the model.
+    _.each (attrs, function (value, key) {
+        var newKey = VIE.Util.mapAttributeNS(key, this.namespaces);
+        if (key !== newKey) {
+            delete attrs[key];
+            attrs[newKey] = value;
+        }
+    }, self.vie);
+
+    var Model = Backbone.Model.extend({
+        idAttribute: '@subject',
+
+        initialize: function(attributes, options) {
+            if (attributes['@subject']) {
+                this.id = this['@subject'] = this.toReference(attributes['@subject']);
+            } else {
+                this.id = this['@subject'] = attributes['@subject'] = this.cid.replace('c', '_:bnode');
+            }
+            return this;
+        },
+
+        schema: function() {
+          return VIE.Util.getFormSchema(this);
+        },
+
+        // ### Getter, Has, Setter
+        // #### `.get(attr)`
+        // To be able to communicate to a VIE Entity you can use a simple get(property)
+        // command as in `entity.get('rdfs:label')` which will give you one or more literals.
+        // If the property points to a collection, its entities can be browsed further.
+        get: function (attr) {
+            attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
+            var value = Backbone.Model.prototype.get.call(this, attr);
+            value = (_.isArray(value))? value : [ value ];
+
+            value = _.map(value, function(v) {
+                if (v !== undefined && attr === '@type' && self.vie.types.get(v)) {
+                    return self.vie.types.get(v);
+                } else if (v !== undefined && self.vie.entities.get(v)) {
+                    return self.vie.entities.get(v);
+                } else {
+                    return v;
+                }
+            }, this);
+            if(value.length === 0) {
+                return undefined;
+            }
+            // if there is only one element, just return that one
+            value = (value.length === 1)? value[0] : value;
+            return value;
+        },
+
+        // #### `.has(attr)`
+        // Sometimes you'd like to determine if a specific attribute is set 
+        // in an entity. For this reason you can call for example `person.has('friend')`
+        // to determine if a person entity has friends.
+        has: function(attr) {
+            attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
+            return Backbone.Model.prototype.has.call(this, attr);
+        },
+
+        // #### `.set(attrName, value, opts)`, 
+        // The `options` parameter always refers to a `Backbone.Model.set` `options` object.
+        //
+        // **`.set(attributes, options)`** is the most universal way of calling the
+        // `.set` method. In this case the `attributes` object is a map of all 
+        // attributes to be changed.
+        set : function(attrs, options, opts) {
+            if (!attrs) {
+                return this;
+            }
+
+            if (attrs['@subject']) {
+                attrs['@subject'] = this.toReference(attrs['@subject']);
+            }
+
+            // Use **`.set(attrName, value, options)`** for setting or changing exactly one 
+            // entity attribute.
+            if (typeof attrs === "string") {
+                var obj = {};
+                obj[attrs] = options;
+                return this.set(obj, opts);
+            }
+            // **`.set(entity)`**: In case you'd pass a VIE entity, 
+            // the passed entities attributes are being set for the entity.
+            if (attrs.attributes) {
+                attrs = attrs.attributes;
+            }
+            var self = this;
+            var coll;
+            // resolve shortened URIs like rdfs:label..
+            _.each (attrs, function (value, key) {
+                var newKey = VIE.Util.mapAttributeNS(key, self.vie.namespaces);
+                if (key !== newKey) {
+                    delete attrs[key];
+                    attrs[newKey] = value;
+                }
+            }, this);
+            // Finally iterate through the *attributes* to be set and prepare 
+            // them for the Backbone.Model.set method.
+            _.each (attrs, function (value, key) {
+               if (!value) { return; }
+               if (key.indexOf('@') === -1) {
+                   if (value.isCollection) {
+                       // ignore
+                       value.each(function (child) {
+                           self.vie.entities.addOrUpdate(child);
+                       });
+                   } else if (value.isEntity) {
+                       self.vie.entities.addOrUpdate(value);
+                       coll = new self.vie.Collection(value, {
+                         vie: self.vie,
+                         predicate: key
+                       });
+                       attrs[key] = coll;
+                   } else if (_.isArray(value)) {
+                       if (this.attributes[key] && this.attributes[key].isCollection) {
+                         var newEntities = this.attributes[key].addOrUpdate(value);
+                         attrs[key] = this.attributes[key];
+                         attrs[key].reset(newEntities);
+                       }
+                   } else if (value["@value"]) {
+                       // The value is a literal object, ignore
+                   } else if (_.isObject(value) && !_.isDate(value)) {
+                       // The value is another VIE Entity
+                       var child = new self.vie.Entity(value, options);
+                       // which is being stored in `v.entities`
+                       self.vie.entities.addOrUpdate(child);
+                       // and set as VIE Collection attribute on the original entity 
+                       coll = new self.vie.Collection(value, {
+                         vie: self.vie,
+                         predicate: key
+                       });
+                       attrs[key] = coll;
+                   } else {
+                       // ignore
+                   }
+               }
+            }, this);
+            return Backbone.Model.prototype.set.call(this, attrs, options);
+        },
+
+        // **`.unset(attr, opts)` ** removes an attribute from the entity.
+        unset: function (attr, opts) {
+            attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
+            return Backbone.Model.prototype.unset.call(this, attr, opts);
+        },
+
+        // Validation based on type rules.
+        //
+        // There are two ways to skip validation for entity operations:
+        //
+        // * `options.silent = true`
+        // * `options.validate = false`
+        validate: function (attrs, opts) {
+            if (opts && opts.validate === false) {
+                return;
+            }
+            var types = this.get('@type');
+            if (_.isArray(types)) {
+                var results = [];
+                _.each(types, function (type) {
+                    var res = this.validateByType(type, attrs, opts);
+                    if (res) {
+                        results.push(res);
+                    }
+                }, this);
+                if (_.isEmpty(results)) {
+                  return;
+                }
+                return _.flatten(results);
+            }
+
+            return this.validateByType(types, attrs, opts);
+        },
+
+        validateByType: function (type, attrs, opts) {
+            var messages = {
+              max: '<%= property %> cannot contain more than <%= num %> items',
+              min: '<%= property %> must contain at least <%= num %> items',
+              required: '<%= property %> is required'
+            };
+
+            if (!type.attributes) {
+                return;
+            }
+
+            var toError = function (definition, constraint, messageValues) {
+                return {
+                    property: definition.id,
+                    constraint: constraint,
+                    message: _.template(messages[constraint], _.extend({
+                        property: definition.id
+                    }, messageValues))
+                };
+            };
+
+            var checkMin = function (definition, attrs) {
+                if (!attrs[definition.id] || _.isEmpty(attrs[definition.id])) {
+                    return toError(definition, 'required', {});
+                }
+            };
+
+            // Check the number of items in attr against max
+            var checkMax = function (definition, attrs) {
+                if (!attrs[definition.id]) {
+                    return;
+                }
+
+                if (!attrs[definition.id].isCollection && !_.isArray(attrs[definition.id])) {
+                    return;
+                }
+
+                if (attrs[definition.id].length > definition.max) {
+                    return toError(definition, 'max', {
+                        num: definition.max
+                    });
+                }
+            };
+
+            var results = [];
+            _.each(type.attributes.list(), function (definition) {
+                var res;
+                if (definition.max && definition.max != -1) {
+                    res = checkMax(definition, attrs);
+                    if (res) {
+                        results.push(res);
+                    }
+                }
+
+                if (definition.min && definition.min > 0) {
+                    res = checkMin(definition, attrs);
+                    if (res) {
+                        results.push(res);
+                    }
+                }
+            });
+
+            if (_.isEmpty(results)) {
+              return;
+            }
+            return results;
+        },
+
+        isNew: function() {
+            if (this.getSubjectUri().substr(0, 7) === '_:bnode') {
+                return true;
+            }
+            return false;
+        },
+
+        hasChanged: function(attr) {
+            if (this.markedChanged) {
+                return true;
+            }
+
+            return Backbone.Model.prototype.hasChanged.call(this, attr);
+        },
+
+        // Force hasChanged to return true
+        forceChanged: function(changed) {
+            this.markedChanged = changed ? true : false;
+        },
+
+        // **`getSubject()`** is the getter for the entity identifier.
+        getSubject: function(){
+            if (typeof this.id === "undefined") {
+                this.id = this.attributes[this.idAttribute];
+            }
+            if (typeof this.id === 'string') {
+                if (this.id.substr(0, 7) === 'http://' || this.id.substr(0, 4) === 'urn:') {
+                    return this.toReference(this.id);
+                }
+                return this.id;
+            }
+            return this.cid.replace('c', '_:bnode');
+        },
+
+        // TODO describe
+        getSubjectUri: function(){
+            return this.fromReference(this.getSubject());
+        },
+
+        isReference: function(uri){
+            var matcher = new RegExp("^\\<([^\\>]*)\\>$");
+            if (matcher.exec(uri)) {
+                return true;
+            }
+            return false;
+        },
+
+        toReference: function(uri){
+            if (_.isArray(uri)) {
+              var self = this;
+              return _.map(uri, function(part) {
+                 return self.toReference(part);
+              });
+            }
+            var ns = this.vie.namespaces;
+            var ret = uri;
+            if (uri.substring(0, 2) === "_:") {
+                ret = uri;
+            }
+            else if (ns.isCurie(uri)) {
+                ret = ns.uri(uri);
+                if (ret === "<" + ns.base() + uri + ">") {
+                    /* no base namespace extension with IDs */
+                    ret = '<' + uri + '>';
+                }
+            } else if (!ns.isUri(uri)) {
+                ret = '<' + uri + '>';
+            }
+            return ret;
+        },
+
+        fromReference: function(uri){
+            var ns = this.vie.namespaces;
+            if (!ns.isUri(uri)) {
+                return uri;
+            }
+            return uri.substring(1, uri.length - 1);
+        },
+
+        as: function(encoding){
+            if (encoding === "JSON") {
+                return this.toJSON();
+            }
+            if (encoding === "JSONLD") {
+                return this.toJSONLD();
+            }
+            throw new Error("Unknown encoding " + encoding);
+        },
+
+        toJSONLD: function(){
+            var instanceLD = {};
+            var instance = this;
+            _.each(instance.attributes, function(value, name){
+                var entityValue = value; //instance.get(name);
+
+                if (value instanceof instance.vie.Collection) {
+                    entityValue = value.map(function(instance) {
+                        return instance.getSubject();
+                    });
+                }
+
+                // TODO: Handle collections separately
+                instanceLD[name] = entityValue;
+            });
+
+            instanceLD['@subject'] = instance.getSubject();
+
+            return instanceLD;
+        },
+
+        // **`.setOrAdd(arg1, arg2)`** similar to `.set(..)`, `.setOrAdd(..)` can 
+        // be used for setting one or more attributes of an entity, but in
+        // this case it's a collection of values, not just one. That means, if the
+        // entity already has the attribute set, make the value to a VIE Collection
+        // and use the collection as value. The collection can contain entities 
+        // or literals, but not both at the same time.
+        setOrAdd: function (arg1, arg2, option) {
+            var entity = this;
+            if (typeof arg1 === "string" && arg2) {
+                // calling entity.setOrAdd("rdfs:type", "example:Musician")
+                entity._setOrAddOne(arg1, arg2, option);
+            }
+            else
+                if (typeof arg1 === "object") {
+                    // calling entity.setOrAdd({"rdfs:type": "example:Musician", ...})
+                    _(arg1).each(function(val, key){
+                        entity._setOrAddOne(key, val, arg2);
+                    });
+                }
+            return this;
+        },
+
+
+        /* attr is always of type string */
+        /* value can be of type: string,int,double,object,VIE.Entity,VIE.Collection */
+       /*  val can be of type: undefined,string,int,double,array,VIE.Collection */
+       
+        /* depending on the type of value and the type of val, different actions need to be made */
+        _setOrAddOne: function (attr, value, options) {
+            if (!attr || !value)
+                return;
+            options = (options)? options : {};
+            var v;
+                
+            attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
+            
+            if (_.isArray(value)) {
+                for (v = 0; v < value.length; v++) {
+                    this._setOrAddOne(attr, value[v], options);
+                }
+                return;
+            }
+            
+            if (attr === "@type" && value instanceof self.vie.Type) {
+                value = value.id;
+            }
+            
+            var obj = {};
+            var existing = Backbone.Model.prototype.get.call(this, attr);
+            
+            if (!existing) {
+                obj[attr] = value;
+                this.set(obj, options);
+            } else if (existing.isCollection) {
+                if (value.isCollection) {
+                    value.each(function (model) {
+                        existing.add(model);
+                    });
+                } else if (value.isEntity) {
+                    existing.add(value);
+                } else if (typeof value === "object") {
+                    value = new this.vie.Entity(value);
+                    existing.add(value);
+                } else {
+                    throw new Error("you cannot add a literal to a collection of entities!");
+                }
+                this.trigger('change:' + attr, this, value, {});
+                this.change({});
+            } else if (_.isArray(existing)) {
+                if (value.isCollection) {
+                    for (v = 0; v < value.size(); v++) {
+                        this._setOrAddOne(attr, value.at(v).getSubject(), options);
+                    }
+                } else if (value.isEntity) {
+                    this._setOrAddOne(attr, value.getSubject(), options);
+                } else if (typeof value === "object") {
+                    value = new this.vie.Entity(value);
+                    this._setOrAddOne(attr, value, options);
+                } else {
+                    /* yes, we (have to) allow multiple equal values */
+                    existing.push(value);
+                    obj[attr] = existing;
+                    this.set(obj);
+                }
+            } else {
+                var arr = [ existing ];
+                arr.push(value);
+                obj[attr] = arr;
+                return this.set(obj, options);
+            }
+        },
+
+        // **`.hasType(type)`** determines if the entity has the explicit `type` set.
+        hasType: function(type){
+            type = self.vie.types.get(type);
+            return this.hasPropertyValue("@type", type);
+        },
+
+        // TODO describe
+        hasPropertyValue: function(property, value) {
+            var t = this.get(property);
+            if (!(value instanceof Object)) {
+                value = self.vie.entities.get(value);
+            }
+            if (t instanceof Array) {
+                return t.indexOf(value) !== -1;
+            }
+            else {
+                return t === value;
+            }
+        },
+
+        // **`.isof(type)`** determines if the entity is of `type` by explicit or implicit 
+        // declaration. E.g. if Employee is a subtype of Person and e Entity has
+        // explicitly set type Employee, e.isof(Person) will evaluate to true.
+        isof: function (type) {
+            var types = this.get('@type');
+            
+            if (types === undefined) {
+                return false;
+            }
+            types = (_.isArray(types))? types : [ types ];
+            
+            type = (self.vie.types.get(type))? self.vie.types.get(type) : new self.vie.Type(type);
+            for (var t = 0; t < types.length; t++) {
+                if (self.vie.types.get(types[t])) {
+                    if (self.vie.types.get(types[t]).isof(type)) {
+                        return true;
+                    }
+                } else {
+                    var typeTmp = new self.vie.Type(types[t]);
+                    if (typeTmp.id === type.id) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        },
+        // TODO describe
+        addTo : function (collection, update) {
+            var self = this;
+            if (collection instanceof self.vie.Collection) {
+                if (update) {
+                    collection.addOrUpdate(self);
+                } else {
+                    collection.add(self);
+                }
+                return this;
+            }
+            throw new Error("Please provide a proper collection of type VIE.Collection as argument!");
+        },
+
+        isEntity: true,
+
+        vie: self.vie
+    });
+
+    return new Model(attrs, opts);
+};
+//     VIE - Vienna IKS Editables
+//     (c) 2011 Henri Bergius, IKS Consortium
+//     (c) 2011 Sebastian Germesin, IKS Consortium
+//     (c) 2011 Szaby Grünwald, IKS Consortium
+//     VIE may be freely distributed under the MIT license.
+//     For all details and documentation:
+//     http://viejs.org/
+VIE.prototype.Collection = Backbone.Collection.extend({
+    model: VIE.prototype.Entity,
+
+    initialize: function (models, options) {
+      if (!options || !options.vie) {
+        throw new Error('Each collection needs a VIE reference');
+      }
+      this.vie = options.vie;
+      this.predicate = options.predicate;
+    },
+
+    canAdd: function (type) {
+      return true;
+    },
+    
+    get: function(id) {
+        if (id === null) {
+            return null;
+        }
+        
+        id = (id.getSubject)? id.getSubject() : id;        
+        if (typeof id === "string" && id.indexOf("_:") === 0) {
+            if (id.indexOf("bnode") === 2) {
+                //bnode!
+                id = id.replace("_:bnode", 'c');
+                return this._byCid[id];
+            } else {
+                return this._byId["<" + id + ">"];
+            }
+        } else {
+            id = this.toReference(id);
+            return this._byId[id];
+        }
+    },
+
+    addOrUpdate: function(model, options) {
+        options = options || {};
+
+        var collection = this;
+        var existing;
+        if (_.isArray(model)) {
+            var entities = [];
+            _.each(model, function(item) {
+                entities.push(collection.addOrUpdate(item, options));
+            });
+            return entities;
+        }
+
+        if (model === undefined) {
+            throw new Error("No model given");
+        }
+
+        if (_.isString(model)) {
+          model = {
+            '@subject': model,
+            id: model
+          };
+        }
+
+        if (!model.isEntity) {
+            model = new this.model(model);
+        }
+
+        if (model.id && this.get(model.id)) {
+            existing = this.get(model.id);
+        }
+        if (this.getByCid(model.cid)) {
+            existing = this.getByCid(model.cid);
+        }
+        if (existing) {
+            var newAttribs = {};
+            _.each(model.attributes, function(value, attribute) {
+                if (!existing.has(attribute)) {
+                    newAttribs[attribute] = value;
+                    return true;
+                }
+
+                if (attribute === '@subject') {
+                    if (model.isNew() && !existing.isNew()) {
+                        // Save order issue, skip
+                        return true;
+                    }
+                }
+
+                if (existing.get(attribute) === value) {
+                    return true;
+                }
+                //merge existing attribute values with new ones!
+                //not just overwrite 'em!!
+                var oldVals = existing.attributes[attribute];
+                var newVals = value;
+                if (oldVals instanceof collection.vie.Collection) {
+                    // TODO: Merge collections
+                    return true;
+                }
+                if (options.overrideAttributes) {
+                   newAttribs[attribute] = value;
+                   return true;
+                } 
+                if (attribute === '@context') {
+                    newAttribs[attribute] = jQuery.extend(true, {}, oldVals, newVals);
+                } else {
+                    oldVals = (jQuery.isArray(oldVals))? oldVals : [ oldVals ];
+                    newVals = (jQuery.isArray(newVals))? newVals : [ newVals ];
+                    newAttribs[attribute] = _.uniq(oldVals.concat(newVals));
+                    newAttribs[attribute] = (newAttribs[attribute].length === 1)? newAttribs[attribute][0] : newAttribs[attribute];
+                }
+            });
+
+            if (!_.isEmpty(newAttribs)) {
+                existing.set(newAttribs, options.updateOptions);
+            }
+            return existing;
+        }
+        this.add(model, options.addOptions);
+        return model;
+    },
+
+    isReference: function(uri){
+        var matcher = new RegExp("^\\<([^\\>]*)\\>$");
+        if (matcher.exec(uri)) {
+            return true;
+        }
+        return false;
+    },
+        
+    toReference: function(uri){
+        if (this.isReference(uri)) {
+            return uri;
+        }
+        return '<' + uri + '>';
+    },
+        
+    fromReference: function(uri){
+        if (!this.isReference(uri)) {
+            return uri;
+        }
+        return uri.substring(1, uri.length - 1);
+    },
+    
+    isCollection: true
+});
+//     VIE - Vienna IKS Editables
+//     (c) 2011 Henri Bergius, IKS Consortium
+//     (c) 2011 Sebastian Germesin, IKS Consortium
+//     (c) 2011 Szaby Grünwald, IKS Consortium
+//     VIE may be freely distributed under the MIT license.
+//     For all details and documentation:
+//     http://viejs.org/
+//
+
+// ## VIE.Types
+// Within VIE, we provide special capabilities of handling types of entites. This helps
+// for example to query easily for certain entities (e.g., you only need to query for *Person*s 
+// and not for all subtypes).
+if (VIE.prototype.Type) {
+    throw new Error("ERROR: VIE.Type is already defined. Please check your installation!");
+}
+if (VIE.prototype.Types) {
+    throw new Error("ERROR: VIE.Types is already defined. Please check your installation!");
+}
+
+// ### VIE.Type(id, attrs, metadata)
+// This is the constructor of a VIE.Type.  
+// **Parameters**:  
+// *{string}* **id** The id of the type.  
+// *{string|array|VIE.Attribute}* **attrs** A string, proper ```VIE.Attribute``` or an array of these which 
+// *{object}* **metadata** Possible metadata about the type
+// are the possible attributes of the type  
+// **Throws**:  
+// *{Error}* if one of the given paramenters is missing.  
+// **Returns**:  
+// *{VIE.Type}* : A **new** VIE.Type object.  
+// **Example usage**:  
+//
+//     var person = new vie.Type("Person", ["name", "knows"]);
+VIE.prototype.Type = function (id, attrs, metadata) {
+    if (id === undefined || typeof id !== 'string') {
+        throw "The type constructor needs an 'id' of type string! E.g., 'Person'";
+    }
+
+// ### id
+// This field stores the id of the type's instance.  
+// **Parameters**:  
+// nothing
+// **Throws**:  
+// nothing  
+// **Returns**:  
+// *{string}* : The id of the type as a URI.  
+// **Example usage**:  
+//
+//     console.log(person.id);
+//      // --> "<http://viejs.org/ns/Person>"
+    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 <a href="Attribute.html">VIE.Attributes</a>)  
+// **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 <a href="Entity.html">constructor of VIE.Entity</a>  
+// *{object}* **opts**  see <a href="Entity.html">constructor of VIE.Entity</a>  
+// **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);
+//     // --> <http://viejs.org/ns/knows>
+    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 = "<http://dbpedia.org/ontology/Person>";
+//     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 = "<http://dbpedia.org/ontology/Person>";
+//     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); 
+//          --> <http://dbpedia.org/ontology/Person>
+//     ns.uri(scurie);
+//          --> <http://dbpedia.org/ontology/Person>
+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 = "<http://test.ns/Person>";
+//     var curie = "test:Person";
+//     namespaces.isUri(uri);   // --> true
+//     namespaces.isUri(curie); // --> false
+VIE.prototype.Namespaces.prototype.isUri = VIE.Util.isUri;
+//     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/
+
+// Classic VIE API bindings to new VIE
+VIE.prototype.ClassicRDFa = function(vie) {
+    this.vie = vie;
+};
+
+VIE.prototype.ClassicRDFa.prototype = {
+    readEntities: function(selector) {
+        var jsonEntities = [];
+        var entities = this.vie.RDFaEntities.getInstances(selector);
+        _.each(entities, function(entity) {
+            jsonEntities.push(entity.toJSONLD());
+        });
+        return jsonEntities;
+    },
+
+    findPredicateElements: function(subject, element, allowNestedPredicates) {
+        return this.vie.services.rdfa.findPredicateElements(subject, element, allowNestedPredicates);
+    },
+
+    getPredicate: function(element) {
+        return this.vie.services.rdfa.getElementPredicate(element);
+    },
+
+    getSubject: function(element) {
+        return this.vie.services.rdfa.getElementSubject(element);
+    }
+};
+
+VIE.prototype.ClassicRDFaEntities = function(vie) {
+    this.vie = vie;
+};
+
+VIE.prototype.ClassicRDFaEntities.prototype = {
+    getInstances: function(selector) {
+        if (!this.vie.services.rdfa) {
+            this.vie.use(new this.vie.RdfaService());
+        }
+        var foundEntities = null;
+        var loaded = false;
+        this.vie.load({element: selector}).from('rdfa').execute().done(function(entities) {
+            foundEntities = entities;
+            loaded = true;
+        });
+
+        while (!loaded) {
+        }
+
+        return foundEntities;
+    },
+
+    getInstance: function(selector) {
+        var instances = this.getInstances(selector);
+        if (instances && instances.length) {
+            return instances.pop();
+        }
+        return null;
+    }
+};
+
+VIE.prototype.ClassicEntityManager = function(vie) {
+    this.vie = vie;
+    this.entities = this.vie.entities;
+};
+
+VIE.prototype.ClassicEntityManager.prototype = {
+    getBySubject: function(subject) {
+        return this.vie.entities.get(subject);
+    },
+
+    getByJSONLD: function(json) {
+        if (typeof json === 'string') {
+            try {
+                json = jQuery.parseJSON(json);
+            } catch (e) {
+                return null;
+            }
+        }
+        return this.vie.entities.addOrUpdate(json);
+    },
+
+    initializeCollection: function() {
+        return;
+    }
+};
+//     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 - DBPedia service
+// The DBPedia service allows a VIE developer to directly query
+// the DBPedia database for entities and their properties. Obviously,
+// the service does not allow for saving, removing or analyzing methods.
+(function(){
+
+// ## VIE.DBPediaService(options)
+// This is the constructor to instantiate a new service to collect
+// properties of an entity from <a href="http://dbpedia.org">DBPedia</a>.  
+// **Parameters**:  
+// *{object}* **options** Optional set of fields, ```namespaces```, ```rules```, or ```name```.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.DBPediaService}* : A **new** VIE.DBPediaService instance.  
+// **Example usage**:  
+//
+//     var dbpService = new vie.DBPediaService({<some-configuration>});
+VIE.prototype.DBPediaService = function (options) {
+    var defaults = {
+        /* the default name of this service */
+        name : 'dbpedia',
+        /* default namespaces that are shipped with this service */
+        namespaces : {
+            owl    : "http://www.w3.org/2002/07/owl#",
+            yago   : "http://dbpedia.org/class/yago/",
+            foaf: 'http://xmlns.com/foaf/0.1/',
+            georss: "http://www.georss.org/georss/",
+            geo: 'http://www.w3.org/2003/01/geo/wgs84_pos#',
+            rdfs: "http://www.w3.org/2000/01/rdf-schema#",
+            rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+            dbpedia: "http://dbpedia.org/ontology/",
+            dbprop : "http://dbpedia.org/property/",
+            dcelements : "http://purl.org/dc/elements/1.1/"
+        },
+        /* default rules that are shipped with this service */
+        rules : []
+    };
+    /* the options are merged with the default options */
+    this.options = jQuery.extend(true, defaults, options ? options : {});
+
+    this.vie = null; /* this.vie will be set via VIE.use(); */
+    /* overwrite options.name if you want to set another name */
+    this.name = this.options.name;
+    
+    /* basic setup for the ajax connection */
+    jQuery.ajaxSetup({
+        converters: {"text application/rdf+json": function(s){return JSON.parse(s);}},
+        timeout: 60000 /* 60 seconds timeout */
+    });
+};
+
+VIE.prototype.DBPediaService.prototype = {
+    
+// ### init()
+// This method initializes certain properties of the service and is called
+// via ```VIE.use()```.  
+// **Parameters**:  
+// *nothing*  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.DBPediaService}* : The VIE.DBPediaService instance itself.  
+// **Example usage**:  
+//
+//     var dbpService = new vie.DBPediaService({<some-configuration>});
+//     dbpService.init();
+    init: function() {
+
+        for (var key in this.options.namespaces) {
+            var val = this.options.namespaces[key];
+            this.vie.namespaces.add(key, val);
+        }
+        
+        this.rules = jQuery.extend([], VIE.Util.transformationRules(this));
+        this.rules = jQuery.merge(this.rules, (this.options.rules) ? this.options.rules : []);
+        
+        this.connector = new this.vie.DBPediaConnector(this.options);
+        
+        return this;
+    },
+
+// ### load(loadable)
+// This method loads the entity that is stored within the loadable into VIE.
+// You can also query for multiple queries by setting ```entities``` with
+// an array of entities.  
+// **Parameters**:  
+// *{VIE.Loadable}* **lodable** The loadable.  
+// **Throws**:  
+// *{Error}* if an invalid VIE.Loadable is passed.  
+// **Returns**:  
+// *{VIE.DBPediaService}* : The VIE.DBPediaService instance itself.  
+// **Example usage**:  
+//
+//  var dbpService = new vie.DBPediaService({<some-configuration>});
+//  dbpService.load(new vie.Loadable({entity : "<http://...>"}));
+//    OR
+//  var dbpService = new vie.DBPediaService({<some-configuration>});
+//  dbpService.load(new vie.Loadable({entities : ["<http://...>", "<http://...>"]}));
+    load: function(loadable){
+        var service = this;
+        
+        var correct = loadable instanceof this.vie.Loadable;
+        if (!correct) {
+            throw new Error("Invalid Loadable passed");
+        }
+        
+        var success = function (results) {
+            results = (typeof results === "string")? JSON.parse(results) : results;
+            _.defer(function() {
+                try {
+                    var entities = VIE.Util.rdf2Entities(service, results);
+                    entities = (_.isArray(entities))? entities : [ entities ];
+                    _.each(entities, function (entity) {
+                        entity.set("DBPediaServiceLoad", VIE.Util.xsdDateTime(new Date()));
+                    });
+                    entities = (entities.length === 1)? entities[0] : entities;
+                    loadable.resolve(entities);
+                } catch (e) {
+                    loadable.reject(e);
+                }
+            });
+        };
+        
+        var error = function (e) {
+            loadable.reject(e);
+        };
+        
+        var entities = (loadable.options.entity)? loadable.options.entity : loadable.options.entities;
+        
+        if (!entities) {
+            loadable.reject([]);
+        } else {
+            entities = (_.isArray(entities))? entities : [ entities ];
+            var tmpEntities = [];
+            for (var e = 0; e < entities.length; e++) {
+                var tmpEnt = (typeof entities[e] === "string")? entities[e] : entities[e].id;
+                tmpEntities.push(tmpEnt);
+            }
+                        
+            this.connector.load(tmpEntities, success, error);
+        }
+        return this;
+    }
+};
+
+// ## VIE.DBPediaConnector(options)
+// The DBPediaConnector is the connection between the DBPedia service
+// and the backend service.  
+// **Parameters**:  
+// *{object}* **options** The options.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.DBPediaConnector}* : The **new** VIE.DBPediaConnector instance.  
+// **Example usage**:  
+//
+//     var dbpConn = new vie.DBPediaConnector({<some-configuration>});
+VIE.prototype.DBPediaConnector = function (options) {
+    this.options = options;
+    this.baseUrl = "http://dbpedia.org/sparql?default-graph-uri=http%3A%2F%2Fdbpedia.org&timeout=0";
+};
+
+VIE.prototype.DBPediaConnector.prototype = {
+
+// ### load(uri, success, error, options)
+// This method loads all properties from an entity and returns the result by the success callback.  
+// **Parameters**:  
+// *{string}* **uri** The URI of the entity to be loaded.  
+// *{function}* **success** The success callback.  
+// *{function}* **error** The error callback.  
+// *{object}* **options** Options, like the ```format```.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.DBPediaConnector}* : The VIE.DBPediaConnector instance itself.  
+// **Example usage**:  
+//
+//     var dbpConn = new vie.DBPediaConnector(opts);
+//     dbpConn.load("<http://dbpedia.org/resource/Barack_Obama>",
+//                 function (res) { ... },
+//                 function (err) { ... });
+    load: function (uri, success, error, options) {
+        if (!options) { options = {}; }
+        
+        var url = this.baseUrl + 
+        "&format=" + encodeURIComponent("application/rdf+json") + 
+        "&query=";
+        
+        if (_.isArray(uri)) {
+            var construct = "";
+            var where = "";
+            for (var u = 0; u < uri.length; u++) {
+                var subject = (/^<.+>$/.test(uri[u]))? uri[u] : '<' + uri[u] + '>';
+                if (u > 0) {
+                    construct += " .";
+                    where += " UNION ";
+                }
+                construct += " " + subject + " ?prop" + u + " ?val" + u;
+                where     += " { " + subject + " ?prop" + u + " ?val" + u + " }";
+            }
+            url += encodeURIComponent("CONSTRUCT {" + construct + " } WHERE {" + where + " }");
+        } else {
+            uri = (/^<.+>$/.test(uri))? uri : '<' + uri + '>';
+            url += encodeURIComponent("CONSTRUCT { " + uri + " ?prop ?val } WHERE { " + uri + " ?prop ?val }");
+        }
+        var format = options.format || "application/rdf+json";
+
+        if (typeof exports !== "undefined" && typeof process !== "undefined") {
+            /* We're on Node.js, don't use jQuery.ajax */
+            return this._loadNode(url, success, error, options, format);
+        }
+
+        jQuery.ajax({
+            success: function(response){
+                success(response);
+            },
+            error: error,
+            type: "GET",
+            url: url,
+            accepts: {"application/rdf+json": "application/rdf+json"}
+        });
+        
+        return this;
+    },
+
+    _loadNode: function (uri, success, error, options, format) {
+        var request = require('request');
+        var r = request({
+            method: "GET",
+            uri: uri,
+            headers: {
+                Accept: format
+            }
+        }, function(err, response, body) {
+            if (response.statusCode !== 200) {
+              return error(body);
+            }
+            success(JSON.parse(body));
+        });
+        r.end();
+        
+        return 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/
+
+// ## VIE - OpenCalaisService service
+// The OpenCalaisService ...
+(function(){
+
+// ## VIE.OpenCalaisService(options)
+// This is the constructor to instantiate a new service to collect
+// properties of an entity from OpenCalais.  
+// **Parameters**:  
+// *{object}* **options** Optional set of fields, ```namespaces```, ```rules```, ```url```, or ```name```.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.OpenCalaisService}* : A **new** VIE.OpenCalaisService instance.  
+// **Example usage**:  
+//
+//     var service = new vie.OpenCalaisService({<some-configuration>});
+VIE.prototype.OpenCalaisService = function(options) {
+    var defaults = {
+        /* the default name of this service */
+        name : 'opencalais',
+        /* you can pass an array of URLs which are then tried sequentially */
+        url: ["http://api.opencalais.com/enlighten/rest/"],
+        timeout : 60000, /* 60 seconds timeout */
+        namespaces : {
+            opencalaisc:  "http://s.opencalais.com/1/pred/",
+            opencalaiscr: "http://s.opencalais.com/1/type/er/",
+            opencalaiscm: "http://s.opencalais.com/1/type/em/e/"
+        },
+        /* default rules that are shipped with this service */
+        rules : []
+    };
+    /* the options are merged with the default options */
+    this.options = jQuery.extend(true, defaults, options ? options : {});
+
+    this.vie = null; /* will be set via VIE.use(); */
+    /* overwrite options.name if you want to set another name */
+    this.name = this.options.name;
+    
+    /* basic setup for the ajax connection */
+    jQuery.ajaxSetup({
+        converters: {"text application/rdf+json": function(s){return JSON.parse(s);}},
+        timeout: this.options.timeout
+    });
+};
+
+VIE.prototype.OpenCalaisService.prototype = {
+    
+// ### init()
+// This method initializes certain properties of the service and is called
+// via ```VIE.use()```.  
+// **Parameters**:  
+// *nothing*  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolService}* : The VIE.StanbolService instance itself.  
+// **Example usage**:  
+//
+//     var service = new vie.OpenCalaisService({<some-configuration>});
+//     service.init();
+    init: function(){
+
+        for (var key in this.options.namespaces) {
+            var val = this.options.namespaces[key];
+            this.vie.namespaces.add(key, val);
+        }
+        
+        this.rules = jQuery.extend([], VIE.Util.transformationRules(this));
+       /* this.rules = jQuery.extend(this.rules, [{
+            'left' : [
+                      '?subject a opencalaiscm:Person',
+                      '?subject opencalaisc:name ?name'
+                ],
+                '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()
+                                })
+                            ];
+                    };
+                }(this.vie.namespaces)
+            }]);*/
+        this.rules = jQuery.merge(this.rules, (this.options.rules) ? this.options.rules : []);
+        //this.rules = [];
+        this.connector = new this.vie.OpenCalaisConnector(this.options);
+    },
+
+// ### analyze(analyzable)
+// This method extracts text from the jQuery element and sends it to OpenCalais for analysis.  
+// **Parameters**:  
+// *{VIE.Analyzable}* **analyzable** The analyzable.  
+// **Throws**:  
+// *{Error}* if an invalid VIE.Findable is passed.  
+// **Returns**:  
+// *{VIE.OpenCalaisService}* : The VIE.OpenCalaisService instance itself.  
+// **Example usage**:  
+//
+//     var service = new vie.OpenCalaisService({<some-configuration>});
+//     service.analyzable(
+//         new vie.Analyzable({element : jQuery("#foo")})
+//     );
+    analyze: function(analyzable) {
+        var service = this;
+
+        var correct = analyzable instanceof this.vie.Analyzable;
+        if (!correct) {throw "Invalid Analyzable passed";}
+
+        var element = analyzable.options.element ? analyzable.options.element : jQuery('body');
+
+        var text = service._extractText(element);
+
+        if (text.length > 0) {
+            /* query enhancer with extracted text */
+            var success = function (results) {
+                _.defer(function(){
+                    var entities = VIE.Util.rdf2Entities(service, results);
+                    analyzable.resolve(entities);
+                });
+            };
+            var error = function (e) {
+                analyzable.reject(e);
+            };
+
+            this.connector.analyze(text, success, error);
+
+        } else {
+            console.warn("No text found in element.");
+            analyzable.resolve([]);
+        }
+
+    },
+
+    // this private method extracts text from a jQuery element
+    _extractText: function (element) {
+        if (element.get(0) &&
+            element.get(0).tagName &&
+            (element.get(0).tagName == 'TEXTAREA' ||
+            element.get(0).tagName == 'INPUT' && element.attr('type', 'text'))) {
+            return element.get(0).val();
+        }
+        else {
+            var res = element
+                .text()    /* get the text of element */
+                .replace(/\s+/g, ' ') /* collapse multiple whitespaces */
+                .replace(/\0\b\n\r\f\t/g, ''); /* remove non-letter symbols */
+            return jQuery.trim(res);
+        }
+    }
+};
+
+// ## VIE.OpenCalaisConnector(options)
+// The OpenCalaisConnector is the connection between the VIE OpenCalais service
+// and the actual ajax calls.  
+// **Parameters**:  
+// *{object}* **options** The options.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.OpenCalaisService}* : The **new** VIE.OpenCalaisService instance.  
+// **Example usage**:  
+//
+//     var conn = new vie.OpenCalaisConnector({<some-configuration>});
+VIE.prototype.OpenCalaisConnector = function (options) {
+    this.options = options;
+    this.baseUrl = (_.isArray(options.url))? options.url : [ options.url ];
+    this.enhancerUrlPrefix = "/";
+};
+
+VIE.prototype.OpenCalaisConnector.prototype = {
+
+// ### analyze(text, success, error, options)
+// This method sends the given text to OpenCalais returns the result by the success callback.  
+// **Parameters**:  
+// *{string}* **text** The text to be analyzed.  
+// *{function}* **success** The success callback.  
+// *{function}* **error** The error callback.  
+// *{object}* **options** Options, like the ```format```.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.OpenCalaisConnector}* : The VIE.OpenCalaisConnector instance itself.  
+// **Example usage**:  
+//
+//     var conn = new vie.OpenCalaisConnector(opts);
+//     conn.analyze("This is some text.",
+//                 function (res) { ... },
+//                 function (err) { ... });
+    analyze: function(text, success, error, options) {
+        if (!options) { options = { urlIndex : 0}; }
+        if (options.urlIndex >= this.baseUrl.length) {
+            error("Could not connect to the given OpenCalais endpoints! Please check for their setup!");
+            return;
+        }
+        
+        var enhancerUrl = this.baseUrl[options.urlIndex].replace(/\/$/, '');
+        enhancerUrl += this.enhancerUrlPrefix;
+        
+        var format = options.format || "application/rdf+json";
+        
+        var retryErrorCb = function (c, t, s, e, o) {
+            /* in case a OpenCalais backend is not responding and
+             * multiple URLs have been registered
+             */
+            return  function () {
+                console.error("OpenCalais connection error", arguments);
+                c.analyze(t, s, e, _.extend(o, {urlIndex : o.urlIndex+1}));
+            };
+        }(this, text, success, error, options);
+        
+        var data = this._prepareData(text);
+
+        if (typeof exports !== "undefined" && typeof process !== "undefined") {
+            /* We're on Node.js, don't use jQuery.ajax */
+            return this._analyzeNode(enhancerUrl, data, success, retryErrorCb, options, format);
+        }
+
+        jQuery.ajax({
+            success: function(a, b, c){
+                var responseData = c.responseText.replace(/<!--[\s\S]*?-->/g, '');
+                success(responseData);
+            },
+            error: retryErrorCb,
+            type: "POST",
+            url: enhancerUrl,
+            data: data,
+            accept: "text/plain"
+        });
+    },
+
+    _analyzeNode: function(url, text, success, errorCB, options, format) {
+        var request = require('request');
+        var r = request({
+            method: "POST",
+            uri: url,
+            body: text,
+            headers: {
+                Accept: format
+            }
+        }, function(error, response, body) {
+            try {
+                success({results: JSON.parse(body)});
+            } catch (e) {
+                errorCB(e);
+            }
+        });
+        r.end();
+    },
+    
+    _prepareData : function (text) {
+        return {
+            licenseID: this.options.api_key,
+            calculareRelevanceScore: "true",
+            enableMetadataType: "GenericRelations,SocialTags",
+            contentType: "text/html",
+            content: text
+            // for more options check http://developer.opencalais.com/docs/suggest/
+        };
+    }
+};
+})();
+
+
+(function(){
+    
+    VIE.prototype.RdfaRdfQueryService = function(options) {
+        var defaults = {
+            name : 'rdfardfquery',
+            namespaces : {},
+            rules : []
+        };
+        /* the options are merged with the default options */
+        this.options = jQuery.extend(true, defaults, options ? options : {});
+
+        this.views = [];
+
+        this.vie = null; /* will be set via VIE.use(); */
+        /* overwrite options.name if you want to set another name */
+        this.name = this.options.name;
+};
+
+VIE.prototype.RdfaRdfQueryService.prototype = {
+
+    init: function(){
+
+        for (var key in this.options.namespaces) {
+            var val = this.options.namespaces[key];
+            this.vie.namespaces.add(key, val);
+        }
+        
+        this.rules = jQuery.extend([], VIE.Util.transformationRules(this));
+        this.rules = jQuery.merge(this.rules, (this.options.rules) ? this.options.rules : []);
+    },
+        
+    analyze: function(analyzable) {
+        // in a certain way, analyze is the same as load
+        return this.load(analyzable);
+    },
+        
+    load : function(loadable) {
+        var service = this;
+        var correct = loadable instanceof this.vie.Loadable || loadable instanceof this.vie.Analyzable;
+        if (!correct) {
+            throw new Error("Invalid Loadable/Analyzable passed");
+        }
+        
+        var element = loadable.options.element ? loadable.options.element : jQuery(document);
+        try {
+            var rdf = jQuery(element).find("[about],[typeof]").rdfa();
+            
+            jQuery.each(jQuery(element).xmlns(), function(prefix, ns){
+                service.vie.namespaces.addOrReplace(prefix, ns.toString());
+            });
+            
+            var entities = VIE.Util.rdf2Entities(this, rdf);
+            
+            loadable.resolve(entities);
+        } catch (e) {
+            loadable.reject(e);
+        }
+    },
+
+    save : function(savable) {
+        var correct = savable instanceof this.vie.Savable;
+        if (!correct) {
+            savable.reject("Invalid Savable passed");
+        }
+    
+        if (!savable.options.element) {
+            savable.reject("Unable to write entity to RDFa, no element given");
+        }
+    
+        if (!savable.options.entity) {
+            savable.reject("Unable to write to RDFa, no entity given");
+        }
+        
+        if (!jQuery.rdf) {
+            savable.reject("No rdfQuery found.");
+        }
+        var entity = savable.options.entity;
+        
+        var triples = [];
+        var type = entity.get('@type');
+        type = (jQuery.isArray(type))? type[0] : type;
+        type = type.id;
+        triples.push(entity.getSubject() + " a " + type);
+        //TODO: add all attributes!
+        jQuery(savable.options.element).rdfa(triples);
+    
+        savable.resolve();
+    }
+    
+};
+
+})();
+//     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 - RdfaService service
+// The RdfaService service allows ...
+/*global document:false */
+
+(function(){
+
+// ## VIE.RdfaService(options)
+// This is the constructor to instantiate a new service.  
+// **Parameters**:  
+// *{object}* **options** Optional set of fields, ```namespaces```, ```rules```, ```url```, or ```name```.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.RdfaService}* : A **new** VIE.RdfaService instance.  
+// **Example usage**:  
+//
+//     var rdfaService = new vie.RdfaService({<some-configuration>});
+VIE.prototype.RdfaService = function(options) {
+    var defaults = {
+        name : 'rdfa',
+        namespaces : {},
+        subjectSelector : "[about],[typeof],[src],html",
+        predicateSelector : "[property],[rel]",
+        /* default rules that are shipped with this service */
+        rules : []
+    };
+    /* the options are merged with the default options */
+    this.options = jQuery.extend(true, defaults, options ? options : {});
+
+    this.views = [];
+    this.templates = {};
+
+    this.datatypeReaders = {
+      '<http://www.w3.org/2001/XMLSchema#boolean>': function (value) {
+        if (value === 'true' || value === 1 || value === true) {
+          return true;
+        }
+        return false;
+      },
+      '<http://www.w3.org/2001/XMLSchema#dateTime>': function (value) {
+        return new Date(value);
+      },
+      '<http://www.w3.org/2001/XMLSchema#integer>': function (value) {
+        return parseInt(value, 10);
+      }
+    };
+
+    this.datatypeWriters = {
+      '<http://www.w3.org/2001/XMLSchema#dateTime>': function (value) {
+        if (!_.isDate(value)) {
+          return value;
+        }
+        return value.toISOString();
+      }
+    };
+
+    this.vie = null; /* will be set via VIE.use(); */
+    /* overwrite options.name if you want to set another name */
+    this.name = this.options.name;
+};
+
+VIE.prototype.RdfaService.prototype = {
+    
+// ### init()
+// This method initializes certain properties of the service and is called
+// via ```VIE.use()```.  
+// **Parameters**:  
+// *nothing*  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.RdfaService}* : The VIE.RdfaService instance itself.  
+// **Example usage**:  
+//
+//     var rdfaService = new vie.RdfaService({<some-configuration>});
+//     rdfaService.init();
+    init: function(){
+
+        for (var key in this.options.namespaces) {
+            var val = this.options.namespaces[key];
+            this.vie.namespaces.add(key, val);
+        }
+        
+        this.rules = jQuery.merge([], VIE.Util.transformationRules(this));
+        this.rules = jQuery.merge(this.rules, (this.options.rules) ? this.options.rules : []);
+    },
+    
+    analyze: function(analyzable) {
+        // in a certain way, analyze is the same as load
+        return this.load(analyzable);
+    },
+        
+    load : function(loadable) {
+        var service = this;
+        var correct = loadable instanceof this.vie.Loadable || loadable instanceof this.vie.Analyzable;
+        if (!correct) {
+            throw new Error("Invalid Loadable/Analyzable passed");
+        }
+
+        var element;
+        if (!loadable.options.element) {
+            if (typeof document === 'undefined') { 
+                return loadable.resolve([]);
+            }
+            element = jQuery(document);
+        } else {
+            element = loadable.options.element;
+        }
+
+        var entities = this.readEntities(element);
+        loadable.resolve(entities);
+    },
+
+    save : function(savable) {
+        var correct = savable instanceof this.vie.Savable;
+        if (!correct) {
+            throw "Invalid Savable passed";
+        }
+    
+        if (!savable.options.element) {
+            // FIXME: we could find element based on subject
+            throw "Unable to write entity to RDFa, no element given";
+        }
+    
+        if (!savable.options.entity) {
+            throw "Unable to write to RDFa, no entity given";
+        }
+    
+        this._writeEntity(savable.options.entity, savable.options.element);
+        savable.resolve();
+    },
+
+    readEntities : function (element) {
+        var service = this;
+        var ns = this.xmlns(element);
+        for (var prefix in ns) {
+            this.vie.namespaces.addOrReplace(prefix, ns[prefix]);
+        }
+        var entities = [];
+        var entityElements = jQuery(this.options.subjectSelector, element).add(jQuery(element).filter(this.options.subjectSelector)).each(function() {
+            var entity = service._readEntity(jQuery(this));
+            if (entity) {
+                entities.push(entity);
+            }
+        });
+        return entities;
+    },
+    
+    _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;
+        }
+        var vie = this.vie;
+        _.each(entity, function (value, predicate) {
+            if (!_.isArray(value)) {
+                return;
+            }
+            var valueCollection = new this.vie.Collection([], {
+              vie: vie,
+              predicate: predicate
+            });
+            _.each(value, function (valueItem) {
+                var linkedEntity = vie.entities.addOrUpdate({'@subject': valueItem});
+                valueCollection.addOrUpdate(linkedEntity);
+            });
+            entity[predicate] = valueCollection;
+        }, this);
+        entity['@subject'] = subject;
+        if (type) {
+            entity['@type'] = type;
+        }
+        var entityInstance = new this.vie.Entity(entity);
+        entityInstance = this.vie.entities.addOrUpdate(entityInstance, {
+          updateOptions: {
+            silent: true
+          }
+        });
+        this._registerEntityView(entityInstance, element);
+        return entityInstance;
+    },
+    
+    _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;
+            }
+            service.writeElementValue(predicate, predicateElement, value);
+        });
+        return true;
+    },
+    
+    _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;
+        }
+
+        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);
+
+        // For new elements, ensure their relations are read from DOM
+        if (isNew) {
+          jQuery(element).find(this.options.predicateSelector).add(jQuery(element).filter(this.options.predicateSelector)).each(function () {
+            var predicate = jQuery(this).attr('rel');
+            if (!predicate) {
+              return;
+            }
+            entity.set(predicate, new service.vie.Collection([], {
+              vie: service.vie,
+              predicate: predicate
+            }));
+          });
+        }
+    
+        // Find collection elements and create collection views for them
+        _.each(entity.attributes, function(value, predicate) {
+            var attributeValue = entity.fromReference(entity.get(predicate));
+            if (attributeValue && attributeValue.isCollection) {
+                jQuery.each(service.getElementByPredicate(predicate, element), function() {
+                    service._registerCollectionView(attributeValue, jQuery(this), entity);
+                });
+            }
+        });
+        return viewInstance;
+    },
+
+    setTemplate: function (type, predicate, template) {
+      var templateFunc;
+
+      if (!template) {
+        template = predicate;
+        predicate = 'default';
+      }
+      type = this.vie.namespaces.isUri(type) ? type : this.vie.namespaces.uri(type);
+
+      if (_.isFunction(template)) {
+        templateFunc = template;
+      } else {
+        templateFunc = this.getElementTemplate(template);
+      }
+
+      if (!this.templates[type]) {
+        this.templates[type] = {};
+      }
+
+      this.templates[type][predicate] = templateFunc;
+
+      // Update existing Collection Views where this template applies
+      _.each(this.views, function (view) {
+        if (!(view instanceof this.vie.view.Collection)) {
+          return;
+        }
+
+        if (view.collection.predicate !== predicate) {
+          return;
+        }
+
+        view.templates[type] = templateFunc;
+      }, this);
+    },
+
+    getTemplate: function (type, predicate) {
+      if (!predicate) {
+        predicate = 'default';
+      }
+      type = this.vie.namespaces.isUri(type) ? type : this.vie.namespaces.uri(type);
+
+      if (!this.templates[type]) {
+        return;
+      }
+
+      return this.templates[type][predicate];
+    },
+
+    _getElementTemplates: function (element, entity, predicate) {
+      var templates = {};
+
+      var type = entity.get('@type');
+      if (type && type.attributes && type.attributes.get(predicate)) {
+        // Use type-specific templates, if any
+        var attribute = type.attributes.get(predicate);
+        _.each(attribute.range, function (childType) {
+          var template = this.getTemplate(childType, predicate);
+          if (template) {
+            var vieChildType = this.vie.types.get(childType);
+            templates[vieChildType.id] = template;
+          }
+        }, this);
+
+        if (!_.isEmpty(templates)) {
+          return templates;
+        }
+      }
+
+      // Try finding templates that have types
+      var self = this;
+      jQuery('[typeof]', element).each(function () {
+        var templateElement = jQuery(this);
+        var childType = templateElement.attr('typeof');
+        childType = self.vie.namespaces.isUri(childType) ? childType : self.vie.namespaces.uri(childType);
+        if (templates[childType]) {
+          return;
+        }
+        var templateFunc = self.getElementTemplate(templateElement);
+        templates[childType] = templateFunc;
+        templates['<http://www.w3.org/2002/07/owl#Thing>'] = templateFunc;
+      });
+
+      if (_.isEmpty(templates)) {
+        var defaultTemplate = element.children(':first-child');
+        if (defaultTemplate.length) {
+          templates['<http://www.w3.org/2002/07/owl#Thing>'] = self.getElementTemplate(defaultTemplate);
+        }
+      }
+
+      return templates;
+    },
+
+    // Return a template-generating function for given element
+    getElementTemplate: function (element) {
+        var service = this;
+        return function (entity, callback) {
+            var newElement = jQuery(element).clone(false);
+            if (newElement.attr('about') !== undefined) {
+                // Direct match with container element
+                newElement.attr('about', '');
+            }
+            newElement.find('[about]').attr('about', '');
+            var subject = service.findPredicateElements(subject, newElement, false).each(function () {
+                var predicateElement = jQuery(this);
+                var predicate = service.getElementPredicate(predicateElement);
+                if (entity.has(predicate) && entity.get(predicate).isCollection) {
+                    return true;
+                }
+                service.writeElementValue(null, predicateElement, '');
+            });
+            callback(newElement);
+        };
+    },
+    
+    _registerCollectionView : function(collection, element, entity) {
+        var viewInstance = this._getViewForElement(element, true);
+        if (viewInstance) {
+            return viewInstance;
+        }
+    
+        viewInstance = new this.vie.view.Collection({
+            owner: entity,
+            collection: collection,
+            model: collection.model,
+            el: element,
+            templates: this._getElementTemplates(element, entity, collection.predicate),
+            service: this
+        });
+        this.views.push(viewInstance);
+        return viewInstance;
+    },
+    
+    _getElementType : function (element) {
+        var type;
+        if (jQuery(element).attr('typeof') !== this.options.attributeExistenceComparator) {
+            type = jQuery(element).attr('typeof');
+            if (type.indexOf("://") !== -1) {
+                return "<" + type + ">";
+            } else {
+                return type;
+            }
+        }
+        return null;
+    },
+    
+    getElementSubject : function(element) {
+        var service = this;
+        if (typeof document !== 'undefined') { 
+            if (element === document) {
+                return document.baseURI;
+            }
+        }
+        var subject;
+        var matched = null;
+        jQuery(element).closest(this.options.subjectSelector).each(function() {
+            matched = this;
+            if (jQuery(this).attr('about') !== service.options.attributeExistenceComparator) {
+                subject = jQuery(this).attr('about');
+                return true;
+            }
+            if (jQuery(this).attr('src') !== service.options.attributeExistenceComparator) {
+                subject = jQuery(this).attr('src');
+                return true;
+            }
+            if (jQuery(this).attr('typeof') !== service.options.attributeExistenceComparator) {
+                return true;
+            }
+            // We also handle baseURL outside browser context by manually
+            // looking for the `<base>` element inside HTML head.
+            if (jQuery(this).get(0).nodeName === 'HTML') {
+                jQuery('base', this).each(function() {
+                    subject = jQuery(this).attr('href');
+                });
+            }
+        });
+
+        if (!subject) {
+            if (matched === element) {
+                // Workaround for https://github.com/assaf/zombie/issues/235
+                return service.getElementSubject(jQuery(element).parent());
+            }
+            return undefined;
+        }
+                
+        if (typeof subject === 'object') {
+            return subject;
+        }
+        if (subject.indexOf('_:') === 0) {
+            return subject;
+        }
+        if (subject.indexOf('<') === 0) {
+            return subject;
+        }
+        return "<" + subject + ">";
+    },
+    
+    setElementSubject : function(subject, element) {
+        if (jQuery(element).attr('src')) {
+            return jQuery(element).attr('src', subject);
+        }
+        return jQuery(element).attr('about', subject);
+    },
+    
+    getElementPredicate : function(element) {
+        var predicate;
+        element = jQuery(element);
+        predicate = element.attr('property');
+        if (!predicate) {
+            predicate = element.attr('rel');
+        }
+        return predicate;
+    },
+    
+    getElementBySubject : function(subject, element) {
+        var service = this;
+        return jQuery(element).find(this.options.subjectSelector).add(jQuery(element).filter(this.options.subjectSelector)).filter(function() {
+            if (service.getElementSubject(jQuery(this)) !== subject) {
+                return false;
+            }
+     
+            return true;
+        });
+    },
+    
+    getElementByPredicate : function(predicate, element) {
+        var service = this;
+        var subject = this.getElementSubject(element);
+        return jQuery(element).find(this.options.predicateSelector).add(jQuery(element).filter(this.options.predicateSelector)).filter(function() {
+            var foundPredicate = service.getElementPredicate(jQuery(this));
+            if (service.vie.namespaces.curie(foundPredicate) !== service.vie.namespaces.curie(predicate)) {
+                return false;
+            }
+    
+            if (service.getElementSubject(this) !== subject) {
+                return false;
+            }
+     
+            return true;
+        });
+    },
+    
+    _readEntityPredicates : function(subject, element, emptyValues) {
+        var service = this;
+        var entityPredicates = {};
+    
+        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;
+        });
+    
+        if (jQuery(element).get(0).tagName !== 'HTML') {
+            jQuery(element).parent('[rev]').each(function() {
+                var relation = jQuery(this).attr('rev');
+                if (!relation) {
+                    return;
+                }
+                entityPredicates[jQuery(this).attr('rev')] = service.getElementSubject(this); 
+            });
+        }
+        return entityPredicates;
+    },
+
+    findSubjectElements: function (element) {
+      return jQuery('[about]', element);
+    },
+    
+    findPredicateElements : function(subject, element, allowNestedPredicates) {
+        var service = this;
+        return jQuery(element).find(this.options.predicateSelector).add(jQuery(element).filter(this.options.predicateSelector)).filter(function() {
+            if (service.getElementSubject(this) !== subject) {
+                return false;
+            }
+            if (!allowNestedPredicates) {
+                if (!jQuery(this).parents('[property]').length) {
+                    return true;
+                }
+                return false;
+            }
+    
+            return true;
+        });
+    },
+
+    parseElementValue: function (value, element) {
+        if (!element.attr('datatype')) {
+            return value;
+        }
+        var datatype = this.vie.namespaces.uri(element.attr('datatype'));
+        if (!this.datatypeReaders[datatype]) {
+            return value;
+        }
+        return this.datatypeReaders[datatype](value);
+    },
+
+    generateElementValue: function (value, element) {
+        if (!element.attr('datatype')) {
+            return value;
+        }
+        var datatype = this.vie.namespaces.uri(element.attr('datatype'));
+        if (!this.datatypeWriters[datatype]) {
+            return value;
+        }
+        return this.datatypeWriters[datatype](value);
+    },
+
+    readElementValue : function(predicate, element) {
+        // The `content` attribute can be used for providing machine-readable
+        // values for elements where the HTML presentation differs from the
+        // actual value.
+        var content = element.attr('content');
+        if (content) {
+            return this.parseElementValue(content, element);
+        }
+                
+        // The `resource` attribute can be used to link a predicate to another
+        // RDF resource.
+        var resource = element.attr('resource');
+        if (resource) {
+            return ["<" + resource + ">"];
+        }
+                
+        // `href` attribute also links to another RDF resource.
+        var href = element.attr('href');
+        if (href && element.attr('rel') === predicate) {
+            return ["<" + href + ">"];
+        }
+    
+        // If the predicate is a relation, we look for identified child objects
+        // and provide their identifiers as the values. To protect from scope
+        // creep, we only support direct descentants of the element where the
+        // `rel` attribute was set.
+        if (element.attr('rel')) {
+            var value = [];
+            var service = this;
+            jQuery(element).children(this.options.subjectSelector).each(function() {
+                value.push(service.getElementSubject(this));
+            });
+            return value;
+        }
+    
+        // If none of the checks above matched we return the HTML contents of
+        // the element as the literal value.
+        return this.parseElementValue(element.html(), element);
+    },
+    
+    writeElementValue : function(predicate, element, value) {
+        value = this.generateElementValue(value, element);
+
+        //TODO: this is a hack, please fix!
+        if (_.isArray(value) && value.length > 0) {
+            value = value[0];
+        }
+        
+        // The `content` attribute can be used for providing machine-readable
+        // values for elements where the HTML presentation differs from the
+        // actual value.
+        var content = element.attr('content');
+        if (content) {
+            element.attr('content', value);
+            return;
+        }
+                
+        // The `resource` attribute can be used to link a predicate to another
+        // RDF resource.
+        var resource = element.attr('resource');
+        if (resource) {
+            element.attr('resource', value);
+        }
+    
+        // Property has inline value. Change the HTML contents of the property
+        // element to match the new value.
+        element.html(value);
+    },
+    
+    // mostyl copied from http://code.google.com/p/rdfquery/source/browse/trunk/jquery.xmlns.js
+    xmlns : function (elem) {
+        var $elem;
+        if (!elem) {
+            if (typeof document === 'undefined') { 
+                return {};
+            }
+            $elem = jQuery(document);
+        } else {
+            $elem = jQuery(elem);
+        }
+        // Collect namespace definitions from the element and its parents
+        $elem = $elem.add($elem.parents());
+        var obj = {};
+
+        $elem.each(function (i, e) {
+            if (e.attributes) {
+                for (i = 0; i < e.attributes.length; i += 1) {
+                    var attr = e.attributes[i];
+                    if (/^xmlns(:(.+))?$/.test(attr.nodeName)) {
+                        var prefix = /^xmlns(:(.+))?$/.exec(attr.nodeName)[2] || '';
+                        var value = attr.nodeValue;
+                        if (prefix === '' || value !== '') {
+                            obj[prefix] = attr.nodeValue;
+                        }
+                    }
+                }
+            }
+        });
+        
+        return obj;
+    }
+
+};
+
+})();
+//     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 escape:false */
+
+// ## VIE - StanbolService service
+// The StanbolService service allows a VIE developer to directly query
+// the <a href="http://incubator.apache.org/stanbol/">Apache Stanbol</a> entityhub for entities and their properties. 
+// Furthermore, it gives access to the enhance facilities of
+// Stanbol to analyze content and semantically enrich it.
+(function(){
+
+// ## VIE.StanbolService(options)
+// This is the constructor to instantiate a new service to collect
+// properties of an entity from <a href="http://incubator.apache.org/stanbol/">Apache Stanbol</a>.  
+// **Parameters**:  
+// *{object}* **options** Optional set of fields, ```namespaces```, ```rules```, ```url```, or ```name```.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolService}* : A **new** VIE.StanbolService instance.  
+// **Example usage**:  
+//
+//     var stnblService = new vie.StanbolService({<some-configuration>});
+VIE.prototype.StanbolService = function(options) {
+    var defaults = {
+        /* the default name of this service */
+        name : 'stanbol',
+        /* you can pass an array of URLs which are then tried sequentially */
+        url: ["http://dev.iks-project.eu/stanbolfull"],
+        timeout : 20000, /* 20 seconds timeout */
+        namespaces : {
+            semdeski : "http://www.semanticdesktop.org/ontologies/2007/01/19/nie#",
+            semdeskf : "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#",
+            skos: "http://www.w3.org/2004/02/skos/core#",
+            foaf: "http://xmlns.com/foaf/0.1/",
+            opengis: "http://www.opengis.net/gml/",
+            dbpedia: "http://dbpedia.org/ontology/",
+            dbprop: "http://dbpedia.org/property/",
+            owl : "http://www.w3.org/2002/07/owl#",
+            geonames : "http://www.geonames.org/ontology#",
+            enhancer : "http://fise.iks-project.eu/ontology/",
+            entityhub: "http://www.iks-project.eu/ontology/rick/model/",
+            entityhub2: "http://www.iks-project.eu/ontology/rick/query/",
+            rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+            rdfs: "http://www.w3.org/2000/01/rdf-schema#",
+            dcterms  : 'http://purl.org/dc/terms/',
+            schema: 'http://schema.org/',
+            geo: 'http://www.w3.org/2003/01/geo/wgs84_pos#'
+        },
+        /* default rules that are shipped with this service */
+        rules : [
+            /* rule to add backwards-relations to the triples
+             * this makes querying for entities a lot easier!
+             */
+            {
+                'left' : [
+                    '?subject a <http://fise.iks-project.eu/ontology/EntityAnnotation>',
+                    '?subject enhancer:entity-type ?type',
+                    '?subject enhancer:confidence ?confidence',
+                    '?subject enhancer:entity-reference ?entity',
+                    '?subject dcterms:relation ?relation',
+                    '?relation a <http://fise.iks-project.eu/ontology/TextAnnotation>',
+                    '?relation enhancer:selected-text ?selected-text',
+                    '?relation enhancer:selection-context ?selection-context',
+                    '?relation enhancer:start ?start',
+                    '?relation enhancer:end ?end'
+                ],
+                'right' : [
+                    '?entity a ?type',
+                    '?entity enhancer:hasTextAnnotation ?relation',
+                    '?entity enhancer:hasEntityAnnotation ?subject'
+                ]
+            }
+        ],
+        enhancer : {
+            chain : "default"
+        },
+        entityhub : {
+            /* if set to undefined, the Referenced Site Manager @ /entityhub/sites is used. */
+            /* if set to, e.g., dbpedia, eferenced Site @ /entityhub/site/dbpedia is used. */
+            site : undefined
+        }
+    };
+    /* the options are merged with the default options */
+    this.options = jQuery.extend(true, defaults, options ? options : {});
+
+    this.vie = null; /* will be set via VIE.use(); */
+    /* overwrite options.name if you want to set another name */
+    this.name = this.options.name;
+    
+};
+
+VIE.prototype.StanbolService.prototype = {
+    
+// ### init()
+// This internal method initializes certain properties of the service and is called
+// via ```VIE.use()```.  
+// **Parameters**:  
+// *nothing*  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolService}* : The VIE.StanbolService instance itself.  
+    init: function(){
+
+        for (var key in this.options.namespaces) {
+            var val = this.options.namespaces[key];
+            this.vie.namespaces.add(key, val);
+        }
+        
+        this.rules = jQuery.extend([], VIE.Util.transformationRules(this));
+        this.rules = jQuery.merge(this.rules, (this.options.rules) ? this.options.rules : []);
+        
+        this.connector = new this.vie.StanbolConnector(this.options);
+
+        /* adding these entity types to VIE helps later the querying */
+        this.vie.types.addOrOverwrite('enhancer:EntityAnnotation', [
+            /*TODO: add attributes */
+        ]).inherit("owl:Thing");
+        this.vie.types.addOrOverwrite('enhancer:TextAnnotation', [
+            /*TODO: add attributes */
+        ]).inherit("owl:Thing");
+        this.vie.types.addOrOverwrite('enhancer:Enhancement', [
+            /*TODO: add attributes */
+        ]).inherit("owl:Thing");
+    },
+
+// ### analyze(analyzable)
+// This method extracts text from the jQuery element and sends it to Apache Stanbol for analysis.  
+// **Parameters**:  
+// *{VIE.Analyzable}* **analyzable** The analyzable.  
+// **Throws**:  
+// *{Error}* if an invalid VIE.Findable is passed.  
+// **Returns**:  
+// *{VIE.StanbolService}* : The VIE.StanbolService instance itself.  
+// **Example usage**:  
+//
+//     vie.analyze({element : jQuery("#foo")})
+//     .using(new vie.StanbolService({<some-configuration>}))
+//     .execute().success(callback);
+    analyze: function(analyzable) {
+        var service = this;
+
+        var correct = analyzable instanceof this.vie.Analyzable;
+        if (!correct) {throw "Invalid Analyzable passed";}
+
+        var element = analyzable.options.element ? analyzable.options.element : jQuery('body');
+
+        var text = service._extractText(element);
+
+        if (text.length > 0) {
+            /* query enhancer with extracted text */
+            var success = function (results) {
+                _.defer(function(){
+                    var entities = VIE.Util.rdf2Entities(service, results);
+                    analyzable.resolve(entities);
+                });
+            };
+            var error = function (e) {
+                analyzable.reject(e);
+            };
+            
+            var options = {
+                chain : (analyzable.options.chain)? analyzable.options.chain : service.options.enhancer.chain
+            };
+
+            this.connector.analyze(text, success, error, options);
+
+        } else {
+            console.warn("No text found in element.");
+            analyzable.resolve([]);
+        }
+
+    },
+
+// ### find(findable)
+// This method finds entities given the term from the entity hub.  
+// **Parameters**:  
+// *{VIE.Findable}* **findable** The findable.  
+// **Throws**:  
+// *{Error}* if an invalid VIE.Findable is passed.  
+// **Returns**:  
+// *{VIE.StanbolService}* : The VIE.StanbolService instance itself.  
+// **Example usage**:  
+//
+//     vie.find({
+//         term : "Bischofsh",
+//         limit : 10,
+//         offset: 0,
+//         field: "skos:prefLabel", // used for the term lookup, default: "rdfs:label"
+//         properties: ["skos:prefLabel", "rdfs:label"] // are going to be loaded with the result entities
+//     })
+//     .using(new vie.StanbolService({<some-configuration>}))
+//     .execute()
+//     .success(callback);
+    find: function (findable) {
+        var correct = findable instanceof this.vie.Findable;
+        if (!correct) {throw "Invalid Findable passed";}
+        var service = this;
+        /* The term to find, * as wildcard allowed */
+        if (!findable.options.term) {
+            console.info("StanbolConnector: No term to look for!");
+            findable.reject([]);
+        }
+        var term = escape(findable.options.term);
+        var limit = (typeof findable.options.limit === "undefined") ? 20 : findable.options.limit;
+        var offset = (typeof findable.options.offset === "undefined") ? 0 : findable.options.offset;
+        var success = function (results) {
+            _.defer(function(){
+                var entities = VIE.Util.rdf2Entities(service, results);
+                findable.resolve(entities);
+            });
+        };
+        var error = function (e) {
+            findable.reject(e);
+        };
+        
+        findable.options.site = (findable.options.site)? findable.options.site : service.options.entityhub.site;
+        
+        var vie = this.vie;
+        if(findable.options.properties){
+            var properties = findable.options.properties;
+            findable.options.ldPath = _(properties)
+            .map(function(property){
+                if (vie.namespaces.isCurie(property)){
+                    return vie.namespaces.uri(property) + ";";
+                } else {
+                    return property;
+                }
+            })
+            .join("");
+        }
+        if(findable.options.field && vie.namespaces.isCurie(field)){
+            var field = findable.options.field;
+                findable.options.field = vie.namespaces.uri(field);
+        }
+        this.connector.find(term, limit, offset, success, error, findable.options);
+    },
+
+// ### load(loadable)
+// This method loads the entity that is stored within the loadable into VIE.  
+// **Parameters**:  
+// *{VIE.Loadable}* **lodable** The loadable.  
+// **Throws**:  
+// *{Error}* if an invalid VIE.Loadable is passed.  
+// **Returns**:  
+// *{VIE.StanbolService}* : The VIE.StanbolService instance itself.  
+// **Example usage**:  
+//     vie.load({
+//         entity: "<http://...>"
+//     })
+//     .using(new vie.StanbolService({<some-configuration>}))
+//     .execute()
+//     .success(callback);
+    load: function(loadable){
+        var correct = loadable instanceof this.vie.Loadable;
+        if (!correct) {throw "Invalid Loadable passed";}
+        var service = this;
+
+        var entity = loadable.options.entity;
+        if (!entity){
+            console.warn("StanbolConnector: No entity to look for!");
+            loadable.resolve([]);
+        }
+        var success = function (results) {
+            _.defer(function(){
+                var entities = VIE.Util.rdf2Entities(service, results);
+                loadable.resolve(entities);
+            });
+        };
+        var error = function (e) {
+            loadable.reject(e);
+        };
+        
+        var options = {
+            site : (loadable.options.site)? loadable.options.site : service.options.entityhub.site,
+            local : loadable.options.local
+        };
+        
+        this.connector.load(entity, success, error, options);
+    },
+
+ // ### save(savable)
+ // This method saves the given entity to the Apache Stanbol installation.  
+ // **Parameters**:  
+ // *{VIE.Savable}* **savable** The savable.  
+ // **Throws**:  
+ // *{Error}* if an invalid VIE.Savable is passed.  
+ // **Returns**:  
+ // *{VIE.StanbolService}* : The VIE.StanbolService instance itself.  
+ // **Example usage**:  
+ //
+ //      var entity = new vie.Entity({'name' : 'Test Entity'});
+ //      var stnblService = new vie.StanbolService({<some-configuration>});
+ //      stnblService.save(new vie.Savable(entity));
+     save: function(savable){
+         var correct = savable instanceof this.vie.Savable;
+         if (!correct) {throw "Invalid Savable passed";}
+         var service = this;
+
+         var entity = savable.options.entity;
+         if (!entity){
+             console.warn("StanbolConnector: No entity to save!");
+             savable.reject("StanbolConnector: No entity to save!");
+         }
+         var success = function (results) {
+             _.defer(function() {
+                 var entities = VIE.Util.rdf2Entities(service, results);
+                 savable.resolve(entities);
+             });
+         };
+         
+         var error = function (e) {
+             savable.reject(e);
+         };
+         
+         var options = {
+            site : (savable.options.site)? savable.options.site : service.options.entityhub.site,
+            local : savable.options.local
+         };
+         
+         this.connector.save(entity, success, error, options);
+     },
+
+    /* this private method extracts text from a jQuery element */
+    _extractText: function (element) {
+        if (element.get(0) &&
+            element.get(0).tagName &&
+            (element.get(0).tagName == 'TEXTAREA' ||
+            element.get(0).tagName == 'INPUT' && element.attr('type', 'text'))) {
+            return element.get(0).val();
+        }
+        else {
+            var res = element
+                .text()    /* get the text of element */
+                .replace(/\s+/g, ' ') /* collapse multiple whitespaces */
+                .replace(/\0\b\n\r\f\t/g, ''); /* remove non-letter symbols */
+            return jQuery.trim(res);
+        }
+    }
+};
+
+// ## VIE.StanbolConnector(options)
+// The StanbolConnector is the connection between the VIE Stanbol service
+// and the actual ajax calls.  
+// **Parameters**:  
+// *{object}* **options** The options.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolConnector}* : The **new** VIE.StanbolConnector instance.  
+// **Example usage**:  
+//
+//     var stnblConn = new vie.StanbolConnector({<some-configuration>});
+VIE.prototype.StanbolConnector = function (options) {
+    
+    var defaults =  {
+        /* you can pass an array of URLs which are then tried sequentially */
+        url: ["http://dev.iks-project.eu/stanbolfull"],
+        timeout : 20000, /* 20 seconds timeout */
+        enhancer : {
+            urlPostfix : "/enhancer",
+            chain : "default"
+        },
+        entityhub : {
+            /* if set to undefined, the Referenced Site Manager @ /entityhub/sites is used. */
+            /* if set to, e.g., dbpedia, referenced Site @ /entityhub/site/dbpedia is used. */
+            site : undefined,
+            urlPostfix : "/entityhub",
+            local : false
+        },
+        sparql : {
+            urlPostfix : "/sparql"
+        },
+        contenthub : {
+            urlPostfix : "/contenthub",
+            index : "contenthub"
+        },
+        ontonet : {
+            urlPostfix : "/ontonet"
+        },
+        factstore : {
+            urlPostfix : "/factstore"
+        },
+        rules : {
+            urlPostfix : "/rules"
+        },
+        cmsadapter : {
+            urlPostfix : "/cmsadapter"
+        }
+    };
+
+    /* the options are merged with the default options */
+    this.options = jQuery.extend(true, defaults, options ? options : {});
+    this.options.url = (_.isArray(this.options.url))? this.options.url : [ this.options.url ];
+    
+    this._init();
+
+    this.baseUrl = (_.isArray(options.url))? options.url : [ options.url ];
+};
+
+VIE.prototype.StanbolConnector.prototype = {
+        
+// ### _init()
+// Basic setup of the stanbol connector.  This is called internally by the constructor!
+// **Parameters**:  
+// *nothing*
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolConnector}* : The VIE.StanbolConnector instance itself. 
+    _init : function () {
+        var connector = this;
+        
+        /* basic setup for the ajax connection */
+        jQuery.ajaxSetup({
+            converters: {"text application/rdf+json": function(s){return JSON.parse(s);}},
+            timeout: connector.options.timeout
+        });
+        
+        return this;
+    },
+    
+    _iterate : function (params) {
+        if (!params) { return; }
+        
+        if (params.urlIndex >= this.options.url.length) {
+            params.error.call(this, "Could not connect to the given Stanbol endpoints! Please check for their setup!");
+            return;
+        }
+        
+        var retryErrorCb = function (c, p) {
+            /* in case a Stanbol backend is not responding and
+             * multiple URLs have been registered
+             */
+            return function () {
+                console.log("Stanbol connection error", arguments);
+                p.urlIndex = p.urlIndex+1;
+                c._iterate(p);
+            };
+        }(this, params);
+
+        if (typeof exports !== "undefined" && typeof process !== "undefined") {
+            /* We're on Node.js, don't use jQuery.ajax */
+            return params.methodNode.call(
+                    this, 
+                    params.url.call(this, params.urlIndex, params.args.options),
+                    params.args,
+                    params.success,
+                    retryErrorCb);
+        }
+        
+        return params.method.call(
+                this, 
+                params.url.call(this, params.urlIndex, params.args.options),
+                params.args,
+                params.success,
+                retryErrorCb);
+    },
+
+// ### analyze(text, success, error, options)
+// This method sends the given text to Apache Stanbol returns the result by the success callback.  
+// **Parameters**:  
+// *{string}* **text** The text to be analyzed.  
+// *{function}* **success** The success callback.  
+// *{function}* **error** The error callback.  
+// *{object}* **options** Options, like the ```format```, or the ```chain``` to be used.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolConnector}* : The VIE.StanbolConnector instance itself.  
+// **Example usage**:  
+//
+//     var stnblConn = new vie.StanbolConnector(opts);
+//     stnblConn.analyze("This is some text.",
+//                 function (res) { ... },
+//                 function (err) { ... });
+    analyze: function(text, success, error, options) {
+        options = (options)? options :  {};
+        var connector = this;
+        
+        connector._iterate({
+            method : connector._analyze,
+            methodNode : connector._analyzeNode,
+            url : function (idx, opts) {
+                var chain = (opts.chain)? opts.chain : this.options.enhancer.chain;
+                
+                var u = this.options.url[idx].replace(/\/$/, '');
+                u += this.options.enhancer.urlPostfix + "/chain/" + chain.replace(/\/$/, '');
+                return u;
+            },
+            args : {
+                text : text,
+                format : options.format || "application/rdf+json",
+                options : options
+            },
+            success : success,
+            error : error,
+            urlIndex : 0
+        });
+    },
+    
+    _analyze : function (url, args, success, error) {
+        jQuery.ajax({
+            success: success,
+            error: error,
+            url: url,
+            type: "POST",
+            data: args.text,
+            dataType: args.format,
+            contentType: "text/plain",
+            accepts: {"application/rdf+json": "application/rdf+json"}
+        });
+    },
+
+    _analyzeNode: function(url, args, success, error) {
+        var request = require('request');
+        var r = request({
+            method: "POST",
+            uri: url,
+            body: args.text,
+            headers: {
+                Accept: args.format,
+                'Content-Type': 'text/plain'
+            }
+        }, function(err, response, body) {
+            try {
+                success({results: JSON.parse(body)});
+            } catch (e) {
+                error(e);
+            }
+        });
+        r.end();
+    },
+
+// ### load(uri, success, error, options)
+// This method loads all properties from an entity and returns the result by the success callback.  
+// **Parameters**:  
+// *{string}* **uri** The URI of the entity to be loaded.  
+// *{function}* **success** The success callback.  
+// *{function}* **error** The error callback.  
+// *{object}* **options** Options, like the ```format```, the ```site```. If ```local``` is set, only the local entities are accessed.   
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolConnector}* : The VIE.StanbolConnector instance itself.  
+// **Example usage**:  
+//
+//     var stnblConn = new vie.StanbolConnector(opts);
+//     stnblConn.load("<http://dbpedia.org/resource/Barack_Obama>",
+//                 function (res) { ... },
+//                 function (err) { ... });
+
+    load: function (uri, success, error, options) {
+        var connector = this;
+        options = (options)? options :  {};
+        
+        options.uri = uri.replace(/^</, '').replace(/>$/, '');
+        
+        connector._iterate({
+            method : connector._load,
+            methodNode : connector._loadNode,
+            success : success,
+            error : error,
+            url : function (idx, opts) {
+                var site = (opts.site)? opts.site : this.options.entityhub.site;
+                site = (site)? "/" + site : "s";
+                
+                var isLocal = opts.local;
+                
+                var u = this.options.url[idx].replace(/\/$/, '') + this.options.entityhub.urlPostfix;
+                if (isLocal) {
+                    u += "/entity?id=" + escape(opts.uri);
+                } else {
+                    u += "/site" + site + "/entity?id=" + escape(opts.uri);
+                }
+                return u;
+            },
+            args : {
+                format : options.format || "application/rdf+json",
+                options : options
+            },
+            urlIndex : 0
+        });
+    },
+    
+    _load : function (url, args, success, error) {
+        jQuery.ajax({
+            success: success,
+            error: error,
+            url: url,
+            type: "GET",
+            dataType: args.format,
+            contentType: "text/plain",
+            accepts: {"application/rdf+json": "application/rdf+json"}
+        });
+    },
+
+    _loadNode: function(url, args, success, error) {
+        var request = require('request');
+        var r = request({
+            method: "GET",
+            uri: url,
+            body: args.text,
+            headers: {
+                Accept: args.format
+            }
+        }, function(err, response, body) {
+            try {
+                success({results: JSON.parse(body)});
+            } catch (e) {
+                error(e);
+            }
+        });
+        r.end();
+    },
+
+// ### find(term, limit, offset, success, error, options)
+// This method finds entities given the term from the entity hub and returns the result by the success callback.  
+// **Parameters**:  
+// *{string}* **term** The term to be searched for. 
+// *{int}* **limit** The limit of results to be returned. 
+// *{int}* **offset** The offset to be search for.  
+// *{function}* **success** The success callback.  
+// *{function}* **error** The error callback.  
+// *{object}* **options** Options, like the ```format```. If ```local``` is set, only the local entities are accessed.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolConnector}* : The VIE.StanbolConnector instance itself.  
+// **Example usage**:  
+//
+//     var stnblConn = new vie.StanbolConnector(opts);
+//     stnblConn.find("Bishofsh", 10, 0,
+//                 function (res) { ... },
+//                 function (err) { ... });
+    find: function(term, limit, offset, success, error, options) {
+        options = (options)? options :  {};
+        /* curl -X POST -d "name=Bishofsh&limit=10&offset=0" http://localhost:8080/entityhub/sites/find */
+
+        var connector = this;
+        
+        if (!term || term === "") {
+            error ("No term given!");
+            return;
+        }
+        
+        offset = (offset)? offset : 0;
+        limit  = (limit)? limit : 10;
+        
+        connector._iterate({
+            method : connector._find,
+            methodNode : connector._findNode,
+            success : success,
+            error : error,
+            url : function (idx, opts) {
+                var site = (opts.site)? opts.site : this.options.entityhub.site;
+                site = (site)? "/" + site : "s";
+                
+                var isLocal = opts.local;
+                
+                var u = this.options.url[idx].replace(/\/$/, '') + this.options.entityhub.urlPostfix;
+                if (isLocal) {
+                    u += "/sites/find";
+                } else {
+                    u += "/site" + site + "/find";
+                }
+                
+                return u;
+            },
+            args : {
+                term : term,
+                offset : offset,
+                limit : limit,
+                format : options.format || "application/rdf+json",
+                options : options
+            },
+            urlIndex : 0
+        });
+    },
+    
+    _find : function (url, args, success, error) {
+        jQuery.ajax({
+            success: success,
+            error: error,
+            url: url,
+            type: "POST",
+            data: "name=" + args.term + "&limit=" + args.limit + "&offset=" + args.offset,
+            dataType: args.format,
+            contentType : "application/x-www-form-urlencoded",
+            accepts: {"application/rdf+json": "application/rdf+json"}
+        });
+    },
+
+    _findNode: function(url, args, success, error) {
+        var request = require('request');
+        var r = request({
+            method: "POST",
+            uri: url,
+            body : "name=" + args.term + "&limit=" + args.limit + "&offset=" + args.offset,
+            headers: {
+                Accept: args.format
+            }
+        }, function(err, response, body) {
+            try {
+                success({results: JSON.parse(body)});
+            } catch (e) {
+                error(e);
+            }
+        });
+        r.end();
+    },
+    
+// ### lookup(uri, success, error, options)
+// TODO.  
+// **Parameters**:  
+// *{string}* **uri** The URI of the entity to be loaded.  
+// *{function}* **success** The success callback.  
+// *{function}* **error** The error callback.  
+// *{object}* **options** Options, ```create```.
+//    If the parsed ID is a URI of a Symbol, than the stored information of the Symbol are returned in the requested media type ('accept' header field).
+//    If the parsed ID is a URI of an already mapped entity, then the existing mapping is used to get the according Symbol.
+//    If "create" is enabled, and the parsed URI is not already mapped to a Symbol, than all the currently active referenced sites are searched for an Entity with the parsed URI.
+//    If the configuration of the referenced site allows to create new symbols, than a the entity is imported in the Entityhub, a new Symbol and EntityMapping is created and the newly created Symbol is returned.
+//    In case the entity is not found (this also includes if the entity would be available via a referenced site, but create=false) a 404 "Not Found" is returned.
+//    In case the entity is found on a referenced site, but the creation of a new Symbol is not allowed a 403 "Forbidden" is returned.   
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolConnector}* : The VIE.StanbolConnector instance itself.  
+    lookup: function(uri, success, error, options) {
+        options = (options)? options :  {};
+        /*/lookup/?id=http://dbpedia.org/resource/Paris&create=false"*/
+        var connector = this;
+        
+        uri = uri.replace(/^</, '').replace(/>$/, '');
+
+        options.uri = uri;
+        options.create = (options.create)? options.create : false;
+         
+        connector._iterate({
+            method : connector._lookup,
+            methodNode : connector._lookupNode,
+            success : success,
+            error : error,
+            url : function (idx, opts) {
+                 
+                 var u = this.options.url[idx].replace(/\/$/, '') + this.options.entityhub.urlPostfix;
+                 u += "/lookup?id=" + escape(opts.uri) + "&create=" + opts.create;
+                 return u;
+            },
+            args : {
+                format : options.format || "application/rdf+json",
+                options : options
+            },
+            urlIndex : 0
+         });
+     },
+     
+     _lookup : function (url, args, success, error) {
+        jQuery.ajax({
+             success: success,
+             error: error,
+             url: url,
+             type: "GET",
+             dataType: args.format,
+             contentType: "text/plain",
+             accepts: {"application/rdf+json": "application/rdf+json"}
+         });
+     },
+
+     _lookupNode: function(url, args, success, error) {
+         var request = require('request');
+         var r = request({
+             method: "GET",
+             uri: url,
+             body: args.text,
+             headers: {
+                 Accept: args.format
+             }
+         }, function(err, response, body) {
+             try {
+                 success({results: JSON.parse(body)});
+             } catch (e) {
+                 error(e);
+             }
+         });
+         r.end();
+     },
+    
+ // ### referenced(success, error, options)
+ // This method returns a list of all referenced sites that the entityhub comprises.  
+ // **Parameters**:  
+ // *{function}* **success** The success callback.  
+ // *{function}* **error** The error callback.  
+ // *{object}* **options** Options, unused here.   
+ // **Throws**:  
+ // *nothing*  
+ // **Returns**:  
+ // *{VIE.StanbolConnector}* : The VIE.StanbolConnector instance itself.  
+ // **Example usage**:  
+ //
+//      var stnblConn = new vie.StanbolConnector(opts);
+//      stnblConn.referenced(
+//                  function (res) { ... },
+//                  function (err) { ... });  
+     referenced: function(success, error, options) {
+        options = (options)? options :  {};
+        var connector = this;
+        
+        var successCB = function (sites) {
+          if (!_.isArray(sites)) {
+            sites = JSON.parse(sites);
+          }
+          var sitesStripped = [];
+          for (var s = 0, l = sites.length; s < l; s++) {
+            sitesStripped.push(sites[s].replace(/.+\/(.+?)\/?$/, "$1"));
+          }
+          return success(sitesStripped);
+        };
+        
+        connector._iterate({
+            method : connector._referenced,
+            methodNode : connector._referencedNode,
+            success : successCB,
+            error : error,
+            url : function (idx, opts) {
+                 var u = this.options.url[idx].replace(/\/$/, '');
+                 u += this.options.entityhub.urlPostfix + "/sites/referenced";
+                 
+                return u;
+            },
+            args : {
+                options : options
+            },
+            urlIndex : 0
+         });
+     },
+     
+     _referenced : function (url, args, success, error) {
+        jQuery.ajax({
+             success: success,
+             error: error,
+             url: url,
+             type: "GET",
+             accepts: {"application/rdf+json": "application/rdf+json"}
+         });
+     },
+
+     _referencedNode: function(url, args, success, error) {
+         var request = require('request');
+         var r = request({
+             method: "GET",
+             uri: url,
+             headers: {
+                 Accept: args.format
+             }
+         }, function(err, response, body) {
+             try {
+                 success({results: JSON.parse(body)});
+             } catch (e) {
+                 error(e);
+             }
+         });
+         r.end();
+     },
+
+// ### sparql(query, success, error, options)
+// TODO.  
+// **Parameters**:  
+// TODO
+// *{function}* **success** The success callback.  
+// *{function}* **error** The error callback.  
+// *{object}* **options** Options, unused here.   
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolConnector}* : The VIE.StanbolConnector instance itself.  
+     sparql: function(query, success, error, options) {
+        options = (options)? options :  {};
+         var connector = this;
+        
+        connector._iterate({
+            method : connector._sparql,
+            methodNode : connector._sparqlNode,
+            success : success,
+            error : error,
+            url : function (idx, opts) {
+                var u = this.options.url[idx].replace(/\/$/, '');
+                u += this.options.sparql.urlPostfix.replace(/\/$/, '');
+              
+                return u;
+            },
+            args : {
+                query : query,
+                options : options
+            },
+            urlIndex : 0
+          });
+      },
+      
+      _sparql : function (url, args, success, error) {
+        jQuery.ajax({
+              success: success,
+              error: error,
+              url: url,
+              type: "POST",
+              data : "query=" + args.query,
+              contentType : "application/x-www-form-urlencoded"
+          });
+      },
+
+      _sparqlNode: function(url, args, success, error) {
+          var request = require('request');
+          var r = request({
+              method: "POST",
+              uri: url,
+              body : JSON.stringify({query : args.query}),
+              headers: {
+                  Accept: args.format
+              }
+          }, function(err, response, body) {
+              try {
+                  success({results: JSON.parse(body)});
+              } catch (e) {
+                  error(e);
+              }
+          });
+          r.end();
+      },
+      
+// ### ldpath(query, success, error, options)
+// TODO.  
+// **Parameters**:  
+// TODO
+// *{function}* **success** The success callback.  
+// *{function}* **error** The error callback.  
+// *{object}* **options** Options, unused here.   
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolConnector}* : The VIE.StanbolConnector instance itself.  
+    ldpath: function(ldpath, context, success, error, options) {
+        options = (options)? options :  {};
+        var connector = this;
+        
+        context = (_.isArray(context))? context : [ context ];
+        
+        var contextStr = "";
+        for (var c = 0; c < context.length; c++) {
+            contextStr += "&context=" + context[c];
+        }
+        
+        connector._iterate({
+            method : connector._ldpath,
+            methodNode : connector._ldpathNode,
+            success : success,
+            error : error,
+            url : function (idx, opts) {
+                var site = (opts.site)? opts.site : this.options.entityhub.site;
+                site = (site)? "/" + site : "s";
+                
+                var isLocal = opts.local;
+                
+                var u = this.options.url[idx].replace(/\/$/, '') + this.options.entityhub.urlPostfix;
+                if (!isLocal)
+                    u += "/site" + site;
+                u += "/ldpath";
+             
+                return u;
+            },
+            args : {
+                ldpath : ldpath,
+                context : contextStr,
+                format : options.format || "application/rdf+json",
+                options : options
+            },
+            urlIndex : 0
+         });
+     },
+     
+     _ldpath : function (url, args, success, error) {
+        jQuery.ajax({
+             success: success,
+             error: error,
+             url: url,
+             type: "POST",
+             data : "ldpath=" + args.ldpath + args.context,
+             contentType : "application/x-www-form-urlencoded",
+             dataType: args.format,
+             accepts: {"application/rdf+json": "application/rdf+json"}
+         });
+     },
+
+     _ldpathNode: function(url, args, success, error) {
+         var request = require('request');
+         var r = request({
+             method: "POST",
+             uri: url,
+             body : "ldpath=" + args.ldpath + args.context,
+             headers: {
+                 Accept: args.format
+             }
+         }, function(err, response, body) {
+             try {
+                 success({results: JSON.parse(body)});
+             } catch (e) {
+                 error(e);
+             }
+         });
+         r.end();
+     },
+         
+// ### uploadContent(content, success, error, options)
+// TODO.  
+// **Parameters**:  
+// TODO
+// *{function}* **success** The success callback.  
+// *{function}* **error** The error callback.  
+// *{object}* **options** Options, unused here.   
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.StanbolConnector}* : The VIE.StanbolConnector instance itself.  
+      uploadContent: function(content, success, error, options) {
+        options = (options)? options :  {};
+        var connector = this;
+        
+        connector._iterate({
+            method : connector._uploadContent,
+            methodNode : connector._uploadContentNode,
+            success : success,
+            error : error,
+            url : function (idx, opts) {
+                 var u = this.options.url[idx].replace(/\/$/, '');
+                 u += this.options.contenthub.urlPostfix.replace(/\/$/, '');
+                 
+                 var index = (opts.index)? opts.index : this.options.contenthub.index;
+                 
+                 u += "/" + index.replace(/\/$/, '');
+                 u += "/store";
+               
+                 return u;
+            },
+            args : {
+                content: content,
+                options : options
+            },
+            urlIndex : 0
+           });
+       },
+       
+       _uploadContent : function (url, args, success, error) {
+           jQuery.ajax({
+               success: success,
+               error: error,
+               url: url,
+               type: "POST",
+               data : args.content,
+               contentType : "text/plain"
+           });
+       },
+
+       _uploadContentNode: function(url, args, success, error) {
+           var request = require('request');
+           var r = request({
+               method: "POST",
+               uri: url,
+               body : args.content,
+               headers: {
+                   Accept: "application/rdf+xml",
+                   "Content-Type" : "text/plain"
+               }
+           }, function(err, response, body) {
+               try {
+                   success({results: JSON.parse(body)});
+               } catch (e) {
+                   error(e);
+               }
+           });
+           r.end();
+       },
+
+//### createFactSchema(url, schema, success, error, options)
+//TODO.  
+//**Parameters**:  
+//TODO
+//*{function}* **success** The success callback.  
+//*{function}* **error** The error callback.  
+//*{object}* **options** Options, unused here.   
+//**Throws**:  
+//*nothing*  
+//**Returns**:  
+//*{VIE.StanbolConnector}* : The VIE.StanbolConnector instance itself.  
+      createFactSchema: function(url, schema, success, error, options) {
+             options = (options)? options :  {};
+             var connector = this;
+             
+             options.url = url;
+            
+             connector._iterate({
+                method : connector._createFactSchema,
+                methodNode : connector._createFactSchemaNode,
+                success : success,
+                error : error,
+                url : function (idx, opts) {
+                  var u = this.options.url[idx].replace(/\/$/, '');
+                  u += this.options.factstore.urlPostfix.replace(/\/$/, '');
+                  
+                  u += "/facts/" + escape(opts.url);
+                
+                  return u;
+                },
+                args : {
+                    url : url,
+                    schema : schema,
+                    options : options
+                },
+                urlIndex : 0
+            });
+        },
+        
+        _createFactSchema : function (url, args, success, error) {
+               jQuery.ajax({
+                success: success,
+                error: error,
+                url: url,
+                type: "PUT",
+                data : args.schema,
+                contentType : "application/json",
+                dataType: "application/json"
+            });
+        },
+    
+        _createFactSchemaNode: function(url, args, success, error) {
+            var request = require('request');
+            var r = request({
+                method: "PUT",
+                uri: url,
+                body : args.schema,
+                headers: {
+                    Accept: "application/json",
+                    "Content-Type" : "application/json"
+                }
+            }, function(err, response, body) {
+                try {
+                    success({results: JSON.parse(body)});
+                } catch (e) {
+                    error(e);
+                }
+            });
+            r.end();
+        },
+        
+        createFact: function(fact, success, error, options) {
+             options = (options)? options :  {};
+             var connector = this;
+             
+             connector._iterate({
+                method : connector._createFact,
+                methodNode : connector._createFactNode,
+                success : success,
+                error : error,
+                url : function (idx, opts) {
+                     var u = this.options.url[idx].replace(/\/$/, '');
+                     u += this.options.factstore.urlPostfix.replace(/\/$/, '');
+                     
+                     u += "/facts";
+                   
+                  return u;
+                },
+                args : {
+                    fact : fact,
+                    options : options
+                },
+                urlIndex : 0
+               });
+       },
+       
+       _createFact : function (url, args, success, error) {
+           jQuery.ajax({
+               success: success,
+               error: error,
+               url: url,
+               type: "POST",
+               data : args.fact,
+               contentType : "application/json",
+               dataType: "application/json"
+           });
+       },
+    
+       _createFactNode: function(url, args, success, error) {
+           var request = require('request');
+           var r = request({
+               method: "POST",
+               uri: url,
+               body : args.fact,
+               headers: {
+                   Accept: "application/json",
+                   "Content-Type" : "application/json"
+               }
+           }, function(err, response, body) {
+               try {
+                   success({results: JSON.parse(body)});
+               } catch (e) {
+                   error(e);
+               }
+           });
+           r.end();
+       },
+       
+        queryFact: function(query, success, error, options) {
+             options = (options)? options :  {};
+             var connector = this;
+             
+             connector._iterate({
+                method : connector._queryFact,
+                methodNode : connector._queryFactNode,
+                success : success,
+                error : error,
+                url : function (idx, opts) {
+                     var u = this.options.url[idx].replace(/\/$/, '');
+                     u += this.options.factstore.urlPostfix.replace(/\/$/, '');
+                     
+                     u += "/query";
+                   
+                  return u;
+                },
+                args : {
+                    query : query,
+                    options : options
+                },
+                urlIndex : 0
+               });
+       },
+       
+       _queryFact : function (url, args, success, error) {
+           jQuery.ajax({
+               success: success,
+               error: error,
+               url: url,
+               type: "POST",
+               data : args.query,
+               contentType : "application/json",
+               dataType: "application/json"
+           });
+       },
+    
+       _queryFactNode: function(url, args, success, error) {
+           var request = require('request');
+           var r = request({
+               method: "POST",
+               uri: url,
+               body : args.query,
+               headers: {
+                   Accept: "application/json",
+                   "Content-Type" : "application/json"
+               }
+           }, function(err, response, body) {
+               try {
+                   success({results: JSON.parse(body)});
+               } catch (e) {
+                   error(e);
+               }
+           });
+           r.end();
+       }
+};
+})();
+
+//     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 - ZemantaService service
+// The ZemantaService ...
+(function(){
+
+// ## VIE.ZemantaService(options)
+// This is the constructor to instantiate a new service to collect
+// properties of an entity from Zemanta.  
+// **Parameters**:  
+// *{object}* **options** Optional set of fields, ```namespaces```, ```rules```, ```url```, or ```name```.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.ZemantaService}* : A **new** VIE.ZemantaService instance.  
+// **Example usage**:  
+//
+//     var service = new vie.ZemantaService({<some-configuration>});
+VIE.prototype.ZemantaService = function(options) {
+    var defaults = {
+        /* the default name of this service */
+        name : 'zemanta',
+        /* you can pass an array of URLs which are then tried sequentially */
+        url: ["http://api.zemanta.com/services/rest/0.0/"],
+        timeout : 20000, /* 20 seconds timeout */
+        namespaces : {
+            zemanta: "http://s.zemanta.com/ns#"
+        },
+        /* default rules that are shipped with this service */
+        rules : [
+                 {
+                'left' : [
+                    '?subject a zemanta:Recognition',
+                    '?subject zemanta:object ?object',
+                    '?object owl:sameAs ?entity'
+                ],
+                'right' : [
+                    '?entity zemanta:hasEntityAnnotation ?subject'
+                ]
+            }
+         ],
+         "api_key" : undefined
+    };
+    /* the options are merged with the default options */
+    this.options = jQuery.extend(true, defaults, options ? options : {});
+
+    this.vie = null; /* will be set via VIE.use(); */
+    /* overwrite options.name if you want to set another name */
+    this.name = this.options.name;
+    
+    /* basic setup for the ajax connection */
+    jQuery.ajaxSetup({
+        converters: {"text application/rdf+json": function(s){return JSON.parse(s);}},
+        timeout: this.options.timeout
+    });
+};
+
+VIE.prototype.ZemantaService.prototype = {
+    
+// ### init()
+// This method initializes certain properties of the service and is called
+// via ```VIE.use()```.  
+// **Parameters**:  
+// *nothing*  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.ZemantaService}* : The VIE.ZemantaService instance itself.  
+// **Example usage**:  
+//
+//     var service = new vie.ZemantaService({<some-configuration>});
+//     service.init();
+    init: function(){
+
+        for (var key in this.options.namespaces) {
+            var val = this.options.namespaces[key];
+            this.vie.namespaces.add(key, val);
+        }
+        
+        this.rules = jQuery.extend([], VIE.Util.transformationRules(this));
+        this.rules = jQuery.merge(this.rules, (this.options.rules) ? this.options.rules : []);
+        
+        this.connector = new this.vie.ZemantaConnector(this.options);
+
+        /* adding these entity types to VIE helps later the querying */
+        this.vie.types.addOrOverwrite('zemanta:Recognition', [
+            /*TODO: add attributes */
+        ]).inherit("owl:Thing");
+    },
+
+// ### analyze(analyzable)
+// This method extracts text from the jQuery element and sends it to Zemanta for analysis.  
+// **Parameters**:  
+// *{VIE.Analyzable}* **analyzable** The analyzable.  
+// **Throws**:  
+// *{Error}* if an invalid VIE.Findable is passed.  
+// **Returns**:  
+// *{VIE.StanbolService}* : The VIE.ZemantaService instance itself.  
+// **Example usage**:  
+//
+//     var service = new vie.ZemantaService({<some-configuration>});
+//     service.analyzable(
+//         new vie.Analyzable({element : jQuery("#foo")})
+//     );
+    analyze: function(analyzable) {
+        var service = this;
+
+        var correct = analyzable instanceof this.vie.Analyzable;
+        if (!correct) {throw "Invalid Analyzable passed";}
+
+        var element = analyzable.options.element ? analyzable.options.element : jQuery('body');
+
+        var text = service._extractText(element);
+
+        if (text.length > 0) {
+            var success = function (results) {
+                _.defer(function(){
+                    var entities = VIE.Util.rdf2Entities(service, results);
+                    analyzable.resolve(entities);
+                });
+            };
+            var error = function (e) {
+                analyzable.reject(e);
+            };
+            
+            var options = {};
+
+            this.connector.analyze(text, success, error, options);
+
+        } else {
+            console.warn("No text found in element.");
+            analyzable.resolve([]);
+        }
+
+    },
+
+    /* this private method extracts the outerHTML from a jQuery element */
+    _extractText: function (element) {
+        return jQuery(element).wrap("<div>").parent().html();
+    }
+};
+
+// ## VIE.ZemantaConnector(options)
+// The ZemantaConnector is the connection between the VIE Zemanta service
+// and the actual ajax calls.  
+// **Parameters**:  
+// *{object}* **options** The options.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.ZemantaConnector}* : The **new** VIE.ZemantaConnector instance.  
+// **Example usage**:  
+//
+//     var conn = new vie.ZemantaConnector({<some-configuration>});
+VIE.prototype.ZemantaConnector = function (options) {
+    
+    var defaults =  {
+        /* you can pass an array of URLs which are then tried sequentially */
+        url: ["http://api.zemanta.com/services/rest/0.0/"],
+        timeout : 20000, /* 20 seconds timeout */
+        "api_key" : undefined
+    };
+
+    /* the options are merged with the default options */
+    this.options = jQuery.extend(true, defaults, options ? options : {});
+    this.options.url = (_.isArray(this.options.url))? this.options.url : [ this.options.url ];
+    
+    this._init();
+
+    this.baseUrl = (_.isArray(options.url))? options.url : [ options.url ];
+};
+
+VIE.prototype.ZemantaConnector.prototype = {
+        
+// ### _init()
+// Basic setup of the Zemanta connector.  This is called internally by the constructor!
+// **Parameters**:  
+// *nothing*
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.ZemantaConnector}* : The VIE.ZemantaConnector instance itself. 
+    _init : function () {
+        var connector = this;
+        
+        /* basic setup for the ajax connection */
+        jQuery.ajaxSetup({
+            converters: {"text application/rdf+json": function(s){return JSON.parse(s);}},
+            timeout: connector.options.timeout
+        });
+        
+        return this;
+    },
+    
+    _iterate : function (params) {
+        if (!params) { return; }
+        
+        if (params.urlIndex >= this.options.url.length) {
+            params.error.call(this, "Could not connect to the given Zemanta endpoints! Please check for their setup!");
+            return;
+        }
+        
+        var retryErrorCb = function (c, p) {
+            /* in case a Zemanta backend is not responding and
+             * multiple URLs have been registered
+             */
+            return function () {
+                console.log("Zemanta connection error", arguments);
+                p.urlIndex = p.urlIndex+1;
+                c._iterate(p);
+            };
+        }(this, params);
+
+        if (typeof exports !== "undefined" && typeof process !== "undefined") {
+            /* We're on Node.js, don't use jQuery.ajax */
+            return params.methodNode.call(
+                    this, 
+                    params.url.call(this, params.urlIndex, params.args.options),
+                    params.args,
+                    params.success,
+                    retryErrorCb);
+        }
+        
+        return params.method.call(
+                this, 
+                params.url.call(this, params.urlIndex, params.args.options),
+                params.args,
+                params.success,
+                retryErrorCb);
+    },
+
+// ### analyze(text, success, error, options)
+// This method sends the given text to Zemanta returns the result by the success callback.  
+// **Parameters**:  
+// *{string}* **text** The text to be analyzed.  
+// *{function}* **success** The success callback.  
+// *{function}* **error** The error callback.  
+// *{object}* **options** Options, like the ```format```, or the ```chain``` to be used.  
+// **Throws**:  
+// *nothing*  
+// **Returns**:  
+// *{VIE.ZemantaConnector}* : The VIE.ZemantaConnector instance itself.  
+// **Example usage**:  
+//
+//     var conn = new vie.ZemantaConnector(opts);
+//     conn.analyze("<p>This is some HTML text.</p>",
+//                 function (res) { ... },
+//                 function (err) { ... });
+    analyze: function(text, success, error, options) {
+        options = (options)? options :  {};
+        var connector = this;
+        
+        connector._iterate({
+            method : connector._analyze,
+            methodNode : connector._analyzeNode,
+            success : success,
+            error : error,
+            url : function (idx, opts) {
+                var u = this.options.url[idx].replace(/\/$/, '');
+                return u;
+            },
+            args : {
+                text : text,
+                format : options.format || "rdfxml",
+                options : options
+            },
+            urlIndex : 0
+        });
+    },
+    
+    _analyze : function (url, args, success, error) {
+        jQuery.ajax({
+            success: function(a, b, c){
+                var responseData = c.responseText.replace(/<z:signature>.*?<\/z:signature>/, '');
+                success(responseData);
+            },
+            error: error,
+            url: url,
+            type: "POST",
+            dataType: "xml",
+            data: {
+                method : "zemanta.suggest",
+                text : args.text,
+                format : args.format,
+                api_key : this.options.api_key,
+                return_rdf_links : args.options.return_rdf_links
+            },
+            contentType: "text/plain",
+            accepts: {"application/rdf+json": "application/rdf+json"}
+        });
+    },
+
+    _analyzeNode: function(url, args, success, error) {
+        var request = require('request');
+        var r = request({
+            method: "POST",
+            uri: url,
+            body: args.text,
+            headers: {
+                Accept: args.format,
+                'Content-Type': 'text/plain'
+            }
+        }, function(err, response, body) {
+            try {
+                success({results: JSON.parse(body)});
+            } catch (e) {
+                error(e);
+            }
+        });
+        r.end();
+    }
+};
+})();
+
+/*global VIE:false Backbone:false _:false jQuery:false */
+if (!VIE.prototype.view) {
+    VIE.prototype.view = {};
+}
+
+VIE.prototype.view.Collection = Backbone.View.extend({
+    // Ensure the collection view gets updated when items get added or removed
+    initialize: function() {
+        this.templates = this.options.templates;
+        this.service = this.options.service;
+        if (!this.service) {
+            throw "No RDFa service provided to the Collection View";
+        }
+        this.owner = this.options.owner;
+        this.definition = this.options.definition;
+        this.entityViews = {};
+
+        _.bindAll(this, 'addItem', 'removeItem', 'refreshItems');
+
+        this.collection.bind('add', this.addItem);
+        this.collection.bind('remove', this.removeItem);
+        this.collection.bind('reset', this.refreshItems);
+
+        // Make the view aware of existing entities in collection
+        var view = this;
+        this.collection.forEach(function(entity) {
+            view.registerItem(entity, view.collection);
+        });
+    },
+
+    /*
+     * ## canAdd: check if the view can add an item
+     *
+     * The Collection View can add items to itself if two constraints
+     * pass:
+     *
+     *  * Collection View has a template
+     *  * The attribute definition for the collection allows adding a model
+     *
+     *  Optionally you can pass a type to this method to check per type.
+     */
+    canAdd: function (type) {
+      if (_.isEmpty(this.templates)) {
+        return false;
+      }
+
+      if (type && !this.templates[type]) {
+        return false;
+      }
+
+      return this.collection.canAdd(type);
+    },
+
+    addItem: function(entity, collection) {
+        if (collection !== this.collection) {
+            return;
+        }
+
+        var childType = entity.get('@type');
+        var childTypeName;
+        if (_.isArray(childType)) {
+          _.each(childType, function (type) {
+            if (this.canAdd(type.id)) {
+              childTypeName = type.id;
+            }
+          }, this);
+        } else {
+          if (this.canAdd(childType.id)) {
+            childTypeName = childType.id;
+          }
+        }
+          
+        if (!childTypeName) {
+            return;
+        }
+
+        var self = this;
+        // Run the templating function
+        this.templates[childTypeName](entity, function (template) {
+            // Template has been generated, register a view
+            var entityView = self.service._registerEntityView(entity, template, true);
+            var entityElement = entityView.render().$el;
+            if (entity.id) {
+                self.service.setElementSubject(entity.getSubjectUri(), entityElement);
+            }
+
+            // Add the new view to DOM
+            var entityIndex = collection.indexOf(entity);
+            if (entityIndex === 0) {
+                self.$el.prepend(entityElement);
+            } else {
+                var previousEntity = collection.at(entityIndex - 1);
+                var previousView = self.entityViews[previousEntity.cid];
+                if (previousView) {
+                    previousView.$el.after(entityElement);
+                } else {
+                    self.$el.append(entityElement);
+                }
+            }
+
+            // Update reverse relations, if any
+            self.findReverseRelations(entity, entityElement);
+       
+            // Handle eventing
+            self.trigger('add', entityView);
+            self.entityViews[entity.cid] = entityView;
+            entityElement.show();
+        }, this);
+    },
+
+    findReverseRelations: function (entity, element) {
+        // Ensure we catch all inferred predicates. We add these via JSONLD
+        // so the references get properly Collectionized.
+        var service = this.service;
+        element.parent('[rev]').each(function() {
+            var predicate = jQuery(this).attr('rev');
+            var relations = {};
+            relations[predicate] = new service.vie.Collection([], {
+              vie: service.vie,
+              predicate: predicate
+            });
+            var model = service.vie.entities.get(service.getElementSubject(this));
+            if (model) {
+                relations[predicate].addOrUpdate(model);
+            }
+            entity.set(relations);
+        });
+    },
+
+    registerItem: function(entity, collection) {
+        var element = this.service.getElementBySubject(entity.id, this.el);
+        if (!element) {
+            return;
+        }
+        var entityView = this.service._registerEntityView(entity, element);
+        this.entityViews[entity.cid] = entityView;
+    },
+
+    removeItem: function(entity) {
+        if (!this.entityViews[entity.cid]) {
+            return;
+        }
+
+        this.trigger('remove', this.entityViews[entity.cid]);
+        jQuery(this.entityViews[entity.cid].el).remove();
+        delete(this.entityViews[entity.cid]);
+    },
+
+    refreshItems: function(collection) {
+        _.each(this.entityViews, function(view, cid) {
+          jQuery(view.el).remove();
+        });
+        this.entityViews = {};
+        collection.forEach(function(entity) {
+            this.addItem(entity, collection);
+        }, this);
+    }
+});
+/*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.bind('change', this.render);
+        this.model.bind('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);
+    }
+}); 
+// Based on [Julian Aubourg's xdr.js](https://github.com/jaubourg/ajaxHooks/blob/master/src/ajax/xdr.js)  
+// Internet Explorer 8 & 9 don't support the cross-domain request protocol known as CORS. 
+// Their solution we use is called XDomainRequest. This module is a wrapper for 
+// XDR using jQuery ajaxTransport, jQuery's way to support such cases.
+// Author: Szaby Grünwald @ Salzburg Research, 2011
+/*global XDomainRequest:false console:false jQuery:false */
+var root = this;
+(function( jQuery ) {
+
+if ( root.XDomainRequest ) {
+  jQuery.ajaxTransport(function( s ) {
+    if ( s.crossDomain && s.async ) {
+      if ( s.timeout ) {
+        s.xdrTimeout = s.timeout;
+        delete s.timeout;
+      }
+      var xdr;
+      return {
+        send: function( _, complete ) {
+          function callback( status, statusText, responses, responseHeaders ) {
+            xdr.onload = xdr.onerror = xdr.ontimeout = jQuery.noop;
+            xdr = undefined;
+            complete( status, statusText, responses, responseHeaders );
+          }
+          xdr = new XDomainRequest();
+          // For backends supporting header_* in the URI instead of real header parameters,
+          // use the dataType for setting the Accept request header. e.g. Stanbol supports this.
+          if(s.dataType){
+              var headerThroughUriParameters = "header_Accept=" + encodeURIComponent(s.dataType);
+              s.url = s.url + (s.url.indexOf("?") === -1 ? "?" : "&" ) + headerThroughUriParameters;
+          }
+          xdr.open( s.type, s.url );
+          xdr.onload = function(e1, e2) {
+            callback( 200, "OK", { text: xdr.responseText }, "Content-Type: " + xdr.contentType );
+          };
+          // XDR cannot differentiate between errors, 
+          // we call every error 404. Could be changed to another one.
+          xdr.onerror = function(e) {
+              console.error(JSON.stringify(e));
+            callback( 404, "Not Found" );
+          };
+          if ( s.xdrTimeout ) {
+            xdr.ontimeout = function() {
+              callback( 0, "timeout" );
+            };
+            xdr.timeout = s.xdrTimeout;
+          }
+          xdr.send( ( s.hasContent && s.data ) || null );
+        },
+        abort: function() {
+          if ( xdr ) {
+            xdr.onerror = jQuery.noop();
+            xdr.abort();
+          }
+        }
+      };
+    }
+  });
+}
+})( jQuery );
+
+})();
diff --git a/js/theme.js b/js/theme.js
index 4140361..edc10d1 100644
--- a/js/theme.js
+++ b/js/theme.js
@@ -31,6 +31,20 @@ Drupal.theme.prototype.editBackstage = function(settings) {
 };
 
 /**
+ * Theme function for a "curtain" for the Edit module.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - None.
+ * @return
+ *   The corresponding HTML.
+ */
+Drupal.theme.prototype.editCurtain = function(settings) {
+  var html = '';
+  html += '<div class="edit-curtain" />';
+  return html;
+};
+/**
  * Theme function for a modal of the Edit module.
  *
  * @param settings
@@ -62,7 +76,7 @@ Drupal.theme.prototype.editToolbarContainer = function(settings) {
   var html = '';
   html += '<div id="' + settings.id + '" class="edit-toolbar-container edit-animate-invisible edit-animate-only-visibility">';
   html += '  <div class="edit-toolbar-heightfaker edit-animate-fast">';
-  html += '    <div class="edit-toolbar" />';
+  html += '    <div class="edit-toolbar primary" />';
   html += '  </div>';
   html += '</div>';
   return html;
diff --git a/js/ui-editables.js b/js/ui-editables.js
index 874a224..3310b01 100644
--- a/js/ui-editables.js
+++ b/js/ui-editables.js
@@ -8,28 +8,26 @@
 
 Drupal.edit = Drupal.edit || {};
 
+Drupal.edit.form = {
+  create: function($editable, cb) {
+    // @todo: refactor Drupal.edit.form into *two* separate objects/classes:
+    // direct editables and form-based ones.
 
-Drupal.edit.toolbar = {
-  create: function($editable) {
-    if (Drupal.edit.toolbar.get($editable).length > 0) {
-      return false;
-    }
-    else {
-      // Render toolbar.
-      var $toolbar = $(Drupal.theme('editToolbarContainer', {
-        id: this._id($editable)
-      }));
-
-      // Insert in DOM.
-      if ($editable.css('display') == 'inline') {
-        $toolbar.prependTo($editable.offsetParent());
-
-        var pos = $editable.position();
-        Drupal.edit.toolbar.get($editable)
-        .css('left', pos.left).css('top', pos.top);
-      }
-      else {
-        $toolbar.insertBefore($editable);
+    var entity = Drupal.edit.vie.entities.get(Drupal.edit.util.getElementSubject($editable));
+    var predicate = Drupal.edit.util.getElementPredicate($editable);
+    var edit_id = entity.getSubjectUri() + ':' + predicate;
+    var $field = Drupal.edit.util.findFieldForEditable($editable);
+    // @todo: this needs to be refactored, we should have access to the view
+    // rather than the $editable/$el of the view.
+    // moreover, we should probably be de-coupling this and trigger events
+    // instead of mucking with the toolbar from here.
+    var toolbarView = Drupal.edit.state.get('editedFieldView').getToolbarView();
+    var $toolbar = toolbarView.getToolbarElement();
+    // We only create a placeholder-div/form for the form-based instances.
+    if ($field.hasClass('edit-type-form')) {
+      // Check whether this form has already been loaded.
+      if (Drupal.edit.form.get($editable).length > 0) {
+        return false;
       }
 
       // Animate the toolbar into visibility.
@@ -130,6 +128,10 @@ Drupal.edit.form = {
         Drupal.edit.toolbar.addClass($editable, 'info', 'loading');
       }, 0);
 
+      // Indicate in the 'info' toolgroup that the form is loading.
+      // Drupal.edit.toolbar.addClass($editable, 'primary', 'info', 'loading');
+      toolbarView.addClass('info', 'loading');
+
       // Render form container.
       var $form = $(Drupal.theme('editFormContainer', {
         id: this._id($editable),
@@ -144,7 +146,8 @@ Drupal.edit.form = {
         $form.css('left', pos.left).css('top', pos.top);
         // Reset the toolbar's positioning because it'll be moved inside the
         // form container.
-        Drupal.edit.toolbar.get($editable).css('left', '').css('top', '');
+        // Drupal.edit.toolbar.get($editable).css('left', '').css('top', '');
+        $toolbar.css('left', '').css('top', '');
       }
       else {
         $form.insertBefore($editable);
@@ -152,12 +155,74 @@ Drupal.edit.form = {
 
       // Move  toolbar inside .edit-form-container, to let it snap to the width
       // of the form instead of the field formatter.
-      Drupal.edit.toolbar.get($editable).detach().prependTo('.edit-form')
+      // Drupal.edit.toolbar.get($editable).detach().prependTo('.edit-form');
+      $toolbar.detach().prependTo('.edit-form');
 
-      return true;
     }
-  },
 
+    var onLoadCallback = function(status, $form) {
+      // @todo: re-factor
+      if ($field.hasClass('edit-type-form')) {
+        var formWrapperId = Drupal.edit.form._id($editable);
+        // $form.wrap('<div>');
+        $('#' + formWrapperId + ' .placeholder').replaceWith($form);
+
+        // Indicate in the 'info' toolgroup that the form has loaded.
+        // Drupal.edit.toolbar.removeClass($editable, 'primary', 'info', 'loading');
+        toolbarView.removeClass('info', 'loading');
+
+        // Detect changes in this form.
+        Drupal.edit.form.get($editable)
+          .delegate(':input', 'formUpdated.edit', function () {
+            $editable
+              .data('edit-content-changed', true)
+              .trigger('edit-content-changed.edit');
+          })
+          .delegate('input', 'keypress.edit', function (event) {
+            if (event.keyCode == 13) {
+              return false;
+            }
+          });
+        var $submit = Drupal.edit.form.get($editable).find('.edit-form-submit');
+      } else if ($field.hasClass('edit-type-direct')) {
+        // Direct forms are stuffed into #edit_backstage, apparently.
+        $('#edit_backstage').append($form);
+        var $submit = $form.find('.edit-form-submit');
+      }
+      Drupal.edit.form._setupAjaxForm($editable, $field, $submit);
+
+      // Animations.
+      // Drupal.edit.toolbar.show($editable, 'ops');
+      toolbarView.show('ops');
+      $editable.trigger('edit-form-loaded.edit');
+
+      // callback to be able to decorate / continue with the editable...
+      cb($editable, $field);
+    };
+
+    this.loadForm(entity, predicate, onLoadCallback);
+  },
+  // @todo: complete refactoring.
+  _setupAjaxForm: function($editable, $field, $submit) {
+    // Re-wire the form to handle submit.
+    var element_settings = {
+      url: $submit.closest('form').attr('action'),
+      setClick: true,
+      event: 'click.edit',
+      progress: {type:'throbber'},
+      // IPE-specific settings.
+      $editable: $editable,
+      $field: $field,
+      submit: { nocssjs : ($field.hasClass('edit-type-direct')) }
+    };
+    var base = $submit.attr('id');
+    // Removing existing Drupal.ajax-thingy.
+    if (Drupal.ajax.hasOwnProperty(base)) {
+      delete Drupal.ajax[base];
+      $editable.unbind('edit-internal.edit');
+    }
+    Drupal.ajax[base] = new Drupal.ajax(base, $submit[0], element_settings);
+  },
   get: function($editable) {
     return ($editable.length == 0)
       ? $([])
@@ -165,14 +230,138 @@ Drupal.edit.form = {
   },
 
   remove: function($editable) {
+    console.log('Drupal.edit.form.remove', Drupal.edit.form.get($editable));
     Drupal.edit.form.get($editable).remove();
   },
 
   _id: function($editable) {
-    var edit_id = ($editable.hasClass('edit-entity'))
-      ? Drupal.edit.getID($editable)
-      : Drupal.edit.getID(Drupal.edit.findFieldForEditable($editable));
+    var edit_id = Drupal.edit.util.getID($editable);
     return 'edit-form-for-' + edit_id.split(':').join('_');
+  },
+
+  /**
+   * Loads a drupal form for a given vieEntity
+   *
+   * @todo: we need to use this for non-form-based FieldView-instances as well.
+   * @todo: error handling etc.
+   *
+   * @param vieEntity object vieEntity
+   * @param predicate string predicate ("field name")
+   * @param callback callback callback once form is loaded callback(status, $form)
+   */
+  loadForm: function (vieEntity, predicate, callback) {
+    var edit_id = vieEntity.getSubjectUri() + ':' + predicate;
+    var $field = Drupal.edit.util.findFieldForID(edit_id);
+    var $editable = Drupal.edit.util.findEditablesForFields($field);
+    var element_settings = {
+      url      : Drupal.edit.util.calcFormURLForField(edit_id),
+      event    : 'edit-internal.edit',
+      $field   : $field,
+      $editable: $editable,
+      submit   : { nocssjs : ($field.hasClass('edit-type-direct')) },
+      progress : { type : null } // No progress indicator.
+    };
+    // Removing existing Drupal.ajax-thingy.
+    if (Drupal.ajax.hasOwnProperty(edit_id)) {
+      delete Drupal.ajax[edit_id];
+      $editable.unbind('edit-internal.edit');
+    }
+    Drupal.ajax[edit_id] = new Drupal.ajax(edit_id, $editable, element_settings);
+    // Some form of closure.
+    Drupal.ajax[edit_id].commands.edit_field_form = function(ajax, response, status) {
+      callback(status, $(response.data));
+    }
+    $editable.trigger('edit-internal.edit');
+  },
+  /**
+   * Saves (trigger submit) a edit.module form that has been loaded via
+   * Drupal.edit.form.loadForm method. Replaces the old editable with the
+   * response.
+   *
+   * @todo: validation handling.
+   *
+   * @param vieEntity object vieEntity
+   * @param predicate string  predicate ("field name")
+   * @param $editable object  editable object
+   * @param value object? form value - ignored in form-based
+   * @param callback  callback after form has been submitted cb(error, $el);
+   */
+  saveForm: function (vieEntity, predicate, $editable, value, callback) {
+    var edit_id = vieEntity.getSubjectUri() + ':' + predicate;
+    var $field = Drupal.edit.util.findFieldForID(edit_id);
+    // Handle form FormEditableFieldView
+    if ($field.hasClass('edit-type-form')) {
+      // Figure out the submit button for this form.
+      var $submit = Drupal.edit.form.get($editable).find('.edit-form-submit');
+      var base = $submit.attr('id');
+
+      // handle the saveForm callback (edit_field_form_saved).
+      var formEditableFormSubmittedCallback = function(ajax, response, status) {
+        var edit_id = vieEntity.getSubjectUri() + ':' + predicate;
+        // response.data contains the updated rendering of the field, if any.
+        if (response.data) {
+          // Stop the editing.
+          var currentEditorView = Drupal.edit.state.get('editedFieldView');
+          if (currentEditorView) {
+            currentEditorView.disableEditor();
+          }
+          // this is different from edit.module. did not understand what the
+          // stuff was about.
+          $inner = $(response.data).html();
+          $editable.html($inner);
+        }
+        // Remove this Drupal.ajax[base]?
+        delete Drupal.ajax[base];
+        callback(null, $editable);
+      }
+      // Setup of a closure to handle the response at last minute.
+      Drupal.ajax[base].commands.edit_field_form_saved = formEditableFormSubmittedCallback;
+      $submit.trigger('click.edit');
+    } else {
+      // EditableFieldView (direct or wysiwyg).
+
+      var submitDirectForm = function(value) {
+        var $submit = $('#edit_backstage form .edit-form-submit');
+        var base = $submit.attr('id');
+
+        // Shove the value into any field that isn't hidden or a submit button.
+        $('#edit_backstage form').find(':input[type!="hidden"][type!="submit"]').val(value);
+        console.log('submitDirectForm', $submit, base, $('#edit_backstage form'));
+        // Set the callback.
+        Drupal.ajax[base].commands.edit_field_form_saved = function(ajax, response, status) {
+          // Direct forms are stuffed into #edit_backstage, apparently.
+          // that's why Drupal.edit.form.remove($editable); doesn't work.
+          $('#edit_backstage form').remove();
+          // Removing Drupal.ajax-thingy.
+          delete Drupal.ajax[base];
+          // @todo: title currently returns success but no response.data.
+          if (response.data) {
+            // Stop the editing.
+            var currentEditorView = Drupal.edit.state.get('editedFieldView');
+            if (currentEditorView) {
+              currentEditorView.disableEditor();
+            }
+            callback(null, $editable);
+          } else {
+            // @todo: handle errors, empty response?
+            console.log('no response data', response, status);
+            callback(true, $editable);
+          }
+        };
+        $submit.trigger('click.edit');
+      }
+
+      // If form doesn't already exist, create it and then submit.
+      if (!Drupal.edit.form.get($editable).length > 0) {
+        Drupal.edit.form.create($editable, function($editable, $field) {
+          // Submit the value.
+          submitDirectForm(value);
+        });
+      } else {
+        // Submit the value.
+        submitDirectForm(value);
+      }
+    }
   }
 };
 
diff --git a/js/util.js b/js/util.js
index ff25d1d..4cb106f 100644
--- a/js/util.js
+++ b/js/util.js
@@ -8,6 +8,112 @@
 
 Drupal.edit = Drupal.edit || {};
 Drupal.edit.util = Drupal.edit.util || {};
+Drupal.edit.util.views = {};
+
+Drupal.edit.util.getID = function(element) {
+  var id = jQuery(element).data('edit-id');
+  if (!id) {
+    id = jQuery(element).closest('[data-edit-id]').data('edit-id');
+  }
+  return id;
+};
+
+Drupal.edit.util.getElementSubject = function(element) {
+  return Drupal.edit.util.getID(element).split(':').slice(0, 2).join(':');
+};
+
+Drupal.edit.util.getElementPredicate = function(element) {
+  return Drupal.edit.util.getID(element).split(':').pop();
+};
+
+Drupal.edit.util.getElementValue = function(element) {
+  var valueElement = jQuery('.field-item', element);
+  if (valueElement.length === 0) {
+    // Handle page title
+    valueElement = jQuery('h1', element);
+  }
+  return $.trim(valueElement.html());
+};
+
+Drupal.edit.util.getElementEntity = function(element, vie) {
+  return vie.entities.get(Drupal.edit.util.getElementSubject(element));
+};
+
+Drupal.edit.util.findEditableEntities = function(context) {
+  var entityElements = $('.edit-entity.edit-allowed', context || Drupal.settings.edit.context);
+  entityElements.each(function () {
+    // Register the entity with VIE
+    Drupal.edit.vie.entities.addOrUpdate({
+      '@subject': Drupal.edit.util.getElementSubject(jQuery(this)),
+      '@type': jQuery(this).data('edit-entity-label')
+    }, {
+      overrideAttributes: true
+    });
+  });
+  return entityElements;
+};
+
+Drupal.edit.util.findEditableFields = function(context) {
+  var propertyElements = $('.edit-field.edit-allowed', context || Drupal.settings.edit.context);
+  propertyElements.each(function () {
+    // Register with VIE
+    var propertyName = Drupal.edit.util.getElementPredicate(jQuery(this));
+    var entityData = {
+      '@subject': Drupal.edit.util.getElementSubject(jQuery(this))
+    };
+    entityData[propertyName] = Drupal.edit.util.getElementValue(jQuery(this));
+    Drupal.edit.vie.entities.addOrUpdate(entityData, {
+      overrideAttributes: true
+    });
+  });
+  return propertyElements;
+};
+
+/*
+ * findEditableFields() just looks for fields that are editable, i.e. for the
+ * field *wrappers*. Depending on the field, however, either the whole field wrapper
+ * will be marked as editable (in this case, an inline form will be used for editing),
+ * *or* a specific (field-specific even!) DOM element within that field wrapper will be
+ * marked as editable.
+ * This function is for finding the *editables* themselves, given the *editable fields*.
+ */
+Drupal.edit.util.findEditablesForFields = function($fields) {
+  var $editables = $();
+
+  // type = form
+  $editables = $editables.add($fields.filter('.edit-type-form'));
+
+  // type = direct
+  var $direct = $fields.filter('.edit-type-direct');
+  $editables = $editables.add($direct.find('.field-item'));
+  // Edge case: "title" pseudofield on pages with lists of nodes.
+  $editables = $editables.add($direct.filter('h2').find('a'));
+  // Edge case: "title" pseudofield on node pages.
+  $editables = $editables.add($direct.find('h1'));
+
+  return $editables;
+};
+
+Drupal.edit.util.findFieldForID = function(id, context) {
+  return $('[data-edit-id="' + id + '"]', context || $('#content'));
+};
+
+Drupal.edit.util.findFieldForEditable = function($editable) {
+  return $editable.filter('.edit-type-form').length ? $editable : $editable.closest('.edit-type-direct');
+};
+
+Drupal.edit.util.findEntityForEditable = function($editable) {
+  return Drupal.edit.util.findEntityForField(Drupal.edit.util.findFieldForEditable($editable));
+};
+
+Drupal.edit.util.findEntityForField = function($f) {
+  var $e = $f.closest('.edit-entity');
+  if ($e.length == 0) {
+    var entity_edit_id = $f.data('edit-id').split(':').slice(0,2).join(':');
+    $e = $('.edit-entity[data-edit-id="' + entity_edit_id + '"]');
+  }
+  return $e;
+};
 
 Drupal.edit.util.calcFormURLForField = function(id) {
   var parts = id.split(':');
diff --git a/js/views.js b/js/views.js
new file mode 100644
index 0000000..6ed8418
--- /dev/null
+++ b/js/views.js
@@ -0,0 +1,755 @@
+(function ($) {
+  Drupal.edit = Drupal.edit || {};
+  Drupal.edit.views = Drupal.edit.views || {};
+
+  Drupal.edit.views.ToolbarView = Backbone.View.extend({
+    fieldView:null,
+    // @todo: this should be the toolbar's $el.
+    $toolbar:null,
+    initialize:function (options) {
+      this.fieldView = options.fieldView;
+    },
+    getEditable:function () {
+      return this.fieldView.$el;
+    },
+    getToolbarElement:function () {
+      return $('#' + this._id() );
+    },
+    createToolbar:function () {
+      if (this.getToolbarElement().length) {
+        return true;
+      }
+      $editable = this.getEditable();
+      // Render toolbar.
+      var $toolbar = $(Drupal.theme('editToolbarContainer', {
+        id:this._id()
+      }));
+      // Insert in DOM.
+      if ($editable.css('display') == 'inline') {
+        $toolbar.prependTo($editable.offsetParent());
+        var pos = $editable.position();
+        $toolbar.css('left', pos.left).css('top', pos.top);
+      }
+      else {
+        $toolbar.insertBefore($editable);
+      }
+
+      // Animate the toolbar into visibility.
+      setTimeout(function () {
+        $toolbar.removeClass('edit-animate-invisible');
+      }, 0);
+
+      // Remove any and all existing toolbars, except for any that are for a
+      // currently being edited field.
+      $('.edit-toolbar-container:not(:has(.edit-editing))')
+        .trigger('edit-toolbar-remove.edit');
+
+      // Event bindings.
+      $toolbar
+        .bind('mouseenter.edit', function (e) {
+          // Prevent triggering the entity's mouse enter event.
+          e.stopPropagation();
+        })
+        .bind('mouseleave.edit', function (e) {
+          var el = $editable[0];
+          if (e.relatedTarget != el && !jQuery.contains(el, e.relatedTarget)) {
+            console.log('triggering mouseleave on ', $editable);
+            $editable.trigger('mouseleave.edit');
+          }
+          // Prevent triggering the entity's mouse leave event.
+          e.stopPropagation();
+        })
+        // Immediate removal whenever requested.
+        // (This is necessary when showing many toolbars in rapid succession: we
+        // don't want all of them to show up!)
+        .bind('edit-toolbar-remove.edit', function (e) {
+          $toolbar.remove();
+        })
+        .delegate('.edit-toolbar, .edit-toolgroup', 'click.edit mousedown.edit', function (e) {
+          if (!$(e.target).is(':input')) {
+            return false;
+          }
+        });
+
+      // We get the label to show from VIE's type system
+      var label = this.fieldView.predicate;
+      var attributeDef = this.fieldView.model.get('@type').attributes.get(this.fieldView.predicate);
+      if (attributeDef && attributeDef.metadata) {
+        label = attributeDef.metadata.label;
+      }
+      var self = this;
+
+      $toolbar.find('.edit-toolbar:not(:has(.edit-toolgroup.info))')
+        .append(Drupal.theme('editToolgroup', {
+        classes:'info',
+        buttons:[
+          {
+            url:'#',
+            label:label,
+            classes:'blank-button label',
+            hasButtonRole:false
+          }
+        ]
+      }))
+        .delegate('a.label', 'click.edit', function (event) {
+          self.fieldView.$el.trigger('click.edit');
+          event.stopPropagation();
+          event.preventDefault();
+        });
+      $toolbar
+        .addClass('edit-editing')
+        .find('.edit-toolbar:not(:has(.edit-toolgroup.ops))')
+        .append(Drupal.theme('editToolgroup', {
+        classes:'ops',
+        buttons:[
+          {
+            url:'#',
+            label:Drupal.t('Save'),
+            classes:'field-save save gray-button'
+          },
+          {
+            url:'#',
+            label:'<span class="close"></span>',
+            classes:'field-close close gray-button'
+          }
+        ]
+      }))
+        .delegate('a.field-save', 'click.edit', function (event) {
+          self.fieldView.saveClicked(event);
+        })
+        .delegate('a.field-close', 'click.edit', function (event) {
+          self.fieldView.closeClicked(event);
+        });
+
+      if ($editable.hasClass('edit-type-direct-with-wysiwyg')) {
+        $toolbar
+        .find('.edit-toolbar:not(:has(.edit-toolbar-wysiwyg-tabs))')
+        .append(Drupal.theme('editToolgroup', {
+          classes: 'wysiwyg-tabs',
+          buttons: []
+        }))
+        .end()
+        .find('.edit-toolbar:not(:has(.edit-toolgroup.wysiwyg))')
+        .append(Drupal.theme('editToolgroup', {
+          classes: 'wysiwyg',
+          buttons: []
+        }));
+        this.fieldView.$el.addClass('edit-wysiwyg-attached');
+      }
+      return true;
+    },
+    // @todo: proper Backbone.remove() and unbind all events above!
+    removeToolbar:function () {
+      var $toolbar  = this.getToolbarElement();
+      // Remove after animation.
+      $toolbar
+        .addClass('edit-animate-invisible')
+        // Prevent this toolbar from being detected *while* it is being removed.
+        .removeAttr('id')
+        .find('.edit-toolbar .edit-toolgroup')
+        .addClass('edit-animate-invisible')
+        .bind(Drupal.edit.const.transitionEnd, function (e) {
+          $toolbar.remove();
+        });
+    },
+    // Animate into view.
+    show:function (toolgroup) {
+      this._find(toolgroup).removeClass('edit-animate-invisible');
+    },
+    hide:function (toolgroup) {
+      this._find(toolgroup).addClass('edit-animate-invisible');
+    },
+    addClass:function (toolgroup, classes) {
+      this._find(toolgroup).addClass(classes);
+    },
+    removeClass:function (toolgroup, classes) {
+      this._find(toolgroup).removeClass(classes);
+    },
+    _find:function (toolgroup) {
+      return this.getToolbarElement().find('.edit-toolbar .edit-toolgroup.' + toolgroup);
+    },
+    _id:function () {
+      var edit_id = Drupal.edit.util.getID(this.getEditable());
+      return 'edit-toolbar-for-' + edit_id.split(':').join('_');
+    }
+  });
+
+  Drupal.edit.views.OverlayView = Backbone.View.extend({
+    state: null,
+
+    events: {
+      'click': 'escapeEditor'
+    },
+
+    initialize: function (options) {
+      this.state = options.state;
+      _.bindAll(this, 'stateChange', 'escapeEditor');
+      this.state.bind('change:isViewing', this.stateChange);
+    },
+
+    stateChange: function () {
+      if (this.state.get('isViewing')) {
+        this.hideOverlay();
+        return;
+      }
+      this.showOverlay();
+    },
+
+    showOverlay: function () {
+      $(Drupal.theme('editOverlay', {}))
+      .appendTo('body')
+      .addClass('edit-animate-slow edit-animate-invisible')
+
+      // Animations
+      $('#edit_overlay').css('top', $('#navbar').outerHeight());
+      $('#edit_overlay').removeClass('edit-animate-invisible');
+
+      // Disable contextual links in edit mode.
+      $('.contextual-links-region')
+      .addClass('edit-contextual-links-region')
+      .removeClass('contextual-links-region');
+    },
+
+    hideOverlay: function () {
+      $('#edit_overlay')
+      .addClass('edit-animate-invisible')
+      .bind(Drupal.edit.const.transitionEnd, function (event) {
+        $('#edit_overlay, .edit-form-container, .edit-toolbar-container, #edit_modal, .edit-curtain').remove();
+      });
+
+      // Enable contextual links in edit mode.
+      $('.edit-contextual-links-region')
+      .addClass('contextual-links-region')
+      .removeClass('edit-contextual-links-region');
+    },
+
+    escapeEditor: function () {
+      var editor = this.state.get('fieldBeingEdited');
+      if (Drupal.edit.modal.get().length > 0 || editor.length === 0) {
+        return;
+      }
+
+      var editedFieldView = this.state.get('editedFieldView');
+      // No modals open and user is in edit state, close editor by
+      // triggering a click to the cancel button
+      editedFieldView.getToolbarElement().find('a.close').trigger('click.edit');
+    }
+  })
+
+  // ## EditableView
+  //
+  // This view wraps a editable DOM element, and connects it
+  // with the VIE Entity instance. Whenever the particular
+  // attribute (predicate) of the instance changes, whether
+  // due to user interaction or some AJAX call, the contents
+  // of the DOM element will be automatically updated.
+  Drupal.edit.views.EditableView = Backbone.View.extend({
+    predicate: null,
+
+    initialize: function (options) {
+      this.predicate = '<http://viejs.org/ns/' + options.predicate + '>';
+      _.bindAll(this, 'render');
+      this.model.bind('change:' + this.predicate, this.render);
+    },
+
+    render: function () {
+      this.$el.html(this.model.get(this.predicate));
+      return this;
+    }
+  });
+
+  // ## FieldView
+  //
+  // This view wraps a field, and connects it with the state of
+  // the Spark Edit module. When state changes to `edit`, the view
+  // decorates the view with the necessary DOM and classes to provide
+  // the editing tools
+  Drupal.edit.views.FieldView = Backbone.View.extend({
+    predicate: null,
+    state: null,
+    editable: false,
+    editing: false,
+    vie: null,
+    editableViews: [],
+    toolbarView: null,
+
+    events: {
+      'mouseenter': 'mouseEnter',
+      'mouseleave': 'mouseLeave'
+    },
+
+    initialize: function (options) {
+      this.state = this.options.state;
+      this.predicate = this.options.predicate;
+      this.vie = this.options.vie;
+
+      _.bindAll(this, 'stateChange', 'mouseEnter', 'mouseLeave', 'checkHighlight');
+
+      this.state.on('change:isViewing', this.stateChange);
+      this.state.on('change:fieldBeingHighlighted', this.checkHighlight);
+
+    },
+
+    buildEditableView: function () {
+      var self = this;
+      Drupal.edit.util.findEditablesForFields(this.$el).each(function () {
+        self.editableViews.push(new Drupal.edit.views.EditableView({
+          model: self.model,
+          el: this,
+          predicate: self.predicate
+        }));
+      });
+    },
+
+    stateChange: function () {
+      if (this.state.get('isViewing')) {
+        this.editable = false;
+        this.undecorate();
+        return;
+      }
+      this.editable = true;
+      this.decorate();
+    },
+
+    decorate: function () {
+      this.$el
+      .addClass('edit-animate-fast')
+      .addClass('edit-candidate edit-editable')
+      .data('edit-background-color', Drupal.edit.util.getBgColor(this.$el));
+    },
+
+    undecorate: function () {
+      // @todo: clarify: undecorating shouldn't remove edit-editable?
+      this.$el
+        .removeClass('edit-candidate edit-editable edit-highlighted edit-editing edit-belowoverlay');
+    },
+
+    mouseEnter: function (event) {
+      if (!this.editable) {
+        return;
+      }
+      if (this.state.get('editedFieldView')) {
+        // Some field is being edited, ignore
+        return;
+      }
+      var self = this;
+      Drupal.edit.util.ignoreHoveringVia(event, '.edit-toolbar-container', function () {
+        if (!self.editing) {
+          console.log('field:mouseenter', self.model.id, self.predicate);
+          self.startHighlight();
+        }
+        event.stopPropagation();
+      });
+    },
+
+    mouseLeave: function (event) {
+      if (!this.editable) {
+        return;
+      }
+      if (this.state.get('editedFieldView')) {
+        // Some field is being edited, ignore
+        return;
+      }
+      var self = this;
+      Drupal.edit.util.ignoreHoveringVia(event, '.edit-toolbar-container', function () {
+        if (!self.editing) {
+          console.log('field:mouseleave', self.model.id, self.predicate);
+          self.stopHighlight();
+        }
+        event.stopPropagation();
+      });
+    },
+
+    startHighlight: function () {
+      console.log('startHighlight', this.model.id, this.predicate);
+
+      // Animations.
+      var self = this;
+      setTimeout(function () {
+        self.$el.addClass('edit-highlighted');
+        self.getToolbarView().show('info');
+      }, 0);
+
+      this.state.set('fieldBeingHighlighted', this.$el);
+      this.state.set('highlightedEditable', this.model.id + ':' + this.predicate);
+    },
+
+    stopHighlight: function () {
+      console.log('stopHighlight', this.model.id, this.predicate);
+      // Animations
+      this.$el.removeClass('edit-highlighted');
+      this.state.set('fieldBeingHighlighted', []);
+      this.state.set('highlightedEditable', null);
+      // hide info
+      this.disableToolbar();
+
+    },
+
+    checkHighlight: function () {
+      return;
+      if (this.state.get('fieldBeingHighlighted') === this.$el) {
+        return;
+      }
+      this.stopHighlight();
+    },
+    enableToolbar: function () {
+      if (!this.toolbarView) {
+        this.toolbarView = new Drupal.edit.views.ToolbarView({
+          fieldView: this
+        });
+      }
+      this.toolbarView.createToolbar();
+    },
+    disableToolbar: function() {
+      if (this.toolbarView) {
+        this.toolbarView.removeToolbar();
+        this.toolbarView.remove();
+        // @todo: make sure everything is unbound.
+        delete this.toolbarView;
+      }
+    },
+    getToolbarView: function() {
+      if (!this.toolbarView) {
+        this.enableToolbar();
+      }
+      return this.toolbarView;
+    },
+    getToolbarElement: function() {
+      return this.getToolbarView().getToolbarElement();
+    }
+  });
+
+  // ## EditableFieldView
+  //
+  // This element is a subtype of the FieldView that adds the controlling
+  // needed for direct editables (as provided by Create.js editable widget)
+  // to the FieldView
+  Drupal.edit.views.EditableFieldView = Drupal.edit.views.FieldView.extend({
+
+    events: {
+      'mouseenter': 'mouseEnter',
+      'mouseleave': 'mouseLeave',
+      'click':      'enableEditor',
+      'createeditableenable': 'editorEnabled',
+      'createeditabledisable': 'editorDisabled',
+      'createeditablechanged': 'contentChanged'
+    },
+
+    initialize: function (options) {
+      this.state = this.options.state;
+      this.predicate = this.options.predicate;
+      this.vie = this.options.vie;
+
+      _.bindAll(this, 'stateChange', 'mouseEnter', 'mouseLeave', 'checkHighlight', 'enableEditor', 'editorEnabled', 'editorDisabled', 'contentChanged');
+
+      this.state.on('change:isViewing', this.stateChange);
+      this.state.on('change:fieldBeingHighlighted', this.checkHighlight);
+    },
+
+    stateChange: function () {
+      if (this.state.get('isViewing')) {
+        this.stopEditable();
+        return;
+      }
+      this.startEditable();
+    },
+
+    // Entered edit state
+    startEditable: function () {
+      this.editable = true;
+
+       this.$el.createEditable({
+          model: this.model,
+          vie: this.vie,
+          disabled: true
+        });
+
+      this.decorate();
+    },
+
+    // Left edit state
+    stopEditable: function () {
+      if (!this.editable) {
+        return;
+      }
+
+      this.editable = false;
+
+      this.disableEditor();
+      this.undecorate();
+    },
+
+    enableEditor: function (event) {
+      if (!this.editable) {
+        // Not in edit state, ignore
+        return;
+      }
+
+      if (this.editing) {
+        // Already editing, ignore
+        return;
+      }
+
+      if (event) {
+        event.stopPropagation();
+        event.preventDefault();
+      }
+
+      this.startHighlight();
+
+      this.$el
+      .addClass('edit-editing')
+      .css('background-color', this.$el.data('edit-background-color'));
+
+      // Ensure others are not editable when we are
+      if (this.state.get('editedFieldView')) {
+        this.state.get('editedFieldView').disableEditor();
+      }
+      // @todo: we currently need to set this to access the current FieldView
+      // in ui-editable.js which is horrible.
+      this.state.set('editedFieldView', this);
+      // Start the Create.js editable widget
+      this.enableEditableWidget();
+      // Enable the toolbar with the save and close buttons
+      this.enableToolbar();
+
+      this.state.set('fieldBeingEdited', this.$el);
+      this.state.set('editedEditable', Drupal.edit.util.getID(this.$el));
+      this.state.set('editedFieldView', this);
+    },
+
+    enableEditableWidget: function () {
+      this.$el.createEditable({
+        vie: this.vie,
+        disabled: false
+      });
+    },
+
+    disableEditor: function () {
+      console.log('disableEditor', this.model.id, this.predicate);
+
+      this.$el
+      .removeClass('edit-editing')
+      .css('background-color', '');
+
+      // TODO: Restore curtain height
+
+      // Stop the Create.js editable widget
+      this.disableEditableWidget();
+      this.disableToolbar();
+
+      $('#edit_backstage form').remove();
+
+      this.state.set('fieldBeingEdited', []);
+      this.state.set('editedEditable', null);
+      this.state.set('editedFieldView', null);
+    },
+
+    disableEditableWidget: function () {
+      this.$el.createEditable({
+        vie: this.vie,
+        disabled: true
+      });
+    },
+
+    editorEnabled: function () {
+      console.log("editorenabled", this.model.id, this.predicate);
+      // Avoid re-"padding" of editable.
+      if (!this.editing) {
+        this.padEditable();
+      }
+
+      this.getToolbarView().show('wysiwyg-tabs');
+      this.getToolbarView().show('wysiwyg');
+      // Show the ops (save, close) as well.
+      this.getToolbarView().show('ops');
+      // hmm, why in the DOM?
+      this.$el.data('edit-content-changed', false);
+      this.$el.trigger('edit-form-loaded.edit');
+      this.editing = true;
+    },
+
+    saveClicked: function (event) {
+      this.$el.blur();
+      if (event) {
+        event.stopPropagation();
+        event.preventDefault();
+      }
+      // Find entity and predicate.
+      var entity = Drupal.edit.vie.entities.get(Drupal.edit.util.getElementSubject(this.$el));
+      var predicate = this.predicate;
+      // Drupal.edit.form.saveForm loads and saves form if necessary.
+      Drupal.edit.form.saveForm(entity, predicate, this.$el, this.model.get(this.predicate), function() {
+        // Editable has been saved.
+      });
+    },
+
+    closeClicked: function (event) {
+      event.stopPropagation();
+      event.preventDefault();
+      // @TODO - handle dirty state.
+      // Disable the editor for the time being, but allow the editable to be
+      // re-enabled on click if needed.
+      this.disableEditor();
+    },
+
+    padEditable: 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.$el
+        .data('edit-width-empty', true)
+        .addClass('edit-animate-disable-width')
+        .css('width', this.$el.width());
+      }
+
+      // 2) Add padding; use animations.
+      var posProp = Drupal.edit.util.getPositionProperties(this.$el);
+      var $toolbar = this.getToolbarElement();
+      setTimeout(function() {
+        // Re-enable width animations (padding changes affect width too!).
+        self.$el.removeClass('edit-animate-disable-width');
+
+        // The whole toolbar must move to the top when it's an inline editable.
+        if (self.$el.css('display') == 'inline') {
+          $toolbar.css('top', parseFloat($toolbar.css('top')) - 5 + 'px');
+        }
+
+        // @todo: adjust this according to the new
+        // Drupal.theme.prototype.editToolbarContainer
+        // The toolbar must move to the top and the left.
+        var $hf = $toolbar.find('.edit-toolbar-heightfaker');
+        $hf.css({ bottom: '6px', left: '-5px' });
+        // When using a WYSIWYG editor, the width of the toolbar must match the
+        // width of the editable.
+        if (self.$el.hasClass('edit-type-direct-with-wysiwyg')) {
+          $hf.css({ width: self.$el.width() + 10 });
+        }
+
+        // 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);
+    },
+
+    unpadEditable: function () {
+      var self = this;
+
+      // 1) Set the empty width again.
+      if (this.$el.data('edit-width-empty') === true) {
+        console.log('restoring width');
+        this.$el
+        .addClass('edit-animate-disable-width')
+        .css('width', '');
+      }
+
+      // 2) Remove padding; use animations (these will run simultaneously with)
+      // the fading out of the toolbar as its gets removed).
+      var posProp = Drupal.edit.util.getPositionProperties(this.$el);
+      var $toolbar = this.getToolbarElement();
+
+      setTimeout(function() {
+        // Re-enable width animations (padding changes affect width too!).
+        self.$el.removeClass('edit-animate-disable-width');
+
+        // Move the toolbar back to its original position.
+        var $hf = $toolbar.find('.edit-toolbar-heightfaker');
+        $hf.css({ bottom: '1px', left: '' });
+        // When using a WYSIWYG editor, restore the width of the toolbar.
+        if (self.$el.hasClass('edit-type-direct-with-wysiwyg')) {
+          $hf.css({ width: '' });
+        }
+        // Undo our changes to the clipping (to prevent the bottom box-shadow).
+        $toolbar
+        .undelegate('.edit-toolbar', Drupal.edit.const.transitionEnd)
+        .find('.edit-toolbar').css('clip', '');
+
+        // 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);
+    },
+
+    editorDisabled: function () {
+      // Avoid re-"unpadding" of editable.
+      if (this.editing) {
+        this.unpadEditable();
+      }
+      this.$el.removeClass('ui-state-disabled');
+      this.$el.removeClass('edit-wysiwyg-attached');
+
+      this.editing = false;
+    },
+
+    contentChanged: function () {
+      this.$el.data('edit-content-changed', true);
+      this.$el.trigger('edit-content-changed.edit');
+
+      this.getToolbarElement()
+      .find('a.save')
+      .addClass('blue-button')
+      .removeClass('gray-button')
+    }
+  });
+
+  // ## FormEditableFieldView
+  //
+  // This view is a subtype of the FieldView that is used for the
+  // elements Spark edits via regular Drupal forms.
+  Drupal.edit.views.FormEditableFieldView = Drupal.edit.views.EditableFieldView.extend({
+
+    enableEditableWidget: function () {
+      this.$el.createEditable({
+        vie: this.vie,
+        disabled: false
+      });
+    },
+
+    disableEditableWidget: function () {
+      this.$el.createEditable({
+        vie: this.vie,
+        disabled: true
+      });
+    },
+
+    saveClicked: function (event) {
+      // Stop events.
+      if (event) {
+        event.stopPropagation();
+        event.preventDefault();
+      }
+
+      var value = this.model.get(this.predicate);
+      var entity = Drupal.edit.vie.entities.get(Drupal.edit.util.getElementSubject(this.$el));
+      var that = this;
+
+      Drupal.edit.form.saveForm(entity, this.predicate, this.$el, null, function(error, $el) {
+        // Restart the editable.
+        that.startEditable();
+      });
+    }
+
+  });
+
+})(jQuery);
-- 
1.7.10.4

