From 0085ce71e6e7d43861fc0f00341e5c3d9093ee5d Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 7 Oct 2016 17:26:20 +0800 Subject: [PATCH] upgrade to best in place 3.0.0 alpha --- app/assets/javascripts/lib/best_in_place.js | 685 +++++++++++++++++ app/assets/javascripts/lib/bip.js | 780 -------------------- app/views/layouts/_templates.html.erb | 13 +- app/views/maps/_mapinfobox.html.erb | 4 +- frontend/src/Metamaps/Listeners.js | 1 - frontend/src/Metamaps/Map/InfoBox.js | 25 +- frontend/src/Metamaps/SynapseCard.js | 11 +- frontend/src/Metamaps/TopicCard.js | 9 +- 8 files changed, 728 insertions(+), 800 deletions(-) create mode 100644 app/assets/javascripts/lib/best_in_place.js delete mode 100644 app/assets/javascripts/lib/bip.js 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('
').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,'&').replace(/ /g, ' ').replace(/<|>/g, '>').replace(/\n/g, '
'); - - // Compare curated content with curated twin. - var twinContent = $twin.html().replace(/
/ig,'
'); - - if(textareaContent+' ' != twinContent){ - - // Add an extra white space so new rows are added when you are at the end of a row. - $twin.html(textareaContent+' '); - - // 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/views/layouts/_templates.html.erb b/app/views/layouts/_templates.html.erb index 59a5ceb8..921a7d88 100644 --- a/app/views/layouts/_templates.html.erb +++ b/app/views/layouts/_templates.html.erb @@ -183,11 +183,12 @@
{{name}} + 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}}
- {{{desc_html}}} + {{{desc_html}}}
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 %>
<% if policy(@map).update? %> - <%= @map.name %> + <%= @map.name %> <% else %> <%= @map.name %> <% end %> @@ -67,7 +67,7 @@
<% if policy(@map).update? %> - <%= @map.desc %> + <%= @map.desc %> <% 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..ddfd72c3 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: "
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.
", - nameHTML: '{{name}}', - descHTML: '{{desc}}', + nameHTML: outdent` + {{name}}`, + descHTML: outdent` + {{desc}}`, init: function () { var self = InfoBox diff --git a/frontend/src/Metamaps/SynapseCard.js b/frontend/src/Metamaps/SynapseCard.js index 8203657d..b1810dac 100644 --- a/frontend/src/Metamaps/SynapseCard.js +++ b/frontend/src/Metamaps/SynapseCard.js @@ -80,11 +80,12 @@ const SynapseCard = { // desc editing form $('#editSynUpperBar').append('
') $('#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 diff --git a/frontend/src/Metamaps/TopicCard.js b/frontend/src/Metamaps/TopicCard.js index 84892450..f74cf18f 100644 --- a/frontend/src/Metamaps/TopicCard.js +++ b/frontend/src/Metamaps/TopicCard.js @@ -274,12 +274,13 @@ const TopicCard = { }) // this is for all subsequent renders after in-place editing the desc field - $(showCard).find('.best_in_place_desc').bind('ajax:success', function () { - var desc = $(this).html() === $(this).data('nil') + 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('bestInPlaceEditor').original_content = desc + $(this).data('bip-value', desc) this.innerHTML = Util.mdToHTML(desc) topic.trigger('saved') }) @@ -458,7 +459,7 @@ const TopicCard = { nodeValues.metacode_select = $('#metacodeOptions').html() nodeValues.desc_nil = 'Click to add description...' nodeValues.desc_markdown = (topic.get('desc') === '' && authorized) - ? desc_nil + ? nodeValues.desc_nil : topic.get('desc') nodeValues.desc_html = Util.mdToHTML(nodeValues.desc_markdown) return nodeValues