From 51f3e022fef8309c22296c7d67f8e4fa9047a5ce Mon Sep 17 00:00:00 2001 From: Eike Foken Date: Wed, 14 Sep 2011 00:09:50 +0200 Subject: [PATCH] Add new in-place editor --- application/controllers/ajax.php | 34 +- application/models/project.php | 2 +- application/views/header.php | 5 +- application/views/projects/detail.php | 10 + assets/css/style.css | 19 + assets/js/jquery.editinplace.js | 646 ++++++++++++++++++ ...{tablednd.jquery.js => jquery.tablednd.js} | 0 assets/js/scattport.js | 60 +- 8 files changed, 711 insertions(+), 65 deletions(-) create mode 100644 assets/js/jquery.editinplace.js rename assets/js/{tablednd.jquery.js => jquery.tablednd.js} (100%) diff --git a/application/controllers/ajax.php b/application/controllers/ajax.php index 037e3e0..a00d1bf 100644 --- a/application/controllers/ajax.php +++ b/application/controllers/ajax.php @@ -35,6 +35,11 @@ class Ajax extends CI_Controller { */ public function __construct() { parent::__construct(); + + // security check + if (!$this->input->is_ajax_request()) { + show_error(_('No AJAX request was made.')); + } } /** @@ -49,12 +54,31 @@ class Ajax extends CI_Controller { } /** - * Saves a projects description. + * Saves the projects description. + * + * @param string $projectId */ - public function save_project() { + public function update_project($projectId) { $this->load->model('project'); - $data['description'] = $this->input->post('content'); - $this->project->update($this->session->userdata('active_project'), $data); + + $data['description'] = $this->input->post('description'); + $this->project->update($data, $projectId); + + $this->output->set_output($data['description']); + } + + /** + * Saves the experiments description. + * + * @param string $experimentId + */ + public function update_experiment($experimentId) { + $this->load->model('experiment'); + + $data['description'] = $this->input->post('description'); + $this->experiment->update($data, $experimentId); + + $this->output->set_output($data['description']); } /** @@ -67,6 +91,8 @@ class Ajax extends CI_Controller { /** * Displays the description of parameters. + * + * @param string $parameterId */ public function parameter_help($parameterId) { $this->load->model('parameter'); diff --git a/application/models/project.php b/application/models/project.php index aa4311f..6a24d27 100644 --- a/application/models/project.php +++ b/application/models/project.php @@ -213,7 +213,7 @@ class Project extends CI_Model { * @param integer $projectId The ID of the project to update * @param array $data Array with data to update */ - public function update($projectId, $data) { + public function update($data, $projectId) { return $this->db->where('id', $projectId)->update('projects', $data); } diff --git a/application/views/header.php b/application/views/header.php index e08c3a8..cab8885 100644 --- a/application/views/header.php +++ b/application/views/header.php @@ -13,7 +13,10 @@ - + + + + diff --git a/application/views/projects/detail.php b/application/views/projects/detail.php index 6492026..a5f7395 100644 --- a/application/views/projects/detail.php +++ b/application/views/projects/detail.php @@ -130,4 +130,14 @@ + + load->view('footer');?> diff --git a/assets/css/style.css b/assets/css/style.css index e75c226..2f81973 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -684,3 +684,22 @@ html ul.tabs li.active a:hover { #dashboard a.button strong { font-size: 24px; } + +/* Inplace editor */ + +input.inplace_field, textarea.inplace_field { + margin: 5px 0 5px 0; + padding: 3px 5px; + border: 1px solid #d2d2d2; + font-size: 12px; +} + +a.inplace_save { + background: url(../images/icons/tick.png) 10px center no-repeat #f3f3f3; + padding-left: 30px; +} + +a.inplace_cancel { + background: url(../images/icons/slash.png) 10px center no-repeat #f3f3f3; + padding-left: 30px; +} diff --git a/assets/js/jquery.editinplace.js b/assets/js/jquery.editinplace.js new file mode 100644 index 0000000..4507a6c --- /dev/null +++ b/assets/js/jquery.editinplace.js @@ -0,0 +1,646 @@ +/* + +A jQuery edit in place plugin + +Version 2.3.0 + +Authors: + Dave Hauenstein + Martin Häcker + +Project home: + http://code.google.com/p/jquery-in-place-editor/ + +Patches with tests welcomed! For guidance see the tests . To submit, attach them to the bug tracker. + +License: +This source file is subject to the BSD license bundled with this package. +Available online: {@link http://www.opensource.org/licenses/bsd-license.php} +If you did not receive a copy of the license, and are unable to obtain it, +learn to use a search engine. + +*/ + +(function($){ + +$.fn.editInPlace = function(options) { + + var settings = $.extend({}, $.fn.editInPlace.defaults, options); + assertMandatorySettingsArePresent(settings); + preloadImage(settings.saving_image); + + return this.each(function() { + var dom = $(this); + // This won't work with live queries as there is no specific element to attach this + // one way to deal with this could be to store a reference to self and then compare that in click? + if (dom.data('editInPlace')) + return; // already an editor here + dom.data('editInPlace', true); + + new InlineEditor(settings, dom).init(); + }); +}; + +/// Switch these through the dictionary argument to $(aSelector).editInPlace(overideOptions) +/// Required Options: Either url or callback, so the editor knows what to do with the edited values. +$.fn.editInPlace.defaults = { + url: "", // string: POST URL to send edited content + bg_over: "#ffc", // string: background color of hover of unactivated editor + bg_out: "transparent", // string: background color on restore from hover + hover_class: "", // string: class added to root element during hover. Will override bg_over and bg_out + show_buttons: true, // boolean: will show the buttons: cancel or save; will automatically cancel out the onBlur functionality + save_button: '

Save', // string: image button tag to use as “Save” button + cancel_button: 'Cancel

', // string: image button tag to use as “Cancel” button + params: "", // string: example: first_name=dave&last_name=hauenstein extra paramters sent via the post request to the server + field_type: "textarea", // string: "text", "textarea", or "select"; The type of form field that will appear on instantiation + default_text: "(Click here to add text)", // string: text to show up if the element that has this functionality is empty + use_html: false, // boolean, set to true if the editor should use jQuery.fn.html() to extract the value to show from the dom node + textarea_rows: 6, // integer: set rows attribute of textarea, if field_type is set to textarea. Use CSS if possible though + textarea_cols: 60, // integer: set cols attribute of textarea, if field_type is set to textarea. Use CSS if possible though + select_text: "Choose new value", // string: default text to show up in select box + select_options: "", // string or array: Used if field_type is set to 'select'. Can be comma delimited list of options 'textandValue,text:value', Array of options ['textAndValue', 'text:value'] or array of arrays ['textAndValue', ['text', 'value']]. The last form is especially usefull if your labels or values contain colons) + text_size: null, // integer: set cols attribute of text input, if field_type is set to text. Use CSS if possible though + + // Specifying callback_skip_dom_reset will disable all saving_* options + saving_text: undefined, // string: text to be used when server is saving information. Example "Saving..." + saving_image: "", // string: uses saving text specify an image location instead of text while server is saving + saving_animation_color: 'transparent', // hex color string, will be the color the pulsing animation during the save pulses to. Note: Only works if jquery-ui is loaded + + value_required: false, // boolean: if set to true, the element will not be saved unless a value is entered + element_id: "element_id", // string: name of parameter holding the id or the editable + update_value: "update_value", // string: name of parameter holding the updated/edited value + original_value: 'original_value', // string: name of parameter holding the updated/edited value + original_html: "original_html", // string: name of parameter holding original_html value of the editable /* DEPRECATED in 2.2.0 */ use original_value instead. + save_if_nothing_changed: false, // boolean: submit to function or server even if the user did not change anything + on_blur: "save", // string: "save" or null; what to do on blur; will be overridden if show_buttons is true + cancel: "", // string: if not empty, a jquery selector for elements that will not cause the editor to open even though they are clicked. E.g. if you have extra buttons inside editable fields + + // All callbacks will have this set to the DOM node of the editor that triggered the callback + + callback: null, // function: function to be called when editing is complete; cancels ajax submission to the url param. Prototype: function(idOfEditor, enteredText, orinalHTMLContent, settingsParams, callbacks). The function needs to return the value that should be shown in the dom. Returning undefined means cancel and will restore the dom and trigger an error. callbacks is a dictionary with two functions didStartSaving and didEndSaving() that you can use to tell the inline editor that it should start and stop any saving animations it has configured. /* DEPRECATED in 2.1.0 */ Parameter idOfEditor, use $(this).attr('id') instead + callback_skip_dom_reset: false, // boolean: set this to true if the callback should handle replacing the editor with the new value to show + success: null, // function: this function gets called if server responds with a success. Prototype: function(newEditorContentString) + error: null, // function: this function gets called if server responds with an error. Prototype: function(request) + error_sink: function(idOfEditor, errorString) { alert(errorString); }, // function: gets id of the editor and the error. Make sure the editor has an id, or it will just be undefined. If set to null, no error will be reported. /* DEPRECATED in 2.1.0 */ Parameter idOfEditor, use $(this).attr('id') instead + preinit: null, // function: this function gets called after a click on an editable element but before the editor opens. If you return false, the inline editor will not open. Prototype: function(currentDomNode). DEPRECATED in 2.2.0 use delegate shouldOpenEditInPlace call instead + postclose: null, // function: this function gets called after the inline editor has closed and all values are updated. Prototype: function(currentDomNode). DEPRECATED in 2.2.0 use delegate didCloseEditInPlace call instead + delegate: null // object: if it has methods with the name of the callbacks documented below in delegateExample these will be called. This means that you just need to impelment the callbacks you are interested in. +}; + +// Lifecycle events that the delegate can implement +// this will always be fixed to the delegate +var delegateExample = { + // called while opening the editor. + // return false to prevent editor from opening + shouldOpenEditInPlace: function(aDOMNode, aSettingsDict, triggeringEvent) {}, + // return content to show in inplace editor + willOpenEditInPlace: function(aDOMNode, aSettingsDict) {}, + didOpenEditInPlace: function(aDOMNode, aSettingsDict) {}, + + // called while closing the editor + // return false to prevent the editor from closing + shouldCloseEditInPlace: function(aDOMNode, aSettingsDict, triggeringEvent) {}, + // return value will be shown during saving + willCloseEditInPlace: function(aDOMNode, aSettingsDict) {}, + didCloseEditInPlace: function(aDOMNode, aSettingsDict) {}, + + missingCommaErrorPreventer:'' +}; + + +function InlineEditor(settings, dom) { + this.settings = settings; + this.dom = dom; + this.originalValue = null; + this.didInsertDefaultText = false; + this.shouldDelayReinit = false; +}; + +$.extend(InlineEditor.prototype, { + + init: function() { + this.setDefaultTextIfNeccessary(); + this.connectOpeningEvents(); + }, + + reinit: function() { + if (this.shouldDelayReinit) + return; + + this.triggerCallback(this.settings.postclose, /* DEPRECATED in 2.1.0 */ this.dom); + this.triggerDelegateCall('didCloseEditInPlace'); + + this.markEditorAsInactive(); + this.connectOpeningEvents(); + }, + + setDefaultTextIfNeccessary: function() { + if('' !== this.dom.html()) + return; + + this.dom.html(this.settings.default_text); + this.didInsertDefaultText = true; + }, + + connectOpeningEvents: function() { + var that = this; + this.dom + .bind('mouseenter.editInPlace', function(){ that.addHoverEffect(); }) + .bind('mouseleave.editInPlace', function(){ that.removeHoverEffect(); }) + .bind('click.editInPlace', function(anEvent){ that.openEditor(anEvent); }); + }, + + disconnectOpeningEvents: function() { + // prevent re-opening the editor when it is already open + this.dom.unbind('.editInPlace'); + }, + + addHoverEffect: function() { + if (this.settings.hover_class) + this.dom.addClass(this.settings.hover_class); + else + this.dom.css("background-color", this.settings.bg_over); + }, + + removeHoverEffect: function() { + if (this.settings.hover_class) + this.dom.removeClass(this.settings.hover_class); + else + this.dom.css("background-color", this.settings.bg_out); + }, + + openEditor: function(anEvent) { + if ( ! this.shouldOpenEditor(anEvent)) + return; + + this.disconnectOpeningEvents(); + this.removeHoverEffect(); + this.removeInsertedDefaultTextIfNeccessary(); + this.saveOriginalValue(); + this.markEditorAsActive(); + this.replaceContentWithEditor(); + this.setInitialValue(); + this.workAroundMissingBlurBug(); + this.connectClosingEventsToEditor(); + this.triggerDelegateCall('didOpenEditInPlace'); + }, + + shouldOpenEditor: function(anEvent) { + if (this.isClickedObjectCancelled(anEvent.target)) + return false; + + if (false === this.triggerCallback(this.settings.preinit, /* DEPRECATED in 2.1.0 */ this.dom)) + return false; + + if (false === this.triggerDelegateCall('shouldOpenEditInPlace', true, anEvent)) + return false; + + return true; + }, + + removeInsertedDefaultTextIfNeccessary: function() { + if ( ! this.didInsertDefaultText + || this.dom.html() !== this.settings.default_text) + return; + + this.dom.html(''); + this.didInsertDefaultText = false; + }, + + isClickedObjectCancelled: function(eventTarget) { + if ( ! this.settings.cancel) + return false; + + var eventTargetAndParents = $(eventTarget).parents().andSelf(); + var elementsMatchingCancelSelector = eventTargetAndParents.filter(this.settings.cancel); + return 0 !== elementsMatchingCancelSelector.length; + }, + + saveOriginalValue: function() { + if (this.settings.use_html) + this.originalValue = this.dom.html(); + else + this.originalValue = trim(this.dom.text()); + }, + + restoreOriginalValue: function() { + this.setClosedEditorContent(this.originalValue); + }, + + setClosedEditorContent: function(aValue) { + if (this.settings.use_html) + this.dom.html(aValue); + else + this.dom.text(aValue); + }, + + workAroundMissingBlurBug: function() { + // Strangely, all browser will forget to send a blur event to an input element + // when another one is created and selected programmatically. (at least under some circumstances). + // This means that if another inline editor is opened, existing inline editors will _not_ close + // if they are configured to submit when blurred. + + // Using parents() instead document as base to workaround the fact that in the unittests + // the editor is not a child of window.document but of a document fragment + var ourInput = this.dom.find(':input'); + this.dom.parents(':last').find('.editInPlace-active :input').not(ourInput).blur(); + }, + + replaceContentWithEditor: function() { + var buttons_html = (this.settings.show_buttons) ? this.settings.save_button + ' ' + this.settings.cancel_button : ''; + var editorElement = this.createEditorElement(); // needs to happen before anything is replaced + /* insert the new in place form after the element they click, then empty out the original element */ + this.dom.html('
') + .find('form') + .append(editorElement) + .append(buttons_html); + }, + + createEditorElement: function() { + if (-1 === $.inArray(this.settings.field_type, ['text', 'textarea', 'select'])) + throw "Unknown field_type , supported are 'text', 'textarea' and 'select'"; + + var editor = null; + if ("select" === this.settings.field_type) + editor = this.createSelectEditor(); + else if ("text" === this.settings.field_type) + editor = $(''); + else if ("textarea" === this.settings.field_type) + editor = $('

'; - var button = '

Speichern Abbrechen'; - var revert = $(this).html(); - - $(this).after(textarea + button).remove(); - - $('.save').click(function() { - saveChanges(this, false); - }); - $('.cancel').click(function() { - saveChanges(this, revert); - }); - }).mouseover(function() { - $(this).addClass('editable'); - }).mouseout(function() { - $(this).removeClass('editable'); - }); - /* * Active project selection */