diff --git a/app/assets/javascripts/lib/best_in_place.js b/app/assets/javascripts/lib/best_in_place.js
new file mode 100644
index 00000000..5000103f
--- /dev/null
+++ b/app/assets/javascripts/lib/best_in_place.js
@@ -0,0 +1,685 @@
+/*
+ * BestInPlace (for jQuery)
+ * version: 3.0.0.alpha (2014)
+ *
+ * By Bernat Farrero based on the work of Jan Varwig.
+ * Examples at http://bernatfarrero.com
+ *
+ * Licensed under the MIT:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * @requires jQuery
+ *
+ * Usage:
+ *
+ * Attention.
+ * The format of the JSON object given to the select inputs is the following:
+ * [["key", "value"],["key", "value"]]
+ * The format of the JSON object given to the checkbox inputs is the following:
+ * ["falseValue", "trueValue"]
+
+ */
+//= require jquery.autosize
+
+function BestInPlaceEditor(e) {
+    'use strict';
+    this.element = e;
+    this.initOptions();
+    this.bindForm();
+    this.initPlaceHolder();
+    jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
+}
+
+BestInPlaceEditor.prototype = {
+    // Public Interface Functions //////////////////////////////////////////////
+
+    activate: function () {
+        'use strict';
+        var to_display;
+        if (this.isPlaceHolder()) {
+            to_display = "";
+        } else if (this.original_content) {
+            to_display = this.original_content;
+        } else {
+            switch (this.formType) {
+                case 'input':
+                case 'textarea':
+                    if (this.display_raw) {
+                        to_display = this.element.html().replace(/&/gi, '&');
+                    }
+                    else {
+                        var value = this.element.data('bipValue');
+                        if (typeof value === 'undefined') {
+                            to_display = '';
+                        } else if (typeof value === 'string') {
+                            to_display = this.element.data('bipValue').replace(/&/gi, '&');
+                        } else {
+                            to_display = this.element.data('bipValue');
+                        }
+                    }
+                    break;
+                case 'select':
+                    to_display = this.element.html();
+
+            }
+        }
+
+        this.oldValue = this.isPlaceHolder() ? "" : this.element.html();
+        this.display_value = to_display;
+        jQuery(this.activator).unbind("click", this.clickHandler);
+        this.activateForm();
+        this.element.trigger(jQuery.Event("best_in_place:activate"));
+    },
+
+    abort: function () {
+        'use strict';
+        this.activateText(this.oldValue);
+        jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
+        this.element.trigger(jQuery.Event("best_in_place:abort"));
+        this.element.trigger(jQuery.Event("best_in_place:deactivate"));
+    },
+
+    abortIfConfirm: function () {
+        'use strict';
+        if (!this.useConfirm) {
+            this.abort();
+            return;
+        }
+
+        if (confirm(BestInPlaceEditor.defaults.locales[''].confirmMessage)) {
+            this.abort();
+        }
+    },
+
+    update: function () {
+        'use strict';
+        var editor = this,
+            value = this.getValue();
+
+        // Avoid request if no change is made
+        if (this.formType in {"input": 1, "textarea": 1} && value === this.oldValue) {
+            this.abort();
+            return true;
+        }
+
+        editor.ajax({
+            "type": this.requestMethod(),
+            "dataType": BestInPlaceEditor.defaults.ajaxDataType,
+            "data": editor.requestData(),
+            "success": function (data, status, xhr) {
+                editor.loadSuccessCallback(data, status, xhr);
+            },
+            "error": function (request, error) {
+                editor.loadErrorCallback(request, error);
+            }
+        });
+
+
+        switch (this.formType) {
+            case "select":
+                this.previousCollectionValue = value;
+
+                // search for the text for the span
+                $.each(this.values, function(index, arr){ if (String(arr[0]) === String(value)) editor.element.html(arr[1]); });
+                break;
+
+            case "checkbox":
+                $.each(this.values, function(index, arr){ if (String(arr[0]) === String(value)) editor.element.html(arr[1]); });
+                break;
+
+            default:
+                if (value !== "") {
+                    if (this.display_raw) {
+                        editor.element.html(value);
+                    } else {
+                        editor.element.text(value);
+                    }
+                } else {
+                    editor.element.html(this.placeHolder);
+                }
+        }
+
+        editor.element.data('bipValue', value);
+        editor.element.attr('data-bip-value', value);
+
+        editor.element.trigger(jQuery.Event("best_in_place:update"));
+
+
+    },
+
+    activateForm: function () {
+        'use strict';
+        alert(BestInPlaceEditor.defaults.locales[''].uninitializedForm);
+    },
+
+    activateText: function (value) {
+        'use strict';
+        this.element.html(value);
+        if (this.isPlaceHolder()) {
+            this.element.html(this.placeHolder);
+        }
+    },
+
+    // Helper Functions ////////////////////////////////////////////////////////
+
+    initOptions: function () {
+        // Try parent supplied info
+        'use strict';
+        var self = this;
+        self.element.parents().each(function () {
+            var $parent = jQuery(this);
+            self.url = self.url || $parent.data("bipUrl");
+            self.activator = self.activator || $parent.data("bipActivator");
+            self.okButton = self.okButton || $parent.data("bipOkButton");
+            self.okButtonClass = self.okButtonClass || $parent.data("bipOkButtonClass");
+            self.cancelButton = self.cancelButton || $parent.data("bipCancelButton");
+            self.cancelButtonClass = self.cancelButtonClass || $parent.data("bipCancelButtonClass");
+            self.skipBlur = self.skipBlur || $parent.data("bipSkipBlur");
+        });
+
+        // Load own attributes (overrides all others)
+        self.url = self.element.data("bipUrl") || self.url || document.location.pathname;
+        self.collection = self.element.data("bipCollection") || self.collection;
+        self.formType = self.element.data("bipType") || "input";
+        self.objectName = self.element.data("bipObject") || self.objectName;
+        self.attributeName = self.element.data("bipAttribute") || self.attributeName;
+        self.activator = self.element.data("bipActivator") || self.element;
+        self.okButton = self.element.data("bipOkButton") || self.okButton;
+        self.okButtonClass = self.element.data("bipOkButtonClass") || self.okButtonClass || BestInPlaceEditor.defaults.okButtonClass;
+        self.cancelButton = self.element.data("bipCancelButton") || self.cancelButton;
+        self.cancelButtonClass = self.element.data("bipCancelButtonClass") || self.cancelButtonClass || BestInPlaceEditor.defaults.cancelButtonClass;
+        self.skipBlur = self.element.data("bipSkipBlur") || self.skipBlur || BestInPlaceEditor.defaults.skipBlur;
+        self.isNewObject = self.element.data("bipNewObject");
+        self.dataExtraPayload = self.element.data("bipExtraPayload");
+
+        // Fix for default values of 0
+        if (self.element.data("bipPlaceholder") == null) {
+          self.placeHolder = BestInPlaceEditor.defaults.locales[''].placeHolder;
+        } else {
+          self.placeHolder = self.element.data("bipPlaceholder");
+        }
+
+        self.inner_class = self.element.data("bipInnerClass");
+        self.html_attrs = self.element.data("bipHtmlAttrs");
+        self.original_content = self.element.data("bipOriginalContent") || self.original_content;
+
+        // if set the input won't be satinized
+        self.display_raw = self.element.data("bip-raw");
+
+        self.useConfirm = self.element.data("bip-confirm");
+
+        if (self.formType === "select" || self.formType === "checkbox") {
+            self.values = self.collection;
+            self.collectionValue = self.element.data("bipValue") || self.collectionValue;
+        }
+    },
+
+    bindForm: function () {
+        'use strict';
+        this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
+        this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
+    },
+
+
+    initPlaceHolder: function () {
+        'use strict';
+        // TODO add placeholder for select and checkbox
+        if (this.element.html() === "") {
+            this.element.addClass('bip-placeholder');
+            this.element.html(this.placeHolder);
+        }
+    },
+
+    isPlaceHolder: function () {
+        'use strict';
+        // TODO: It only work when form is deactivated.
+        // Condition will fail when form is activated
+        return this.element.html() === "" || this.element.html() === this.placeHolder;
+    },
+
+    getValue: function () {
+        'use strict';
+        alert(BestInPlaceEditor.defaults.locales[''].uninitializedForm);
+    },
+
+    // Trim and Strips HTML from text
+    sanitizeValue: function (s) {
+        'use strict';
+        return jQuery.trim(s);
+    },
+
+    requestMethod: function() {
+        'use strict';
+        return this.isNewObject ? 'post' : BestInPlaceEditor.defaults.ajaxMethod;
+    },
+
+    /* Generate the data sent in the POST request */
+    requestData: function () {
+        'use strict';
+        // To prevent xss attacks, a csrf token must be defined as a meta attribute
+        var csrf_token = jQuery('meta[name=csrf-token]').attr('content'),
+            csrf_param = jQuery('meta[name=csrf-param]').attr('content');
+
+        var data = {}
+        data['_method'] = this.requestMethod()
+
+        data[this.objectName] = this.dataExtraPayload || {}
+
+        data[this.objectName][this.attributeName] = this.getValue()
+
+        if (csrf_param !== undefined && csrf_token !== undefined) {
+            data[csrf_param] = csrf_token
+        }
+        return jQuery.param(data);
+    },
+
+    ajax: function (options) {
+        'use strict';
+        options.url = this.url;
+        options.beforeSend = function (xhr) {
+            xhr.setRequestHeader("Accept", "application/json");
+        };
+        return jQuery.ajax(options);
+    },
+
+    // Handlers ////////////////////////////////////////////////////////////////
+
+    loadSuccessCallback: function (data, status, xhr) {
+        'use strict';
+        data = jQuery.trim(data);
+        //Update original content with current text.
+        if (this.display_raw) {
+          this.original_content = this.element.html();
+        } else {
+          this.original_content = this.element.text();
+        }
+
+        if (data && data !== "") {
+            var response = jQuery.parseJSON(data);
+            if (response !== null && response.hasOwnProperty("display_as")) {
+                this.element.data('bip-original-content', this.element.text());
+                this.element.html(response.display_as);
+            }
+            if (this.isNewObject && response && response[this.objectName]) {
+                if (response[this.objectName]["id"]) {
+                    this.isNewObject = false
+                    this.url += "/" + response[this.objectName]["id"] // in REST a POST /thing url should become PUT /thing/123
+                }
+            }
+        }
+        this.element.toggleClass('bip-placeholder', this.isPlaceHolder());
+
+        this.element.trigger(jQuery.Event("best_in_place:success"), [data, status, xhr]);
+        this.element.trigger(jQuery.Event("ajax:success"), [data, status, xhr]);
+
+        // Binding back after being clicked
+        jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
+        this.element.trigger(jQuery.Event("best_in_place:deactivate"));
+
+        if (this.collectionValue !== null && this.formType === "select") {
+            this.collectionValue = this.previousCollectionValue;
+            this.previousCollectionValue = null;
+        }
+    },
+
+    loadErrorCallback: function (request, error) {
+        'use strict';
+        this.activateText(this.oldValue);
+
+        this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]);
+        this.element.trigger(jQuery.Event("ajax:error"), request, error);
+
+        // Binding back after being clicked
+        jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
+        this.element.trigger(jQuery.Event("best_in_place:deactivate"));
+    },
+
+    clickHandler: function (event) {
+        'use strict';
+        event.preventDefault();
+        event.data.editor.activate();
+    },
+
+    setHtmlAttributes: function () {
+        'use strict';
+        var formField = this.element.find(this.formType);
+
+        if (this.html_attrs) {
+            var attrs = this.html_attrs;
+            $.each(attrs, function (key, val) {
+                formField.attr(key, val);
+            });
+        }
+    },
+
+    placeButtons: function (output, field) {
+        'use strict';
+        if (field.okButton) {
+            output.append(
+                jQuery(document.createElement('input'))
+                    .attr('type', 'submit')
+                    .attr('class', field.okButtonClass)
+                    .attr('value', field.okButton)
+            );
+        }
+        if (field.cancelButton) {
+            output.append(
+                jQuery(document.createElement('input'))
+                    .attr('type', 'button')
+                    .attr('class', field.cancelButtonClass)
+                    .attr('value', field.cancelButton)
+            );
+        }
+    }
+};
+
+
+// Button cases:
+// If no buttons, then blur saves, ESC cancels
+// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
+// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
+// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
+BestInPlaceEditor.forms = {
+    "input": {
+        activateForm: function () {
+            'use strict';
+            var output = jQuery(document.createElement('form'))
+                .addClass('form_in_place')
+                .attr('action', 'javascript:void(0);')
+                .attr('style', 'display:inline');
+            var input_elt = jQuery(document.createElement('input'))
+                .attr('type', 'text')
+                .attr('name', this.attributeName)
+                .val(this.display_value);
+
+            // Add class to form input
+            if (this.inner_class) {
+                input_elt.addClass(this.inner_class);
+            }
+
+            output.append(input_elt);
+            this.placeButtons(output, this);
+
+            this.element.html(output);
+            this.setHtmlAttributes();
+
+            this.element.find("input[type='text']")[0].select();
+            this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
+            if (this.cancelButton) {
+                this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
+            }
+            if (!this.okButton) {
+                this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
+            }
+            this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
+            this.blurTimer = null;
+            this.userClicked = false;
+        },
+
+        getValue: function () {
+            'use strict';
+            return this.sanitizeValue(this.element.find("input").val());
+        },
+
+        // When buttons are present, use a timer on the blur event to give precedence to clicks
+        inputBlurHandler: function (event) {
+            'use strict';
+            if (event.data.editor.okButton) {
+                event.data.editor.blurTimer = setTimeout(function () {
+                    if (!event.data.editor.userClicked) {
+                        event.data.editor.abort();
+                    }
+                }, 500);
+            } else {
+                if (event.data.editor.cancelButton) {
+                    event.data.editor.blurTimer = setTimeout(function () {
+                        if (!event.data.editor.userClicked) {
+                            event.data.editor.update();
+                        }
+                    }, 500);
+                } else {
+                    event.data.editor.update();
+                }
+            }
+        },
+
+        submitHandler: function (event) {
+            'use strict';
+            event.data.editor.userClicked = true;
+            clearTimeout(event.data.editor.blurTimer);
+            event.data.editor.update();
+        },
+
+        cancelButtonHandler: function (event) {
+            'use strict';
+            event.data.editor.userClicked = true;
+            clearTimeout(event.data.editor.blurTimer);
+            event.data.editor.abort();
+            event.stopPropagation(); // Without this, click isn't handled
+        },
+
+        keyupHandler: function (event) {
+            'use strict';
+            if (event.keyCode === 27) {
+                event.data.editor.abort();
+                event.stopImmediatePropagation();
+            }
+        }
+    },
+
+    "select": {
+        activateForm: function () {
+            'use strict';
+            var output = jQuery(document.createElement('form'))
+                    .attr('action', 'javascript:void(0)')
+                    .attr('style', 'display:inline'),
+                selected = '',
+                select_elt = jQuery(document.createElement('select'))
+                    .attr('class', this.inner_class !== null ? this.inner_class : ''),
+                currentCollectionValue = this.collectionValue,
+                key, value,
+                a = this.values;
+
+            $.each(a, function(index, arr){
+                key = arr[0];
+                value = arr[1];
+                var option_elt = jQuery(document.createElement('option'))
+                    .val(key)
+                    .html(value);
+
+                if (currentCollectionValue) {
+                  if (String(key) === String(currentCollectionValue)) option_elt.attr('selected', 'selected');
+                }
+                select_elt.append(option_elt);
+            });
+            output.append(select_elt);
+
+            this.element.html(output);
+            this.setHtmlAttributes();
+            this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
+            this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
+            this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
+            this.element.find("select")[0].focus();
+
+            // automatically click on the select so you
+            // don't have to click twice
+            try {
+              var e = document.createEvent("MouseEvents");
+              e.initMouseEvent("mousedown", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+              this.element.find("select")[0].dispatchEvent(e);
+            }
+            catch(e) {
+              // browser doesn't support this, e.g. IE8
+            }
+        },
+
+        getValue: function () {
+            'use strict';
+            return this.sanitizeValue(this.element.find("select").val());
+        },
+
+        blurHandler: function (event) {
+            'use strict';
+            event.data.editor.update();
+        },
+
+        keyupHandler: function (event) {
+            'use strict';
+            if (event.keyCode === 27) {
+                event.data.editor.abort();
+            }
+        }
+    },
+
+    "checkbox": {
+        activateForm: function () {
+            'use strict';
+            this.collectionValue = !this.getValue();
+            this.setHtmlAttributes();
+            this.update();
+        },
+
+        getValue: function () {
+            'use strict';
+            return this.collectionValue;
+        }
+    },
+
+    "textarea": {
+        activateForm: function () {
+            'use strict';
+            // grab width and height of text
+            var width = this.element.css('width');
+            var height = this.element.css('height');
+
+            // construct form
+            var output = jQuery(document.createElement('form'))
+                .addClass('form_in_place')
+                .attr('action', 'javascript:void(0);')
+                .attr('style', 'display:inline');
+            var textarea_elt = jQuery(document.createElement('textarea'))
+                .attr('name', this.attributeName)
+                .val(this.sanitizeValue(this.display_value));
+
+            if (this.inner_class !== null) {
+                textarea_elt.addClass(this.inner_class);
+            }
+
+            output.append(textarea_elt);
+
+            this.placeButtons(output, this);
+
+            this.element.html(output);
+            this.setHtmlAttributes();
+
+            // set width and height of textarea
+            jQuery(this.element.find("textarea")[0]).css({'min-width': width, 'min-height': height});
+            jQuery(this.element.find("textarea")[0]).autosize();
+
+            this.element.find("textarea")[0].focus();
+            this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
+
+            if (this.cancelButton) {
+                this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
+            }
+
+            if (!this.skipBlur) {
+                this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
+            }
+            this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
+            this.blurTimer = null;
+            this.userClicked = false;
+        },
+
+        getValue: function () {
+            'use strict';
+            return this.sanitizeValue(this.element.find("textarea").val());
+        },
+
+        // When buttons are present, use a timer on the blur event to give precedence to clicks
+        blurHandler: function (event) {
+            'use strict';
+            if (event.data.editor.okButton) {
+                event.data.editor.blurTimer = setTimeout(function () {
+                    if (!event.data.editor.userClicked) {
+                        event.data.editor.abortIfConfirm();
+                    }
+                }, 500);
+            } else {
+                if (event.data.editor.cancelButton) {
+                    event.data.editor.blurTimer = setTimeout(function () {
+                        if (!event.data.editor.userClicked) {
+                            event.data.editor.update();
+                        }
+                    }, 500);
+                } else {
+                    event.data.editor.update();
+                }
+            }
+        },
+
+        submitHandler: function (event) {
+            'use strict';
+            event.data.editor.userClicked = true;
+            clearTimeout(event.data.editor.blurTimer);
+            event.data.editor.update();
+        },
+
+        cancelButtonHandler: function (event) {
+            'use strict';
+            event.data.editor.userClicked = true;
+            clearTimeout(event.data.editor.blurTimer);
+            event.data.editor.abortIfConfirm();
+            event.stopPropagation(); // Without this, click isn't handled
+        },
+
+        keyupHandler: function (event) {
+            'use strict';
+            if (event.keyCode === 27) {
+                event.data.editor.abortIfConfirm();
+            }
+        }
+    }
+};
+
+BestInPlaceEditor.defaults = {
+    locales: {},
+    ajaxMethod: "put",  //TODO Change to patch when support to 3.2 is dropped
+    ajaxDataType: 'text',
+    okButtonClass: '',
+    cancelButtonClass: '',
+    skipBlur: false
+};
+
+// Default locale
+BestInPlaceEditor.defaults.locales[''] = {
+    confirmMessage: "Are you sure you want to discard your changes?",
+    uninitializedForm: "The form was not properly initialized. getValue is unbound",
+    placeHolder: '-'
+};
+
+jQuery.fn.best_in_place = function () {
+    'use strict';
+    function setBestInPlace(element) {
+        if (!element.data('bestInPlaceEditor')) {
+            element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
+            return true;
+        }
+    }
+
+    jQuery(this.context).delegate(this.selector, 'click', function () {
+        var el = jQuery(this);
+        if (setBestInPlace(el)) {
+            el.click();
+        }
+    });
+
+    this.each(function () {
+        setBestInPlace(jQuery(this));
+    });
+
+    return this;
+};
+
+
+
diff --git a/app/assets/javascripts/lib/bip.js b/app/assets/javascripts/lib/bip.js
deleted file mode 100644
index 1d575fef..00000000
--- a/app/assets/javascripts/lib/bip.js
+++ /dev/null
@@ -1,780 +0,0 @@
-/*
-        BestInPlace (for jQuery)
-        version: 0.1.0 (01/01/2011)
-        @requires jQuery >= v1.4
-        @requires jQuery.purr to display pop-up windows
-
-        By Bernat Farrero based on the work of Jan Varwig.
-        Examples at http://bernatfarrero.com
-
-        Licensed under the MIT:
-          http://www.opensource.org/licenses/mit-license.php
-
-        Usage:
-
-        Attention.
-        The format of the JSON object given to the select inputs is the following:
-        [["key", "value"],["key", "value"]]
-        The format of the JSON object given to the checkbox inputs is the following:
-        ["falseValue", "trueValue"]
-*/
-
-
-function BestInPlaceEditor(e) {
-  this.element = e;
-  this.initOptions();
-  this.bindForm();
-  this.initNil();
-  jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
-}
-
-BestInPlaceEditor.prototype = {
-  // Public Interface Functions //////////////////////////////////////////////
-
-  activate : function() {
-    var to_display = "";
-    if (this.isNil()) {
-      to_display = "";
-    }
-    else if (this.original_content) {
-      to_display = this.original_content;
-    }
-    else {
-      if (this.sanitize) {
-        to_display = this.element.text();
-      } else {
-        to_display = this.element.html();
-      }
-    }
-
-    this.oldValue = this.isNil() ? "" : this.element.html();
-    this.display_value = to_display;
-    jQuery(this.activator).unbind("click", this.clickHandler);
-    this.activateForm();
-    this.element.trigger(jQuery.Event("best_in_place:activate"));
-  },
-
-  abort : function() {
-    this.activateText(this.oldValue);
-    jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
-    this.element.trigger(jQuery.Event("best_in_place:abort"));
-    this.element.trigger(jQuery.Event("best_in_place:deactivate"));
-  },
-
-  abortIfConfirm : function () {
-    if (!this.useConfirm) {
-      this.abort();
-      return;
-    }
-
-    if (confirm("Are you sure you want to discard your changes?")) {
-      this.abort();
-    }
-  },
-
-  update : function() {
-    var editor = this;
-    if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
-    { // Avoid request if no change is made
-      this.abort();
-      return true;
-    }
-    editor.ajax({
-      "type"       : "post",
-      "dataType"   : "text",
-      "data"       : editor.requestData(),
-      "success"    : function(data){ editor.loadSuccessCallback(data); },
-      "error"      : function(request, error){ editor.loadErrorCallback(request, error); }
-    });
-    if (this.formType == "select") {
-      var value = this.getValue();
-      this.previousCollectionValue = value;
-
-      jQuery.each(this.values, function(i, v) {
-        if (value == v[0]) {
-          editor.element.html(v[1]);
-        }
-      }
-    );
-    } else if (this.formType == "checkbox") {
-      editor.element.html(this.getValue() ? this.values[1] : this.values[0]);
-    } else {
-      if (this.getValue() !== "") {
-        editor.element.text(this.getValue());
-      } else {
-        editor.element.html(this.nil);
-      }
-    }
-    editor.element.trigger(jQuery.Event("best_in_place:update"));
-  },
-
-  activateForm : function() {
-    alert("The form was not properly initialized. activateForm is unbound");
-  },
-
-  activateText : function(value){
-    this.element.html(value);
-    if(this.isNil()) this.element.html(this.nil);
-  },
-
-  // Helper Functions ////////////////////////////////////////////////////////
-
-  initOptions : function() {
-    // Try parent supplied info
-    var self = this;
-    self.element.parents().each(function(){
-      $parent = jQuery(this);
-      self.url               = self.url               || $parent.attr("data-url");
-      self.collection        = self.collection        || $parent.attr("data-collection");
-      self.formType          = self.formType          || $parent.attr("data-type");
-      self.objectName        = self.objectName        || $parent.attr("data-object");
-      self.attributeName     = self.attributeName     || $parent.attr("data-attribute");
-      self.activator         = self.activator         || $parent.attr("data-activator");
-      self.okButton          = self.okButton          || $parent.attr("data-ok-button");
-      self.okButtonClass     = self.okButtonClass     || $parent.attr("data-ok-button-class");
-      self.cancelButton      = self.cancelButton      || $parent.attr("data-cancel-button");
-      self.cancelButtonClass = self.cancelButtonClass || $parent.attr("data-cancel-button-class");
-      self.nil               = self.nil               || $parent.attr("data-nil");
-      self.inner_class       = self.inner_class       || $parent.attr("data-inner-class");
-      self.html_attrs        = self.html_attrs        || $parent.attr("data-html-attrs");
-      self.original_content  = self.original_content  || $parent.attr("data-original-content");
-      self.collectionValue   = self.collectionValue   || $parent.attr("data-value");
-    });
-
-    // Try Rails-id based if parents did not explicitly supply something
-    self.element.parents().each(function(){
-      var res = this.id.match(/^(\w+)_(\d+)$/i);
-      if (res) {
-        self.objectName = self.objectName || res[1];
-      }
-    });
-
-    // Load own attributes (overrides all others)
-    self.url               = self.element.attr("data-url")                 || self.url      || document.location.pathname;
-    self.collection        = self.element.attr("data-collection")          || self.collection;
-    self.formType          = self.element.attr("data-type")                || self.formtype || "input";
-    self.objectName        = self.element.attr("data-object")              || self.objectName;
-    self.attributeName     = self.element.attr("data-attribute")           || self.attributeName;
-    self.activator         = self.element.attr("data-activator")           || self.element;
-    self.okButton          = self.element.attr("data-ok-button")           || self.okButton;
-    self.okButtonClass     = self.element.attr("data-ok-button-class")     || self.okButtonClass || "";
-    self.cancelButton      = self.element.attr("data-cancel-button")       || self.cancelButton;
-    self.cancelButtonClass = self.element.attr("data-cancel-button-class") || self.cancelButtonClass || "";
-    self.nil               = self.element.attr("data-nil")                 || self.nil      || "—";
-    self.inner_class       = self.element.attr("data-inner-class")         || self.inner_class   || null;
-    self.html_attrs        = self.element.attr("data-html-attrs")          || self.html_attrs;
-    self.original_content  = self.element.attr("data-original-content")    || self.original_content;
-    self.collectionValue   = self.element.attr("data-value")               || self.collectionValue;
-
-    if (!self.element.attr("data-sanitize")) {
-      self.sanitize = true;
-    }
-    else {
-      self.sanitize = (self.element.attr("data-sanitize") == "true");
-    }
-
-    if (!self.element.attr("data-use-confirm")) {
-      self.useConfirm = true;
-    } else {
-      self.useConfirm = (self.element.attr("data-use-confirm") != "false");
-    }
-
-    if ((self.formType == "select" || self.formType == "checkbox") && self.collection !== null)
-    {
-      self.values = jQuery.parseJSON(self.collection);
-    }
-
-  },
-
-  bindForm : function() {
-    this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
-    this.getValue     = BestInPlaceEditor.forms[this.formType].getValue;
-  },
-
-  initNil: function() {
-    if (this.element.html() === "")
-    {
-      this.element.html(this.nil);
-    }
-  },
-
-  isNil: function() {
-    // TODO: It only work when form is deactivated.
-    //       Condition will fail when form is activated
-    return this.element.html() === "" || this.element.html() === this.nil;
-  },
-
-  getValue : function() {
-    alert("The form was not properly initialized. getValue is unbound");
-  },
-
-  // Trim and Strips HTML from text
-  sanitizeValue : function(s) {
-   return jQuery.trim(s);
-  },
-
-  /* Generate the data sent in the POST request */
-  requestData : function() {
-    // To prevent xss attacks, a csrf token must be defined as a meta attribute
-    csrf_token = jQuery('meta[name=csrf-token]').attr('content');
-    csrf_param = jQuery('meta[name=csrf-param]').attr('content');
-
-    var data = "_method=put";
-    data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(this.getValue());
-
-    if (csrf_param !== undefined && csrf_token !== undefined) {
-      data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token);
-    }
-    return data;
-  },
-
-  ajax : function(options) {
-    options.url = this.url;
-    options.beforeSend = function(xhr){ xhr.setRequestHeader("Accept", "application/json"); };
-    return jQuery.ajax(options);
-  },
-
-  // Handlers ////////////////////////////////////////////////////////////////
-
-  loadSuccessCallback : function(data) {
-    data = jQuery.trim(data);
-
-    if(data && data!=""){
-      var response = jQuery.parseJSON(jQuery.trim(data));
-      if (response !== null && response.hasOwnProperty("display_as")) {
-        this.element.attr("data-original-content", this.element.text());
-        this.original_content = this.element.text();
-        this.element.html(response["display_as"]);
-      }
-
-      this.element.trigger(jQuery.Event("best_in_place:success"), data);
-      this.element.trigger(jQuery.Event("ajax:success"), data);
-    } else {
-      this.element.trigger(jQuery.Event("best_in_place:success"));
-      this.element.trigger(jQuery.Event("ajax:success"));
-    }
-
-    // Binding back after being clicked
-    jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
-    this.element.trigger(jQuery.Event("best_in_place:deactivate"));
-
-    if (this.collectionValue !== null && this.formType == "select") {
-      this.collectionValue = this.previousCollectionValue;
-      this.previousCollectionValue = null;
-    }
-  },
-
-  loadErrorCallback : function(request, error) {
-    this.activateText(this.oldValue);
-
-    this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]);
-    this.element.trigger(jQuery.Event("ajax:error"), request, error);
-
-    // Binding back after being clicked
-    jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
-    this.element.trigger(jQuery.Event("best_in_place:deactivate"));
-  },
-
-  clickHandler : function(event) {
-    event.preventDefault();
-    event.data.editor.activate();
-  },
-
-  setHtmlAttributes : function() {
-    var formField = this.element.find(this.formType);
-
-    if(this.html_attrs){
-      var attrs = jQuery.parseJSON(this.html_attrs);
-      for(var key in attrs){
-        formField.attr(key, attrs[key]);
-      }
-    }
-  }
-};
-
-
-// Button cases:
-// If no buttons, then blur saves, ESC cancels
-// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
-// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
-// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
-BestInPlaceEditor.forms = {
-  "input" : {
-    activateForm : function() {
-      var output = jQuery(document.createElement('form'))
-                   .addClass('form_in_place')
-                   .attr('action', 'javascript:void(0);')
-                   .attr('style', 'display:inline');
-      var input_elt = jQuery(document.createElement('input'))
-                      .attr('type', 'text')
-                      .attr('name', this.attributeName)
-                      .val(this.display_value);
-      if(this.inner_class !== null) {
-        input_elt.addClass(this.inner_class);
-      }
-      output.append(input_elt);
-      if(this.okButton) {
-        output.append(
-          jQuery(document.createElement('input'))
-          .attr('type', 'submit')
-          .attr('class', this.okButtonClass)
-          .attr('value', this.okButton)
-        )
-      }
-      if(this.cancelButton) {
-        output.append(
-          jQuery(document.createElement('input'))
-          .attr('type', 'button')
-          .attr('class', this.cancelButtonClass)
-          .attr('value', this.cancelButton)
-        )
-      }
-
-      this.element.html(output);
-      this.setHtmlAttributes();
-      // START METAMAPS CODE
-      //this.element.find("input[type='text']")[0].select();
-      this.element.find("input[type='text']")[0].focus();
-      // END METAMAPS CODE
-      this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
-      if (this.cancelButton) {
-        this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
-      }
-      this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
-      // START METAMAPS CODE
-      this.element.find("input[type='text']").bind('keydown', {editor: this}, BestInPlaceEditor.forms.input.keydownHandler);  
-      // END METAMAPS CODE
-      this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
-      this.blurTimer = null;
-      this.userClicked = false;
-    },
-
-    getValue : function() {
-      return this.sanitizeValue(this.element.find("input").val());
-    },
-
-    // When buttons are present, use a timer on the blur event to give precedence to clicks
-    inputBlurHandler : function(event) {
-      if (event.data.editor.okButton) {
-        event.data.editor.blurTimer = setTimeout(function () {
-          if (!event.data.editor.userClicked) {
-            event.data.editor.abort();
-          }
-        }, 500);
-      } else {
-        if (event.data.editor.cancelButton) {
-          event.data.editor.blurTimer = setTimeout(function () {
-            if (!event.data.editor.userClicked) {
-              event.data.editor.update();
-            }
-          }, 500);
-        } else {
-          event.data.editor.update();
-        }
-      }
-    },
-
-    submitHandler : function(event) {
-      event.data.editor.userClicked = true;
-      clearTimeout(event.data.editor.blurTimer);
-      event.data.editor.update();
-    },
-
-    cancelButtonHandler : function(event) {
-      event.data.editor.userClicked = true;
-      clearTimeout(event.data.editor.blurTimer);
-      event.data.editor.abort();
-      event.stopPropagation(); // Without this, click isn't handled
-    },
-
-    keyupHandler : function(event) {
-      if (event.keyCode == 27) {
-        event.data.editor.abort();
-      }
-      // START METAMAPS CODE
-      else if (event.keyCode == 13 && !event.shiftKey) {
-        event.data.editor.update();
-      }
-      // END METAMAPS CODE
-    }
-  },
-
-  "date" : {
-    activateForm : function() {
-      var that      = this,
-          output    = jQuery(document.createElement('form'))
-                      .addClass('form_in_place')
-                      .attr('action', 'javascript:void(0);')
-                      .attr('style', 'display:inline'),
-          input_elt = jQuery(document.createElement('input'))
-                      .attr('type', 'text')
-                      .attr('name', this.attributeName)
-                      .attr('value', this.sanitizeValue(this.display_value));
-      if(this.inner_class !== null) {
-        input_elt.addClass(this.inner_class);
-      }
-      output.append(input_elt)
-
-      this.element.html(output);
-      this.setHtmlAttributes();
-      this.element.find('input')[0].select();
-      this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
-      this.element.find("input").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
-
-      this.element.find('input')
-        .datepicker({
-            onClose: function() {
-              that.update();
-            }
-          })
-        .datepicker('show');
-    },
-
-    getValue :  function() {
-      return this.sanitizeValue(this.element.find("input").val());
-    },
-
-    submitHandler : function(event) {
-      event.data.editor.update();
-    },
-
-    // START METAMAPS CODE
-    keydownHandler : function(event) {
-      if (event.keyCode == 13 && !event.shiftKey) {
-        event.preventDefault();
-        event.stopPropagation();
-        return false;
-      }
-    },
-    // END METAMAPS CODE
-
-    keyupHandler : function(event) {
-      if (event.keyCode == 27) {
-        event.data.editor.abort();
-      }
-    }
-  },
-
-  "select" : {
-    activateForm : function() {
-      var output     = jQuery(document.createElement('form'))
-                       .attr('action', 'javascript:void(0)')
-                       .attr('style', 'display:inline');
-          selected   = '',
-          oldValue   = this.oldValue,
-          select_elt = jQuery(document.createElement('select'))
-                      .attr('class', this.inned_class !== null ? this.inner_class : '' ),
-          currentCollectionValue = this.collectionValue;
-
-      jQuery.each(this.values, function (index, value) {
-        var option_elt = jQuery(document.createElement('option'))
-                         // .attr('value', value[0])
-                         .val(value[0])
-                         .html(value[1]);
-        if(value[0] == currentCollectionValue) {
-          option_elt.attr('selected', 'selected');
-        }
-        select_elt.append(option_elt);
-      });
-      output.append(select_elt);
-
-      this.element.html(output);
-      this.setHtmlAttributes();
-      this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
-      this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
-      this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
-      this.element.find("select")[0].focus();
-    },
-
-    getValue : function() {
-      return this.sanitizeValue(this.element.find("select").val());
-      // return this.element.find("select").val();
-    },
-
-    blurHandler : function(event) {
-      event.data.editor.update();
-    },
-
-    keyupHandler : function(event) {
-      if (event.keyCode == 27) event.data.editor.abort();
-    }
-  },
-
-  "checkbox" : {
-    activateForm : function() {
-      this.collectionValue = !this.getValue();
-      this.setHtmlAttributes();
-      this.update();
-    },
-
-    getValue : function() {
-      return this.collectionValue;
-    }
-  },
-
-  "textarea" : {
-    activateForm : function() {
-      // grab width and height of text
-      width = this.element.css('width');
-      height = this.element.css('height');
-
-      // construct form
-      var output   = jQuery(document.createElement('form'))
-                     .attr('action', 'javascript:void(0)')
-                     .attr('style', 'display:inline')
-                     .append(jQuery(document.createElement('textarea'))
-                             .val(this.sanitizeValue(this.display_value)));
-      if(this.okButton) {
-        output.append(
-          jQuery(document.createElement('input'))
-          .attr('type', 'submit')
-          .attr('value', this.okButton)
-        );
-      }
-      if(this.cancelButton) {
-        output.append(
-          jQuery(document.createElement('input'))
-          .attr('type', 'button')
-          .attr('value', this.cancelButton)
-        )
-      }
-
-      this.element.html(output);
-      this.setHtmlAttributes();
-
-      // set width and height of textarea
-      jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
-      jQuery(this.element.find("textarea")[0]).elastic();
-
-      this.element.find("textarea")[0].focus();
-      this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
-      if (this.cancelButton) {
-        this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
-      }
-      this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
-      // START METAMAPS CODE
-      this.element.find("textarea").bind('keydown', {editor: this}, BestInPlaceEditor.forms.textarea.keydownHandler);  
-      // END METAMAPS CODE
-      this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
-      this.blurTimer = null;
-      this.userClicked = false;
-    },
-
-    getValue :  function() {
-      return this.sanitizeValue(this.element.find("textarea").val());
-    },
-
-    // When buttons are present, use a timer on the blur event to give precedence to clicks
-    blurHandler : function(event) {
-      if (event.data.editor.okButton) {
-        event.data.editor.blurTimer = setTimeout(function () {
-          if (!event.data.editor.userClicked) {
-            event.data.editor.abortIfConfirm();
-          }
-        }, 500);
-      } else {
-        if (event.data.editor.cancelButton) {
-          event.data.editor.blurTimer = setTimeout(function () {
-            if (!event.data.editor.userClicked) {
-              event.data.editor.update();
-            }
-          }, 500);
-        } else {
-          event.data.editor.update();
-        }
-      }
-    },
-
-    submitHandler : function(event) {
-      event.data.editor.userClicked = true;
-      clearTimeout(event.data.editor.blurTimer);
-      event.data.editor.update();
-    },
-
-    cancelButtonHandler : function(event) {
-      event.data.editor.userClicked = true;
-      clearTimeout(event.data.editor.blurTimer);
-      event.data.editor.abortIfConfirm();
-      event.stopPropagation(); // Without this, click isn't handled
-    },
-      
-    // START METAMAPS CODE
-    keydownHandler : function(event) {
-      if (event.keyCode == 13 && !event.shiftKey) {
-        event.preventDefault();
-        event.stopPropagation();
-        return false;
-      }
-    },
-    // END METAMAPS CODE
-      
-    keyupHandler : function(event) {
-      if (event.keyCode == 27) {
-        event.data.editor.abortIfConfirm();
-      }
-      // START METAMAPS CODE
-      else if (event.keyCode == 13 && !event.shiftKey) {
-        event.data.editor.update();
-      }
-      // END METAMAPS CODE
-    }
-  }
-};
-
-jQuery.fn.best_in_place = function() {
-
-  function setBestInPlace(element) {
-    if (!element.data('bestInPlaceEditor')) {
-      element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
-      return true;
-    }
-  }
-
-  jQuery(this.context).delegate(this.selector, 'click', function () {
-    var el = jQuery(this);
-    if (setBestInPlace(el))
-      el.click();
-  });
-
-  this.each(function () {
-    setBestInPlace(jQuery(this));
-  });
-
-  return this;
-};
-
-
-
-/**
-* @name             Elastic
-* @descripton           Elastic is Jquery plugin that grow and shrink your textareas automaticliy
-* @version            1.6.5
-* @requires           Jquery 1.2.6+
-*
-* @author             Jan Jarfalk
-* @author-email         jan.jarfalk@unwrongest.com
-* @author-website         http://www.unwrongest.com
-*
-* @licens             MIT License - http://www.opensource.org/licenses/mit-license.php
-*/
-
-(function(jQuery){
-  if (typeof jQuery.fn.elastic !== 'undefined') return;
-
-  jQuery.fn.extend({
-    elastic: function() {
-      //  We will create a div clone of the textarea
-      //  by copying these attributes from the textarea to the div.
-      var mimics = [
-        'paddingTop',
-        'paddingRight',
-        'paddingBottom',
-        'paddingLeft',
-        'fontSize',
-        'lineHeight',
-        'fontFamily',
-        'width',
-        'fontWeight'];
-
-      return this.each( function() {
-
-        // Elastic only works on textareas
-        if ( this.type != 'textarea' ) {
-          return false;
-        }
-
-        var $textarea = jQuery(this),
-          $twin   = jQuery('<div />').css({'position': 'absolute','display':'none','word-wrap':'break-word'}),
-          lineHeight  = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
-          minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
-          maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
-          goalheight  = 0,
-          i       = 0;
-
-        // Opera returns max-height of -1 if not set
-        if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
-
-        // Append the twin to the DOM
-        // We are going to meassure the height of this, not the textarea.
-        $twin.appendTo($textarea.parent());
-
-        // Copy the essential styles (mimics) from the textarea to the twin
-        i = mimics.length;
-        while(i--){
-          $twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
-        }
-
-
-        // Sets a given height and overflow state on the textarea
-        function setHeightAndOverflow(height, overflow){
-          curratedHeight = Math.floor(parseInt(height,10));
-          if($textarea.height() != curratedHeight){
-            $textarea.css({'height': curratedHeight + 'px','overflow':overflow});
-
-          }
-        }
-
-
-        // This function will update the height of the textarea if necessary
-        function update() {
-
-          // Get curated content from the textarea.
-          var textareaContent = $textarea.val().replace(/&/g,'&amp;').replace(/  /g, '&nbsp;').replace(/<|>/g, '&gt;').replace(/\n/g, '<br />');
-
-          // Compare curated content with curated twin.
-          var twinContent = $twin.html().replace(/<br>/ig,'<br />');
-
-          if(textareaContent+'&nbsp;' != twinContent){
-
-            // Add an extra white space so new rows are added when you are at the end of a row.
-            $twin.html(textareaContent+'&nbsp;');
-
-            // Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
-            if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
-
-              var goalheight = $twin.height()+lineHeight;
-              if(goalheight >= maxheight) {
-                setHeightAndOverflow(maxheight,'auto');
-              } else if(goalheight <= minheight) {
-                setHeightAndOverflow(minheight,'hidden');
-              } else {
-                setHeightAndOverflow(goalheight,'hidden');
-              }
-
-            }
-
-          }
-
-        }
-
-        // Hide scrollbars
-        $textarea.css({'overflow':'hidden'});
-
-        // Update textarea size on keyup, change, cut and paste
-        $textarea.bind('keyup change cut paste', function(){
-          update();
-        });
-
-        // Compact textarea on blur
-        // Lets animate this....
-        $textarea.bind('blur',function(){
-          if($twin.height() < maxheight){
-            if($twin.height() > minheight) {
-              $textarea.height($twin.height());
-            } else {
-              $textarea.height(minheight);
-            }
-          }
-        });
-
-        // And this line is to catch the browser paste event
-        $textarea.on("input paste", function(e){ setTimeout( update, 250); });
-
-        // Run update once when elastic is initialized
-        update();
-
-      });
-
-        }
-    });
-})(jQuery);
diff --git a/app/assets/javascripts/lib/jquery.purr.js b/app/assets/javascripts/lib/jquery.purr.js
deleted file mode 100644
index 1972165b..00000000
--- a/app/assets/javascripts/lib/jquery.purr.js
+++ /dev/null
@@ -1,180 +0,0 @@
-/**
- * jquery.purr.js
- * Copyright (c) 2008 Net Perspective (net-perspective.com)
- * Licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
- * 
- * @author R.A. Ray
- * @projectDescription	jQuery plugin for dynamically displaying unobtrusive messages in the browser. Mimics the behavior of the MacOS program "Growl."
- * @version 0.1.0
- * 
- * @requires jquery.js (tested with 1.2.6)
- * 
- * @param fadeInSpeed 					int - Duration of fade in animation in miliseconds
- * 													default: 500
- *	@param fadeOutSpeed  				int - Duration of fade out animationin miliseconds
- 														default: 500
- *	@param removeTimer  				int - Timeout, in miliseconds, before notice is removed once it is the top non-sticky notice in the list
- 														default: 4000
- *	@param isSticky 						bool - Whether the notice should fade out on its own or wait to be manually closed
- 														default: false
- *	@param usingTransparentPNG 	bool - Whether or not the notice is using transparent .png images in its styling
- 														default: false
- */
-
-( function( $ ) {
-	
-	$.purr = function ( notice, options )
-	{ 
-		// Convert notice to a jQuery object
-		notice = $( notice );
-		
-		// Add a class to denote the notice as not sticky
-		if ( !options.isSticky )
-		{
-			notice.addClass( 'not-sticky' );
-		};
-		
-		// Get the container element from the page
-		var cont = document.getElementById( 'purr-container' );
-		
-		// If the container doesn't yet exist, we need to create it
-		if ( !cont )
-		{
-			cont = '<div id="purr-container"></div>';
-		}
-		
-		// Convert cont to a jQuery object
-		cont = $( cont );
-		
-		// Add the container to the page
-		$( 'body' ).append( cont );
-			
-		notify();
-
-		function notify ()
-		{	
-			// Set up the close button
-			var close = document.createElement( 'a' );
-			$( close ).attr(	
-				{
-					className: 'close',
-					href: '#close',
-					innerHTML: 'Close'
-				}
-			)
-				.appendTo( notice )
-					.click( function ()
-						{
-							removeNotice();
-							
-							return false;
-						}
-					);
-			
-			// Add the notice to the page and keep it hidden initially
-			notice.appendTo( cont )
-				.hide();
-				
-			if ( jQuery.browser.msie && options.usingTransparentPNG )
-			{
-				// IE7 and earlier can't handle the combination of opacity and transparent pngs, so if we're using transparent pngs in our
-				// notice style, we'll just skip the fading in.
-				notice.show();
-			}
-			else
-			{
-				//Fade in the notice we just added
-				notice.fadeIn( options.fadeInSpeed );
-			}
-			
-			// Set up the removal interval for the added notice if that notice is not a sticky
-			if ( !options.isSticky )
-			{
-				var topSpotInt = setInterval( function ()
-				{
-					// Check to see if our notice is the first non-sticky notice in the list
-					if ( notice.prevAll( '.not-sticky' ).length == 0 )
-					{ 
-						// Stop checking once the condition is met
-						clearInterval( topSpotInt );
-						
-						// Call the close action after the timeout set in options
-						setTimeout( function ()
-							{
-								removeNotice();
-							}, options.removeTimer
-						);
-					}
-				}, 200 );	
-			}
-		}
-
-		function removeNotice ()
-		{
-			// IE7 and earlier can't handle the combination of opacity and transparent pngs, so if we're using transparent pngs in our
-			// notice style, we'll just skip the fading out.
-			if ( jQuery.browser.msie && options.usingTransparentPNG )
-			{
-				notice.css( { opacity: 0	} )
-					.animate( 
-						{ 
-							height: '0px' 
-						}, 
-						{ 
-							duration: options.fadeOutSpeed, 
-							complete:  function ()
-								{
-									notice.remove();
-								} 
-							} 
-					);
-			}
-			else
-			{
-				// Fade the object out before reducing its height to produce the sliding effect
-				notice.animate( 
-					{ 
-						opacity: '0'
-					}, 
-					{ 
-						duration: options.fadeOutSpeed, 
-						complete: function () 
-							{
-								notice.animate(
-									{
-										height: '0px'
-									},
-									{
-										duration: options.fadeOutSpeed,
-										complete: function ()
-											{
-												notice.remove();
-											}
-									}
-								);
-							}
-					} 
-				);
-			}
-		};
-	};
-	
-	$.fn.purr = function ( options )
-	{
-		options = options || {};
-		options.fadeInSpeed = options.fadeInSpeed || 500;
-		options.fadeOutSpeed = options.fadeOutSpeed || 500;
-		options.removeTimer = options.removeTimer || 4000;
-		options.isSticky = options.isSticky || false;
-		options.usingTransparentPNG = options.usingTransparentPNG || false;
-		
-		this.each( function() 
-			{
-				new $.purr( this, options );
-			}
-		);
-		
-		return this;
-	};
-})( jQuery );
-
diff --git a/app/assets/stylesheets/base.css.erb b/app/assets/stylesheets/base.css.erb
index 5b0fcf84..6cfb6b57 100644
--- a/app/assets/stylesheets/base.css.erb
+++ b/app/assets/stylesheets/base.css.erb
@@ -143,6 +143,16 @@
   margin-top:5px;
 }
 
+.CardOnGraph .desc ol,
+.CardOnGraph .desc ul {
+  margin-left: 1em;
+
+}
+.CardOnGraph .desc a:hover {
+  text-decoration: underline;
+  opacity: 0.9;
+}
+
 .CardOnGraph .best_in_place_desc {
   display:block;
   margin-top:2px;	
diff --git a/app/views/layouts/_templates.html.erb b/app/views/layouts/_templates.html.erb
index ff41c7dc..921a7d88 100644
--- a/app/views/layouts/_templates.html.erb
+++ b/app/views/layouts/_templates.html.erb
@@ -183,11 +183,12 @@
         <span class="title"> 
             <div class="titleWrapper" id="titleActivator">      
               <span class="best_in_place best_in_place_name"                      
-                    data-url="/topics/{{id}}"                                     
-                    data-object="topic"                                           
-                    data-attribute="name"
-                    data-activator="#titleActivator"           
-                    data-type="textarea">{{name}}</span>
+                    data-bip-url="/topics/{{id}}"                                     
+                    data-bip-object="topic"                                           
+                    data-bip-attribute="name"
+                    data-bip-activator="#titleActivator"           
+                    data-bip-value="{{name}}"
+                    data-bip-type="textarea">{{name}}</span>
             </div> 
         </span>
         <div class="links">
@@ -220,7 +221,7 @@
         </div>
         <div class="scroll">
             <div class="desc">
-                <span class="best_in_place best_in_place_desc" data-url="/topics/{{id}}" data-object="topic" data-nil="{{desc_nil}}" data-attribute="desc" data-type="textarea">{{desc}}</span> 
+                <span class="best_in_place best_in_place_desc" data-bip-url="/topics/{{id}}" data-bip-object="topic" data-bip-nil="{{desc_nil}}" data-bip-attribute="desc" data-bip-type="textarea" data-bip-value="{{desc_markdown}}">{{{desc_html}}}</span>
                 <div class="clearfloat"></div>
             </div>
         </div>
diff --git a/app/views/maps/_mapinfobox.html.erb b/app/views/maps/_mapinfobox.html.erb
index 5a158d2d..8e6b2dba 100644
--- a/app/views/maps/_mapinfobox.html.erb
+++ b/app/views/maps/_mapinfobox.html.erb
@@ -16,7 +16,7 @@
   <% if @map %>
   <div class="mapInfoName" id="mapInfoName">
     <% if policy(@map).update? %>
-      <span class="best_in_place best_in_place_name" id="best_in_place_map_<%= @map.id %>_name" data-url="/maps/<%= @map.id %>" data-object="map" data-attribute="name" data-type="textarea" data-activator="#mapInfoName"><%= @map.name %></span>    
+      <span class="best_in_place best_in_place_name" id="best_in_place_map_<%= @map.id %>_name" data-bip-url="/maps/<%= @map.id %>" data-bip-object="map" data-bip-attribute="name" data-bip-type="textarea" data-bip-activator="#mapInfoName" data-bip-value="<%= @map.name %>"><%= @map.name %></span>    
     <% else %>
       <%= @map.name %>
     <% end %>
@@ -67,7 +67,7 @@
   
   <div class="mapInfoDesc" id="mapInfoDesc">
     <% if policy(@map).update? %>
-      <span class="best_in_place best_in_place_desc" id="best_in_place_map_<%= @map.id %>_desc" data-url="/maps/<%= @map.id %>" data-object="map" data-attribute="desc" data-nil="Click to add description..." data-type="textarea" data-activator="#mapInfoDesc"><%= @map.desc %></span>
+      <span class="best_in_place best_in_place_desc" id="best_in_place_map_<%= @map.id %>_desc" data-bip-url="/maps/<%= @map.id %>" data-bip-object="map" data-bip-attribute="desc" data-bip-nil="Click to add description..." data-bip-type="textarea" data-bip-activator="#mapInfoDesc" data-bip-value="<%= @map.desc %>"><%= @map.desc %></span>
     <% else %>
       <%= @map.desc %>
     <% end %>
diff --git a/frontend/src/Metamaps/Listeners.js b/frontend/src/Metamaps/Listeners.js
index cf3365f3..db78323d 100644
--- a/frontend/src/Metamaps/Listeners.js
+++ b/frontend/src/Metamaps/Listeners.js
@@ -23,7 +23,6 @@ const Listeners = {
           if (e.target.className !== 'chat-input') {
             JIT.enterKeyHandler()
           }
-          e.preventDefault()
           break
         case 27: // if esc key is pressed
           JIT.escKeyHandler()
diff --git a/frontend/src/Metamaps/Map/InfoBox.js b/frontend/src/Metamaps/Map/InfoBox.js
index 0d3a5c5f..79fa6c4d 100644
--- a/frontend/src/Metamaps/Map/InfoBox.js
+++ b/frontend/src/Metamaps/Map/InfoBox.js
@@ -1,5 +1,7 @@
 /* global Metamaps, $, Hogan, Bloodhound, Countable */
 
+import outdent from 'outdent'
+
 import Active from '../Active'
 import GlobalUI from '../GlobalUI'
 import Router from '../Router'
@@ -19,8 +21,27 @@ const InfoBox = {
   changing: false,
   selectingPermission: false,
   changePermissionText: "<div class='tooltips'>As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.</div>",
-  nameHTML: '<span class="best_in_place best_in_place_name" id="best_in_place_map_{{id}}_name" data-url="/maps/{{id}}" data-object="map" data-attribute="name" data-type="textarea" data-activator="#mapInfoName">{{name}}</span>',
-  descHTML: '<span class="best_in_place best_in_place_desc" id="best_in_place_map_{{id}}_desc" data-url="/maps/{{id}}" data-object="map" data-attribute="desc" data-nil="Click to add description..." data-type="textarea" data-activator="#mapInfoDesc">{{desc}}</span>',
+  nameHTML: outdent`
+    <span class="best_in_place best_in_place_name"
+      id="best_in_place_map_{{id}}_name"
+      data-bip-url="/maps/{{id}}"
+      data-bip-object="map"
+      data-bip-attribute="name"
+      data-bip-type="textarea"
+      data-bip-activator="#mapInfoName"
+      data-bip-value="{{name}}"
+    >{{name}}</span>`,
+  descHTML: outdent`
+    <span class="best_in_place best_in_place_desc"
+      id="best_in_place_map_{{id}}_desc"
+      data-bip-url="/maps/{{id}}"
+      data-bip-object="map"
+      data-bip-attribute="desc"
+      data-bip-nil="Click to add description..."
+      data-bip-type="textarea"
+      data-bip-activator="#mapInfoDesc"
+      data-bip-value="{{desc}}"
+    >{{desc}}</span>`,
   init: function () {
     var self = InfoBox
 
@@ -152,6 +173,13 @@ const InfoBox = {
       Active.Map.trigger('saved')
     })
 
+    $('.mapInfoDesc .best_in_place_desc, .mapInfoName .best_in_place_name').unbind('keypress').keypress(function(e) {
+      const ENTER = 13
+      if (e.which === ENTER) {
+        $(this).data('bestInPlaceEditor').update()
+      }
+    })
+
     $('.yourMap .mapPermission').unbind().click(self.onPermissionClick)
     // .yourMap in the unbind/bind is just a namespace for the events
     // not a reference to the class .yourMap on the .mapInfoBox
diff --git a/frontend/src/Metamaps/SynapseCard.js b/frontend/src/Metamaps/SynapseCard.js
index 8203657d..303b98cf 100644
--- a/frontend/src/Metamaps/SynapseCard.js
+++ b/frontend/src/Metamaps/SynapseCard.js
@@ -80,11 +80,12 @@ const SynapseCard = {
     // desc editing form
     $('#editSynUpperBar').append('<div id="edit_synapse_desc"></div>')
     $('#edit_synapse_desc').attr('class', 'best_in_place best_in_place_desc')
-    $('#edit_synapse_desc').attr('data-object', 'synapse')
-    $('#edit_synapse_desc').attr('data-attribute', 'desc')
-    $('#edit_synapse_desc').attr('data-type', 'textarea')
-    $('#edit_synapse_desc').attr('data-nil', data_nil)
-    $('#edit_synapse_desc').attr('data-url', '/synapses/' + synapse.id)
+    $('#edit_synapse_desc').attr('data-bip-object', 'synapse')
+    $('#edit_synapse_desc').attr('data-bip-attribute', 'desc')
+    $('#edit_synapse_desc').attr('data-bip-type', 'textarea')
+    $('#edit_synapse_desc').attr('data-bip-nil', data_nil)
+    $('#edit_synapse_desc').attr('data-bip-url', '/synapses/' + synapse.id)
+    $('#edit_synapse_desc').attr('data-bip-value', synapse.get('desc'))
     $('#edit_synapse_desc').html(synapse.get('desc'))
 
     // if edge data is blank or just whitespace, populate it with data_nil
@@ -96,6 +97,12 @@ const SynapseCard = {
       }
     }
 
+    $('#edit_synapse_desc').keypress(function (e) {
+      const ENTER = 13
+      if (e.which === ENTER) {
+        $(this).data('bestInPlaceEditor').update()
+      }
+    })
     $('#edit_synapse_desc').bind('ajax:success', function () {
       var desc = $(this).html()
       if (desc == data_nil) {
diff --git a/frontend/src/Metamaps/TopicCard.js b/frontend/src/Metamaps/TopicCard.js
index 40c51fbd..0956c07b 100644
--- a/frontend/src/Metamaps/TopicCard.js
+++ b/frontend/src/Metamaps/TopicCard.js
@@ -265,6 +265,12 @@ const TopicCard = {
       bipName.bind('best_in_place:deactivate', function () {
         $('.nameCounter.forTopic').remove()
       })
+      bipName.keypress(function(e) {
+        const ENTER = 13
+        if (e.which === ENTER) { // enter
+          $(this).data('bestInPlaceEditor').update()
+        }
+      })
 
       // bind best_in_place ajax callbacks
       bipName.bind('ajax:success', function () {
@@ -273,12 +279,24 @@ const TopicCard = {
         topic.trigger('saved')
       })
 
-      $(showCard).find('.best_in_place_desc').bind('ajax:success', function () {
-        this.innerHTML = this.innerHTML.replace(/\r/g, '')
-        var desc = $(this).html() === $(this).data('nil') ? '' : $(this).html()
+      // this is for all subsequent renders after in-place editing the desc field
+      const bipDesc = $(showCard).find('.best_in_place_desc')
+      bipDesc.bind('ajax:success', function () {
+        var desc = $(this).html() === $(this).data('bip-nil')
+          ? ''
+          : $(this).text()
         topic.set('desc', desc)
+        $(this).data('bip-value', desc)
+        this.innerHTML = Util.mdToHTML(desc)
         topic.trigger('saved')
       })
+      bipDesc.keypress(function(e) {
+        // allow typing Enter with Shift+Enter
+        const ENTER = 13
+        if (e.shiftKey === false && e.which === ENTER) {
+          $(this).data('bestInPlaceEditor').update()
+        }
+      })
     }
 
     var permissionLiClick = function (event) {
@@ -397,8 +415,6 @@ const TopicCard = {
     } else {
     }
 
-    var desc_nil = 'Click to add description...'
-
     nodeValues.attachmentsHidden = ''
     if (topic.get('link') && topic.get('link') !== '') {
       nodeValues.embeds = '<a href="' + topic.get('link') + '" id="embedlyLink" target="_blank" data-card-description="0">'
@@ -454,8 +470,11 @@ const TopicCard = {
     nodeValues.date = topic.getDate()
     // the code for this is stored in /views/main/_metacodeOptions.html.erb
     nodeValues.metacode_select = $('#metacodeOptions').html()
-    nodeValues.desc_nil = desc_nil
-    nodeValues.desc = (topic.get('desc') == '' && authorized) ? desc_nil : topic.get('desc')
+    nodeValues.desc_nil = 'Click to add description...'
+    nodeValues.desc_markdown = (topic.get('desc') === '' && authorized)
+     ? nodeValues.desc_nil
+     : topic.get('desc')
+    nodeValues.desc_html = Util.mdToHTML(nodeValues.desc_markdown)
     return nodeValues
   }
 }
diff --git a/frontend/src/Metamaps/Util.js b/frontend/src/Metamaps/Util.js
index 9eb715de..f1f8b39c 100644
--- a/frontend/src/Metamaps/Util.js
+++ b/frontend/src/Metamaps/Util.js
@@ -1,3 +1,5 @@
+import { Parser, HtmlRenderer } from 'commonmark'
+
 import Visualize from './Visualize'
 
 const Util = {
@@ -119,6 +121,9 @@ const Util = {
   },
   checkURLisYoutubeVideo: function (url) {
     return (url.match(/^https?:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null)
+  },
+  mdToHTML: text => {
+    return new HtmlRenderer().render(new Parser().parse(text))
   }
 }
 
diff --git a/package.json b/package.json
index 82cd120d..8b434d2a 100644
--- a/package.json
+++ b/package.json
@@ -26,8 +26,9 @@
     "babel-preset-es2015": "6.14.0",
     "babel-preset-react": "6.11.1",
     "backbone": "1.0.0",
-    "underscore": "1.4.4",
+    "commonmark": "0.26.0",
     "csv-parse": "1.1.7",
+    "json-loader": "0.5.4",
     "lodash": "4.16.1",
     "node-uuid": "1.4.7",
     "outdent": "0.2.1",
@@ -35,6 +36,7 @@
     "react-dom": "15.3.2",
     "react-dropzone": "3.6.0",
     "socket.io": "0.9.12",
+    "underscore": "1.4.4",
     "webpack": "1.13.2"
   },
   "devDependencies": {
diff --git a/webpack.config.js b/webpack.config.js
index 91498abd..644ff002 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -21,6 +21,9 @@ const config = module.exports = {
   plugins,
   devtool,
   module: {
+    preLoaders: [
+      { test: /\.json$/, loader: 'json' }
+    ],
     loaders: [
       {
         test: /\.(js|jsx)?$/,