// ========================================================================== // Project: SproutCore - JavaScript Application Framework // Copyright: ©2006-2011 Strobe Inc. and contributors. // Portions ©2008-2011 Apple Inc. All rights reserved. // License: Licensed under MIT license (see license.js) // ========================================================================== /* * @mixin * * This mixin is used for views that show a seperate editor view to edit. * For example, the default behavior of SC.LabelView if isEditable is set * to YES is to popup an SC.InlineTextFieldView when double clicked. This is a * seperate text input that will handle the editing and save its value back to * the label when it is finished. * * To use this functionality, all you have to do is apply this mixin to * your view. You may define your own SC.InlineEditorDelegate to further * customize editing behavior. * * {{{ * MyProject.MyView = SC.View.extend(SC.InlineEditable, { * inlineEditorDelegate: myDelegate * }); * }}} * * The delegate methods will default to your view unless the * inlineEditorDelegate implements them. Simple views do not require a * seperate delegate. If your view has a more complicated editing * interaction, you may also implement a custom delegate. For example, if * you have a form with several views that all edit together, you might * set the parent view as the delegate so it can manage the lifecycle and * layout of the editors. * * See SC.InlineEditorDelegate for more information on using a delegate to * customize your view's edit behavior. * * Your view can now be edited by calling beginEditing() on it. * * {{{ * myView.beginEditing(); * }}} * * This will create an editor for the view. You can then end the editing process * by calling commitEditing() or discardEditing() on either the view or the * editor. commitEditing() will save the value and discard will revert to the * original value. * * {{{ * myView.commitEditing(); * myView.discardEditing(); * }}} * * Note that the editor is a private property of the view, so the only views that * should be able to access the methods on it are the editor itself, the view it * is editing, and their delegates. */ SC.InlineEditable = { /* * Walk like a duck. * * @type {Boolean} */ isInlineEditable: YES, /* * Flag to enable or disable editing. * * @type {Boolean} */ isEditable: YES, /* * The view that will be used to edit this view. Defaults to * SC.InlineTextFieldView, which is simply a text field that positions itself * over the view being edited. * * @type {SC.InlineEditor} */ exampleEditor: SC.InlineTextFieldView, /* * Indicates whether the view is currently editing. Attempting to * beginEditing a view that is already editing will fail. * * @type {Boolean} */ isEditing: NO, /* * Delegate that will be notified of events related to the editing * process. Also responsible for managing the lifecycle of the editor. * * @type {SC.InlineEditorDelegate} */ inlineEditorDelegate: SC.InlineTextFieldDelegate, /* * @private * * The editor responsible for editing this view. * * @type {SC.InlineEditor} */ _editor: null, /* * @method * * Tells the view to start editing. This will create an editor for it * and transfer control to the editor. * * Will fail if the delegate returns NO to inlineEditorShouldBeginEditing. * * @returns {Boolean} whether the view succesfully entered edit mode */ beginEditing: function() { var del; del = this.delegateFor('inlineEditorShouldBeginEditing', this.inlineEditorDelegate); if(del && !del.inlineEditorShouldBeginEditing(this, this.get('value'))) return NO; this._editor = this.invokeDelegateMethod(this.inlineEditorDelegate, 'acquireEditor', this); if(this._editor) return this._editor.beginEditing(this); else return NO; }, /* * @method * * Tells the view to save the value currently in the editor and finish * editing. The delegate will be consulted first by calling * inlineEditorShouldCommitEditing, and the operation will not be * allowed if the delegate returns NO. * * Will fail if the delegate returns NO to inlineEditorShouldCommitEditing. * * @returns {Boolean} whether the delegate allowed the value to be committed */ commitEditing: function() { return this._editor ? this._editor.commitEditing() : NO; }, /* * @method * * Tells the view to leave edit mode and revert to the value it had * before editing. May fail if the delegate returns NO to * inlineEditorShouldDiscardEditing. It is possible for the delegate to * return false to inlineEditorShouldDiscardEditing but true to * inlineEditorShouldCommitEditing, so a client view may attempt to * call commitEditing in case discardEditing fails. * * Will fail if the delegate returns NO to inlineEditorShouldDiscardEditing. * * @returns {Boolean} whether the delegate allowed the view to discard its value */ discardEditing: function() { return this._editor ? this._editor.discardEditing() : NO; }, /* * @method * * Allows the view to begin editing if it is editable and it is not * already editing. * * @returns {Boolean} if the view is allowed to begin editing */ inlineEditorShouldBeginEditing: function() { return !this.isEditing && this.isEditable; }, // TODO: implement validator /* * @method * * By default, the editor starts with the value of the view being edited. * * @params {SC.InlineEditable} the view being edited * @params {SC.InlineEditor} the editor for the view * @params {Object} the initial value of the editor */ inlineEditorWillBeginEditing: function(editor, value, editable) { editor.set('value', this.get('value')); }, /* * @method * * Sets isEditing to YES once editing has begun. * * @params {SC.InlineEditable} the view being edited * @params {SC.InlineEditor} the editor for the view * @params {Object} the initial value of the editor */ inlineEditorDidBeginEditing: function(editor, value, editable) { this.set('isEditing', YES); }, /* * @method * * Calls inlineEditorWillEndEditing for backwards compatibility. * * @params {SC.InlineEditable} the view being edited * @params {SC.InlineEditor} the editor for the view * @params {Object} the initial value of the editor */ inlineEditorWillCommmitEditing: function(editor, value, editable) { if(this.inlineEditorWillEndEditing) this.inlineEditorWillEndEditing(editor, value); }, /* * @method * * By default, commiting editing simply sets the value that the editor * returned and cleans up. * * @params {SC.InlineEditable} the view being edited * @params {SC.InlineEditor} the editor for the view * @params {Object} the initial value of the editor */ inlineEditorDidCommitEditing: function(editor, value, editable) { editable.setIfChanged('value', value); if(this.inlineEditorDidEndEditing) this.inlineEditorDidEndEditing(editor, value); this._endEditing(); }, /* * @method * * Calls inlineEditorWillEndEditing for backwards compatibility. * * @params {SC.InlineEditable} the view being edited * @params {SC.InlineEditor} the editor for the view * @params {Object} the initial value of the editor */ inlineEditorWillDiscardEditing: function(editor, editable) { if(this.inlineEditorWillEndEditing) this.inlineEditorWillEndEditing(editor, this.get('value')); }, /* * @method * * Calls inlineEditorDidEndEditing for backwards compatibility and then * cleans up. * * @params {SC.InlineEditable} the view being edited * @params {SC.InlineEditor} the editor for the view * @params {Object} the initial value of the editor */ inlineEditorDidDiscardEditing: function(editor, editable) { if(this.inlineEditorDidEndEditing) this.inlineEditorDidEndEditing(editor, this.get('value')); this._endEditing(); }, /* * @method * @private * * Shared code used to cleanup editing after both discarding and commiting. */ _endEditing: function() { // _editor may be null if we were called using the // SC.InlineTextFieldView class methods if(this._editor) { this.invokeDelegateMethod(this.inlineEditorDelegate, 'releaseEditor', this._editor); this._editor = null; } this.set('isEditing', NO); } };