Add new in-place editor
This commit is contained in:
@@ -35,6 +35,11 @@ class Ajax extends CI_Controller {
|
|||||||
*/
|
*/
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
parent::__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');
|
$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.
|
* Displays the description of parameters.
|
||||||
|
*
|
||||||
|
* @param string $parameterId
|
||||||
*/
|
*/
|
||||||
public function parameter_help($parameterId) {
|
public function parameter_help($parameterId) {
|
||||||
$this->load->model('parameter');
|
$this->load->model('parameter');
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ class Project extends CI_Model {
|
|||||||
* @param integer $projectId The ID of the project to update
|
* @param integer $projectId The ID of the project to update
|
||||||
* @param array $data Array with data 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);
|
return $this->db->where('id', $projectId)->update('projects', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js" language="javascript" type="text/javascript"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js" language="javascript" type="text/javascript"></script>
|
||||||
|
|
||||||
<?=js_asset('tablednd.jquery.js');?>
|
<?=js_asset('jquery.editinplace.js');?>
|
||||||
|
<?=js_asset('jquery.tablednd.js');?>
|
||||||
|
|
||||||
|
<?=js_asset('minmax.js');?>
|
||||||
<?=js_asset('jsc3d.min.js');?>
|
<?=js_asset('jsc3d.min.js');?>
|
||||||
<?=js_asset('scattport.js');?>
|
<?=js_asset('scattport.js');?>
|
||||||
<?=js_asset('jtip.js');?>
|
<?=js_asset('jtip.js');?>
|
||||||
|
|||||||
@@ -130,4 +130,14 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$('.editInPlace').editInPlace({
|
||||||
|
|
||||||
|
url: BASE_URL + 'ajax/update_project/' + '<?=$project['id']?>',
|
||||||
|
saving_image: SITE_URL + 'images/ajax-loader.gif',
|
||||||
|
update_value: 'description',
|
||||||
|
value_required: true
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<?php $this->load->view('footer');?>
|
<?php $this->load->view('footer');?>
|
||||||
|
|||||||
@@ -684,3 +684,22 @@ html ul.tabs li.active a:hover {
|
|||||||
#dashboard a.button strong {
|
#dashboard a.button strong {
|
||||||
font-size: 24px;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
646
assets/js/jquery.editinplace.js
Normal file
646
assets/js/jquery.editinplace.js
Normal file
@@ -0,0 +1,646 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
A jQuery edit in place plugin
|
||||||
|
|
||||||
|
Version 2.3.0
|
||||||
|
|
||||||
|
Authors:
|
||||||
|
Dave Hauenstein
|
||||||
|
Martin Häcker <spamfaenger [at] gmx [dot] de>
|
||||||
|
|
||||||
|
Project home:
|
||||||
|
http://code.google.com/p/jquery-in-place-editor/
|
||||||
|
|
||||||
|
Patches with tests welcomed! For guidance see the tests </spec/unit/>. 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: '<p><a href="javascript:void(0);" class="button inplace_save">Save</a>', // string: image button tag to use as “Save” button
|
||||||
|
cancel_button: '<a href="javascript:void(0);" class="button inplace_cancel">Cancel</a></p>', // 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('<form class="inplace_form" style="display: inline; margin: 0; padding: 0;"></form>')
|
||||||
|
.find('form')
|
||||||
|
.append(editorElement)
|
||||||
|
.append(buttons_html);
|
||||||
|
},
|
||||||
|
|
||||||
|
createEditorElement: function() {
|
||||||
|
if (-1 === $.inArray(this.settings.field_type, ['text', 'textarea', 'select']))
|
||||||
|
throw "Unknown field_type <fnord>, 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 = $('<input type="text" ' + this.inputNameAndClass()
|
||||||
|
+ ' size="' + this.settings.text_size + '" />');
|
||||||
|
else if ("textarea" === this.settings.field_type)
|
||||||
|
editor = $('<textarea ' + this.inputNameAndClass()
|
||||||
|
+ ' rows="' + this.settings.textarea_rows + '" '
|
||||||
|
+ ' cols="' + this.settings.textarea_cols + '" />');
|
||||||
|
|
||||||
|
return editor;
|
||||||
|
},
|
||||||
|
|
||||||
|
setInitialValue: function() {
|
||||||
|
var initialValue = this.triggerDelegateCall('willOpenEditInPlace', this.originalValue);
|
||||||
|
var editor = this.dom.find(':input');
|
||||||
|
editor.val(initialValue);
|
||||||
|
|
||||||
|
// Workaround for select fields which don't contain the original value.
|
||||||
|
// Somehow the browsers don't like to select the instructional choice (disabled) in that case
|
||||||
|
if (editor.val() !== initialValue)
|
||||||
|
editor.val(''); // selects instructional choice
|
||||||
|
},
|
||||||
|
|
||||||
|
inputNameAndClass: function() {
|
||||||
|
return ' name="inplace_value" class="inplace_field" ';
|
||||||
|
},
|
||||||
|
|
||||||
|
createSelectEditor: function() {
|
||||||
|
var editor = $('<select' + this.inputNameAndClass() + '>'
|
||||||
|
+ '<option disabled="true" value="">' + this.settings.select_text + '</option>'
|
||||||
|
+ '</select>');
|
||||||
|
|
||||||
|
var optionsArray = this.settings.select_options;
|
||||||
|
if ( ! $.isArray(optionsArray))
|
||||||
|
optionsArray = optionsArray.split(',');
|
||||||
|
|
||||||
|
for (var i=0; i<optionsArray.length; i++) {
|
||||||
|
var currentTextAndValue = optionsArray[i];
|
||||||
|
if ( ! $.isArray(currentTextAndValue))
|
||||||
|
currentTextAndValue = currentTextAndValue.split(':');
|
||||||
|
|
||||||
|
var value = trim(currentTextAndValue[1] || currentTextAndValue[0]);
|
||||||
|
var text = trim(currentTextAndValue[0]);
|
||||||
|
|
||||||
|
var option = $('<option>').val(value).text(text);
|
||||||
|
editor.append(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
return editor;
|
||||||
|
},
|
||||||
|
|
||||||
|
connectClosingEventsToEditor: function() {
|
||||||
|
var that = this;
|
||||||
|
function cancelEditorAction(anEvent) {
|
||||||
|
that.handleCancelEditor(anEvent);
|
||||||
|
return false; // stop event bubbling
|
||||||
|
}
|
||||||
|
function saveEditorAction(anEvent) {
|
||||||
|
that.handleSaveEditor(anEvent);
|
||||||
|
return false; // stop event bubbling
|
||||||
|
}
|
||||||
|
|
||||||
|
var form = this.dom.find("form");
|
||||||
|
|
||||||
|
form.find(".inplace_field").focus().select();
|
||||||
|
form.find(".inplace_cancel").click(cancelEditorAction);
|
||||||
|
form.find(".inplace_save").click(saveEditorAction);
|
||||||
|
|
||||||
|
if ( ! this.settings.show_buttons) {
|
||||||
|
// TODO: Firefox has a bug where blur is not reliably called when focus is lost
|
||||||
|
// (for example by another editor appearing)
|
||||||
|
if ("save" === this.settings.on_blur)
|
||||||
|
form.find(".inplace_field").blur(saveEditorAction);
|
||||||
|
else
|
||||||
|
form.find(".inplace_field").blur(cancelEditorAction);
|
||||||
|
|
||||||
|
// workaround for msie & firefox bug where it won't submit on enter if no button is shown
|
||||||
|
if ($.browser.mozilla || $.browser.msie)
|
||||||
|
this.bindSubmitOnEnterInInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
form.keyup(function(anEvent) {
|
||||||
|
// allow canceling with escape
|
||||||
|
var escape = 27;
|
||||||
|
if (escape === anEvent.which)
|
||||||
|
return cancelEditorAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
// workaround for webkit nightlies where they won't submit at all on enter
|
||||||
|
// REFACT: find a way to just target the nightlies
|
||||||
|
if ($.browser.safari)
|
||||||
|
this.bindSubmitOnEnterInInput();
|
||||||
|
|
||||||
|
|
||||||
|
form.submit(saveEditorAction);
|
||||||
|
},
|
||||||
|
|
||||||
|
bindSubmitOnEnterInInput: function() {
|
||||||
|
if ('textarea' === this.settings.field_type)
|
||||||
|
return; // can't enter newlines otherwise
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
this.dom.find(':input').keyup(function(event) {
|
||||||
|
var enter = 13;
|
||||||
|
if (enter === event.which)
|
||||||
|
return that.dom.find('form').submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCancelEditor: function(anEvent) {
|
||||||
|
// REFACT: remove duplication between save and cancel
|
||||||
|
if (false === this.triggerDelegateCall('shouldCloseEditInPlace', true, anEvent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var enteredText = this.dom.find(':input').val();
|
||||||
|
enteredText = this.triggerDelegateCall('willCloseEditInPlace', enteredText);
|
||||||
|
|
||||||
|
this.restoreOriginalValue();
|
||||||
|
if (hasContent(enteredText)
|
||||||
|
&& ! this.isDisabledDefaultSelectChoice())
|
||||||
|
this.setClosedEditorContent(enteredText);
|
||||||
|
this.reinit();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSaveEditor: function(anEvent) {
|
||||||
|
if (false === this.triggerDelegateCall('shouldCloseEditInPlace', true, anEvent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var enteredText = this.dom.find(':input').val();
|
||||||
|
enteredText = this.triggerDelegateCall('willCloseEditInPlace', enteredText);
|
||||||
|
|
||||||
|
if (this.isDisabledDefaultSelectChoice()
|
||||||
|
|| this.isUnchangedInput(enteredText)) {
|
||||||
|
this.handleCancelEditor(anEvent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.didForgetRequiredText(enteredText)) {
|
||||||
|
this.handleCancelEditor(anEvent);
|
||||||
|
this.reportError("Error: You must enter a value to save this field");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showSaving(enteredText);
|
||||||
|
|
||||||
|
if (this.settings.callback)
|
||||||
|
this.handleSubmitToCallback(enteredText);
|
||||||
|
else
|
||||||
|
this.handleSubmitToServer(enteredText);
|
||||||
|
},
|
||||||
|
|
||||||
|
didForgetRequiredText: function(enteredText) {
|
||||||
|
return this.settings.value_required
|
||||||
|
&& ("" === enteredText
|
||||||
|
|| undefined === enteredText
|
||||||
|
|| null === enteredText);
|
||||||
|
},
|
||||||
|
|
||||||
|
isDisabledDefaultSelectChoice: function() {
|
||||||
|
return this.dom.find('option').eq(0).is(':selected:disabled');
|
||||||
|
},
|
||||||
|
|
||||||
|
isUnchangedInput: function(enteredText) {
|
||||||
|
return ! this.settings.save_if_nothing_changed
|
||||||
|
&& this.originalValue === enteredText;
|
||||||
|
},
|
||||||
|
|
||||||
|
showSaving: function(enteredText) {
|
||||||
|
if (this.settings.callback && this.settings.callback_skip_dom_reset)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var savingMessage = enteredText;
|
||||||
|
if (hasContent(this.settings.saving_text))
|
||||||
|
savingMessage = this.settings.saving_text;
|
||||||
|
if(hasContent(this.settings.saving_image))
|
||||||
|
// REFACT: alt should be the configured saving message
|
||||||
|
savingMessage = $('<img />').attr('src', this.settings.saving_image).attr('alt', savingMessage);
|
||||||
|
this.dom.html(savingMessage);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSubmitToCallback: function(enteredText) {
|
||||||
|
// REFACT: consider to encode enteredText and originalHTML before giving it to the callback
|
||||||
|
this.enableOrDisableAnimationCallbacks(true, false);
|
||||||
|
var newHTML = this.triggerCallback(this.settings.callback, /* DEPRECATED in 2.1.0 */ this.id(), enteredText, this.originalValue,
|
||||||
|
this.settings.params, this.savingAnimationCallbacks());
|
||||||
|
|
||||||
|
if (this.settings.callback_skip_dom_reset)
|
||||||
|
; // do nothing
|
||||||
|
else if (undefined === newHTML) {
|
||||||
|
// failure; put original back
|
||||||
|
this.reportError("Error: Failed to save value: " + enteredText);
|
||||||
|
this.restoreOriginalValue();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// REFACT: use setClosedEditorContent
|
||||||
|
this.dom.html(newHTML);
|
||||||
|
|
||||||
|
if (this.didCallNoCallbacks()) {
|
||||||
|
this.enableOrDisableAnimationCallbacks(false, false);
|
||||||
|
this.reinit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSubmitToServer: function(enteredText) {
|
||||||
|
var data = this.settings.update_value + '=' + encodeURIComponent(enteredText)
|
||||||
|
+ '&' + this.settings.element_id + '=' + this.dom.attr("id")
|
||||||
|
+ ((this.settings.params) ? '&' + this.settings.params : '')
|
||||||
|
+ '&' + this.settings.original_html + '=' + encodeURIComponent(this.originalValue) /* DEPRECATED in 2.2.0 */
|
||||||
|
+ '&' + this.settings.original_value + '=' + encodeURIComponent(this.originalValue);
|
||||||
|
|
||||||
|
this.enableOrDisableAnimationCallbacks(true, false);
|
||||||
|
this.didStartSaving();
|
||||||
|
var that = this;
|
||||||
|
$.ajax({
|
||||||
|
url: that.settings.url,
|
||||||
|
type: "POST",
|
||||||
|
data: data,
|
||||||
|
dataType: "html",
|
||||||
|
complete: function(request){
|
||||||
|
that.didEndSaving();
|
||||||
|
},
|
||||||
|
success: function(html){
|
||||||
|
var new_text = html || that.settings.default_text;
|
||||||
|
|
||||||
|
/* put the newly updated info into the original element */
|
||||||
|
// FIXME: should be affected by the preferences switch
|
||||||
|
that.dom.html(new_text);
|
||||||
|
// REFACT: remove dom parameter, already in this, not documented, should be easy to remove
|
||||||
|
// REFACT: callback should be able to override what gets put into the DOM
|
||||||
|
that.triggerCallback(that.settings.success, html);
|
||||||
|
},
|
||||||
|
error: function(request) {
|
||||||
|
that.dom.html(that.originalHTML); // REFACT: what about a restorePreEditingContent()
|
||||||
|
if (that.settings.error)
|
||||||
|
// REFACT: remove dom parameter, already in this, not documented, can remove without deprecation
|
||||||
|
// REFACT: callback should be able to override what gets entered into the DOM
|
||||||
|
that.triggerCallback(that.settings.error, request);
|
||||||
|
else
|
||||||
|
that.reportError("Failed to save value: " + request.responseText || 'Unspecified Error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Utilities .........................................................
|
||||||
|
|
||||||
|
triggerCallback: function(aCallback /*, arguments */) {
|
||||||
|
if ( ! aCallback)
|
||||||
|
return; // callback wasn't specified after all
|
||||||
|
|
||||||
|
var callbackArguments = Array.prototype.slice.call(arguments, 1);
|
||||||
|
return aCallback.apply(this.dom[0], callbackArguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
/// defaultReturnValue is only used if the delegate returns undefined
|
||||||
|
triggerDelegateCall: function(aDelegateMethodName, defaultReturnValue, optionalEvent) {
|
||||||
|
// REFACT: consider to trigger equivalent callbacks automatically via a mapping table?
|
||||||
|
if ( ! this.settings.delegate
|
||||||
|
|| ! $.isFunction(this.settings.delegate[aDelegateMethodName]))
|
||||||
|
return defaultReturnValue;
|
||||||
|
|
||||||
|
var delegateReturnValue = this.settings.delegate[aDelegateMethodName](this.dom, this.settings, optionalEvent);
|
||||||
|
return (undefined === delegateReturnValue)
|
||||||
|
? defaultReturnValue
|
||||||
|
: delegateReturnValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
reportError: function(anErrorString) {
|
||||||
|
this.triggerCallback(this.settings.error_sink, /* DEPRECATED in 2.1.0 */ this.id(), anErrorString);
|
||||||
|
},
|
||||||
|
|
||||||
|
// REFACT: this method should go, callbacks should get the dom node itself as an argument
|
||||||
|
id: function() {
|
||||||
|
return this.dom.attr('id');
|
||||||
|
},
|
||||||
|
|
||||||
|
markEditorAsActive: function() {
|
||||||
|
this.dom.addClass('editInPlace-active');
|
||||||
|
},
|
||||||
|
|
||||||
|
markEditorAsInactive: function() {
|
||||||
|
this.dom.removeClass('editInPlace-active');
|
||||||
|
},
|
||||||
|
|
||||||
|
// REFACT: consider rename, doesn't deal with animation directly
|
||||||
|
savingAnimationCallbacks: function() {
|
||||||
|
var that = this;
|
||||||
|
return {
|
||||||
|
didStartSaving: function() { that.didStartSaving(); },
|
||||||
|
didEndSaving: function() { that.didEndSaving(); }
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
enableOrDisableAnimationCallbacks: function(shouldEnableStart, shouldEnableEnd) {
|
||||||
|
this.didStartSaving.enabled = shouldEnableStart;
|
||||||
|
this.didEndSaving.enabled = shouldEnableEnd;
|
||||||
|
},
|
||||||
|
|
||||||
|
didCallNoCallbacks: function() {
|
||||||
|
return this.didStartSaving.enabled && ! this.didEndSaving.enabled;
|
||||||
|
},
|
||||||
|
|
||||||
|
assertCanCall: function(methodName) {
|
||||||
|
if ( ! this[methodName].enabled)
|
||||||
|
throw new Error('Cannot call ' + methodName + ' now. See documentation for details.');
|
||||||
|
},
|
||||||
|
|
||||||
|
didStartSaving: function() {
|
||||||
|
this.assertCanCall('didStartSaving');
|
||||||
|
this.shouldDelayReinit = true;
|
||||||
|
this.enableOrDisableAnimationCallbacks(false, true);
|
||||||
|
|
||||||
|
this.startSavingAnimation();
|
||||||
|
},
|
||||||
|
|
||||||
|
didEndSaving: function() {
|
||||||
|
this.assertCanCall('didEndSaving');
|
||||||
|
this.shouldDelayReinit = false;
|
||||||
|
this.enableOrDisableAnimationCallbacks(false, false);
|
||||||
|
this.reinit();
|
||||||
|
|
||||||
|
this.stopSavingAnimation();
|
||||||
|
},
|
||||||
|
|
||||||
|
startSavingAnimation: function() {
|
||||||
|
var that = this;
|
||||||
|
this.dom
|
||||||
|
.animate({ backgroundColor: this.settings.saving_animation_color }, 400)
|
||||||
|
.animate({ backgroundColor: 'transparent'}, 400, 'swing', function(){
|
||||||
|
// In the tests animations are turned off - i.e they happen instantaneously.
|
||||||
|
// Hence we need to prevent this from becomming an unbounded recursion.
|
||||||
|
setTimeout(function(){ that.startSavingAnimation(); }, 10);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stopSavingAnimation: function() {
|
||||||
|
this.dom
|
||||||
|
.stop(true)
|
||||||
|
.css({backgroundColor: ''});
|
||||||
|
},
|
||||||
|
|
||||||
|
missingCommaErrorPreventer:''
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Private helpers .......................................................
|
||||||
|
|
||||||
|
function assertMandatorySettingsArePresent(options) {
|
||||||
|
// one of these needs to be non falsy
|
||||||
|
if (options.url || options.callback)
|
||||||
|
return;
|
||||||
|
|
||||||
|
throw new Error("Need to set either url: or callback: option for the inline editor to work.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* preload the loading icon if it is configured */
|
||||||
|
function preloadImage(anImageURL) {
|
||||||
|
if ('' === anImageURL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var loading_image = new Image();
|
||||||
|
loading_image.src = anImageURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function trim(aString) {
|
||||||
|
return aString
|
||||||
|
.replace(/^\s+/, '')
|
||||||
|
.replace(/\s+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasContent(something) {
|
||||||
|
if (undefined === something || null === something)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (0 === something.length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
@@ -97,27 +97,6 @@ var CookieList = function(cookieName) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the changes done by in-place edit.
|
|
||||||
*
|
|
||||||
* @param obj
|
|
||||||
* @param cancel
|
|
||||||
*/
|
|
||||||
function saveChanges(obj, cancel) {
|
|
||||||
var a;
|
|
||||||
|
|
||||||
if (!cancel) {
|
|
||||||
a = $(obj).parent().siblings(0).val();
|
|
||||||
$.post(SITE_URL + 'ajax/save_project', { content: a }, function(response) {
|
|
||||||
alert("Die Änderungen wurden gespeichert.");
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
a = cancel;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(obj).parent().parent().after('<div class="editInPlace">' + a + '</div>').remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alternates the table rows.
|
* Alternates the table rows.
|
||||||
*/
|
*/
|
||||||
@@ -127,21 +106,6 @@ $.fn.alternateRowColors = function() {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces html line breaks with newlines.
|
|
||||||
*/
|
|
||||||
$.fn.br2nl = function() {
|
|
||||||
return $(this).html().replace(/(<br>)|(<br \/>)|(<p>)|(<\/p>)/g, "");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar to PHP's nl2br function.
|
|
||||||
*/
|
|
||||||
$.fn.nl2br = function() {
|
|
||||||
var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
|
|
||||||
return $(this).html().replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do some stuff if document is ready.
|
* Do some stuff if document is ready.
|
||||||
*/
|
*/
|
||||||
@@ -153,7 +117,7 @@ $(document).ready(function() {
|
|||||||
$('ul.tabs li:first').addClass('active').show(); // activate first tab
|
$('ul.tabs li:first').addClass('active').show(); // activate first tab
|
||||||
$('.tab_content:first').show(); // show first tab content
|
$('.tab_content:first').show(); // show first tab content
|
||||||
|
|
||||||
// onClick event
|
// on-click event
|
||||||
$('ul.tabs li').click(function() {
|
$('ul.tabs li').click(function() {
|
||||||
$('ul.tabs li').removeClass('active'); // remove any 'active' class
|
$('ul.tabs li').removeClass('active'); // remove any 'active' class
|
||||||
$(this).addClass('active'); // add 'active' class to selected tab
|
$(this).addClass('active'); // add 'active' class to selected tab
|
||||||
@@ -251,28 +215,6 @@ $(document).ready(function() {
|
|||||||
$(this).find('td.drag_handle').removeClass('drag_handle-show');
|
$(this).find('td.drag_handle').removeClass('drag_handle-show');
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
|
||||||
* In-place editor
|
|
||||||
*/
|
|
||||||
$('.editInPlace').click(function() {
|
|
||||||
var textarea = '<div><textarea rows="6" cols="60">' + $(this).br2nl() + '</textarea><p></p>';
|
|
||||||
var button = '<p><a class="button save" href="javascript:void(0);">Speichern</a> <a class="button cancel" href="javascript:void(0);">Abbrechen</a></div>';
|
|
||||||
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
|
* Active project selection
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user