1 // ========================================================================== 2 // Project: SproutCore - JavaScript Application Framework 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 8 /** 9 @namespace 10 11 This mixin is used for views that show a seperate editor view to edit. 12 For example, the default behavior of SC.LabelView if isEditable is set 13 to YES is to popup an SC.InlineTextFieldView when double clicked. This is a 14 seperate text input that will handle the editing and save its value back to 15 the label when it is finished. 16 17 To use this functionality, all you have to do is apply this mixin to 18 your view. You may define your own SC.InlineEditorDelegate to further 19 customize editing behavior. 20 21 MyProject.MyView = SC.View.extend(SC.InlineEditable, { 22 inlineEditorDelegate: myDelegate 23 }); 24 25 The delegate methods will default to your view unless the 26 inlineEditorDelegate implements them. Simple views do not require a 27 seperate delegate. If your view has a more complicated editing 28 interaction, you may also implement a custom delegate. For example, if 29 you have a form with several views that all edit together, you might 30 set the parent view as the delegate so it can manage the lifecycle and 31 layout of the editors. 32 33 See SC.InlineEditorDelegate for more information on using a delegate to 34 customize your view's edit behavior. 35 36 Your view can now be edited by calling beginEditing() on it. 37 38 myView.beginEditing(); 39 40 This will create an editor for the view. You can then end the editing process 41 by calling commitEditing() or discardEditing() on either the view or the 42 editor. commitEditing() will save the value and discard will revert to the 43 original value. 44 45 myView.commitEditing(); 46 myView.discardEditing(); 47 48 Note that the editor is a private property of the view, so the only views that 49 should be able to access the methods on it are the editor itself, the view it 50 is editing, and their delegates. 51 */ 52 SC.InlineEditable = { 53 54 /** 55 Walk like a duck. 56 57 @type Boolean 58 @default YES 59 */ 60 isInlineEditable: YES, 61 62 /** 63 Flag to enable or disable editing. 64 65 @type Boolean 66 @default YES 67 */ 68 isEditable: YES, 69 70 /** 71 The view that will be used to edit this view. Defaults to 72 SC.InlineTextFieldView, which is simply a text field that positions itself 73 over the view being edited. 74 75 @type SC.InlineEditor 76 @default SC.InlineTextFieldView 77 */ 78 exampleEditor: SC.InlineTextFieldView, 79 80 /** 81 Indicates whether the view is currently editing. Attempting to 82 beginEditing a view that is already editing will fail. 83 84 @type Boolean 85 @default NO 86 */ 87 isEditing: NO, 88 89 /** 90 Delegate that will be notified of events related to the editing 91 process. Also responsible for managing the lifecycle of the editor. 92 93 @type SC.InlineEditorDelegate 94 @default SC.InlineTextFieldDelegate 95 */ 96 inlineEditorDelegate: SC.InlineTextFieldDelegate, 97 98 /** 99 @private 100 The editor responsible for editing this view. 101 */ 102 _editor: null, 103 104 /** 105 Tells the view to start editing. This will create an editor for it 106 and transfer control to the editor. 107 108 Will fail if the delegate returns NO to inlineEditorShouldBeginEditing. 109 110 @returns {Boolean} whether the view successfully entered edit mode 111 */ 112 beginEditing: function() { 113 var del; 114 115 del = this.delegateFor('inlineEditorShouldBeginEditing', this.inlineEditorDelegate); 116 if(del && !del.inlineEditorShouldBeginEditing(this, this.get('value'))) return NO; 117 118 this._editor = this.invokeDelegateMethod(this.inlineEditorDelegate, 'acquireEditor', this); 119 120 if(this._editor) return this._editor.beginEditing(this); 121 else return NO; 122 }, 123 124 /** 125 Tells the view to save the value currently in the editor and finish 126 editing. The delegate will be consulted first by calling 127 inlineEditorShouldCommitEditing, and the operation will not be 128 allowed if the delegate returns NO. 129 130 Will fail if the delegate returns NO to inlineEditorShouldCommitEditing. 131 132 @returns {Boolean} whether the delegate allowed the value to be committed 133 */ 134 commitEditing: function() { 135 return this._editor ? this._editor.commitEditing() : NO; 136 }, 137 138 /** 139 Tells the view to leave edit mode and revert to the value it had 140 before editing. May fail if the delegate returns NO to 141 inlineEditorShouldDiscardEditing. It is possible for the delegate to 142 return false to inlineEditorShouldDiscardEditing but true to 143 inlineEditorShouldCommitEditing, so a client view may attempt to 144 call commitEditing in case discardEditing fails. 145 146 Will fail if the delegate returns NO to inlineEditorShouldDiscardEditing. 147 148 @returns {Boolean} whether the delegate allowed the view to discard its value 149 */ 150 discardEditing: function() { 151 return this._editor ? this._editor.discardEditing() : NO; 152 }, 153 154 /** 155 Allows the view to begin editing if it is editable and it is not 156 already editing. 157 158 @returns {Boolean} if the view is allowed to begin editing 159 */ 160 inlineEditorShouldBeginEditing: function() { 161 return !this.get('isEditing') && this.get('isEditable'); 162 }, 163 164 // TODO: implement validator 165 /** 166 By default, the editor starts with the value of the view being edited. 167 168 @params {SC.InlineEditable} editor the view being edited 169 @params {SC.InlineEditor} value the editor for the view 170 @params {Object} editable the initial value of the editor 171 */ 172 inlineEditorWillBeginEditing: function(editor, value, editable) { 173 editor.set('value', this.get('value')); 174 }, 175 176 /** 177 Sets isEditing to YES once editing has begun. 178 179 @params {SC.InlineEditable} the view being edited 180 @params {SC.InlineEditor} the editor for the view 181 @params {Object} the initial value of the editor 182 */ 183 inlineEditorDidBeginEditing: function(editor, value, editable) { 184 this.set('isEditing', YES); 185 }, 186 187 /** @private 188 Calls inlineEditorWillEndEditing for backwards compatibility. 189 190 @params {SC.InlineEditable} the view being edited 191 @params {SC.InlineEditor} the editor for the view 192 @params {Object} the initial value of the editor 193 */ 194 inlineEditorWillCommitEditing: function(editor, value, editable) { 195 if(this.inlineEditorWillEndEditing) this.inlineEditorWillEndEditing(editor, value); 196 }, 197 198 /** 199 By default, commiting editing simply sets the value that the editor 200 returned and cleans up. 201 202 @params {SC.InlineEditable} the view being edited 203 @params {SC.InlineEditor} the editor for the view 204 @params {Object} the initial value of the editor 205 */ 206 inlineEditorDidCommitEditing: function(editor, value, editable) { 207 editable.setIfChanged('value', value); 208 209 if(this.inlineEditorDidEndEditing) this.inlineEditorDidEndEditing(editor, value); 210 211 this._endEditing(); 212 }, 213 214 /** 215 Calls inlineEditorWillEndEditing for backwards compatibility. 216 217 @params {SC.InlineEditable} the view being edited 218 @params {SC.InlineEditor} the editor for the view 219 @params {Object} the initial value of the editor 220 */ 221 inlineEditorWillDiscardEditing: function(editor, editable) { 222 if(this.inlineEditorWillEndEditing) this.inlineEditorWillEndEditing(editor, this.get('value')); 223 }, 224 225 /** 226 Calls inlineEditorDidEndEditing for backwards compatibility and then 227 cleans up. 228 229 @params {SC.InlineEditable} the view being edited 230 @params {SC.InlineEditor} the editor for the view 231 @params {Object} the initial value of the editor 232 */ 233 inlineEditorDidDiscardEditing: function(editor, editable) { 234 if(this.inlineEditorDidEndEditing) this.inlineEditorDidEndEditing(editor, this.get('value')); 235 236 this._endEditing(); 237 }, 238 239 /** 240 @private 241 Shared code used to cleanup editing after both discarding and commiting. 242 */ 243 _endEditing: function() { 244 // _editor may be null if we were called using the 245 // SC.InlineTextFieldView class methods 246 if(this._editor) { 247 this.invokeDelegateMethod(this.inlineEditorDelegate, 'releaseEditor', this._editor); 248 this._editor = null; 249 } 250 251 this.set('isEditing', NO); 252 } 253 }; 254 255