1 // ========================================================================== 2 // Project: SproutCore - JavaScript Application Framework 3 // Copyright: ©2006-2010 Sprout Systems, 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 for a view that is editable but is acting as a proxy to 12 edit another view. For example, a calendar picker that pops up over 13 top of a date display. 14 15 Instantiation and destruction will be handled by the InlineEditorDelegate 16 defined on the InlineEditable view. 17 18 Any runtime configuration should be handled by the delegate's 19 inlineEditorWillBeginEditing method. This will be done at the start of the 20 beginEditing function, so your view should be able to handle having value 21 changed by this method. 22 23 Your view should also be sure to cleanup completely when editing is finished, 24 because the delegate may decide to reuse the same editor elsewhere. 25 26 See SC.InlineTextFieldView for an example of an implementation of 27 SC.InlineEditor. 28 */ 29 SC.InlineEditor = { 30 31 /** 32 Walk like a duck. 33 34 @type Boolean 35 @default YES 36 @readOnly 37 */ 38 isInlineEditor: YES, 39 40 /** 41 Indicates the view is currently editing. For a typical editor, this 42 will always be true as long as the view exists, but some 43 InlineEditorDelegates may have more complex lifecycles in which 44 editors are reused. 45 46 @type Boolean 47 @default NO 48 */ 49 isEditing: NO, 50 51 /** 52 The delegate responsible for the editors lifecycle as well as the 53 target for notifications. This property should be set when the 54 delegate creates the editor and does not need to be in the view 55 definition. 56 57 @type SC.InlineEditorDelegate 58 @default null 59 */ 60 inlineEditorDelegate: null, 61 62 /** 63 @private 64 The view that this view is responsible for editing. 65 @type SC.InlineEditable 66 */ 67 _target: null, 68 69 /** 70 Tells the editor to begin editing another view with the given starting value. 71 Editors may be reused so make sure that the editor is fully cleaned 72 up and reinitialized. 73 74 Sets isEditing to YES. 75 76 Will fail if the editor is already editing. 77 78 If you override this method, be sure to call sc_super() at the beginning of 79 you function so that the delegate will be able to configure the view when it 80 is notified of the inlineEditorWillBeginEditing event. 81 82 @param {SC.View} editable the view being edited 83 @returns {Boolean} whether the editor was able to successfully begin editing 84 */ 85 beginEditing:function(editable) { 86 if(this.get('isEditing') || !editable || !editable.isInlineEditable) return NO; 87 88 var del, target; 89 90 target = this._target = editable; 91 92 del = this.delegateFor('inlineEditorWillBeginEditing', this.inlineEditorDelegate, target); 93 if(del) del.inlineEditorWillBeginEditing(this, this.get('value'), target); 94 95 this.set('isEditing', YES); 96 97 // needs to be invoked last because it needs to run after the view becomes 98 // first responder 99 this.invokeLast(this._callDidBegin); 100 101 // remember that we invoked in case commit gets called before the invoke 102 // goes off 103 this._didBeginInvoked = YES; 104 105 return YES; 106 }, 107 108 /** 109 @private 110 Notifies the delegate of the didBeginEditing event. Needs to be invoked last 111 because becoming first responder doesn't happen until the end of the runLoop 112 and didBegin is supposed to occur after the editor becomes first responder. 113 */ 114 _callDidBegin: function() { 115 // don't notify if we already ended editing 116 if(!this.get('isEditing')) return NO; 117 118 this._didBeginInvoked = NO; 119 120 var target = this._target, del; 121 122 del = this.delegateFor('inlineEditorDidBeginEditing', this.inlineEditorDelegate, target); 123 if(del) del.inlineEditorDidBeginEditing(this, this.get('value'), target); 124 }, 125 126 /** 127 Tells the editor to save its value back to its target view and end 128 editing. Since the editor is a private property of the view it is 129 editing for, this function should only be called from the editor 130 itself. For example, you may want your editor to handle the enter 131 key by calling commitEditing on itself. 132 133 Will fail if the editor is not editing or if the delegate returns NO to 134 inlineEditorShouldCommitEditing. 135 136 @returns {Boolean} whether the editor was allowed to successfully commit its value 137 */ 138 commitEditing:function() { 139 if(!this.get('isEditing')) return NO; 140 141 // if the handler was invoked but never went off, call it now 142 if(this._didBeginInvoked) this._callDidBegin(); 143 144 var del, target = this._target; 145 146 del = this.delegateFor('inlineEditorShouldCommitEditing', this.inlineEditorDelegate, target); 147 if(del && !del.inlineEditorShouldCommitEditing(this, this.get('value'), target)) return NO; 148 149 del = this.delegateFor('inlineEditorWillCommitEditing', this.inlineEditorDelegate, target); 150 if(del) del.inlineEditorWillCommitEditing(this, this.get('value'), target); 151 152 this._endEditing(); 153 154 del = this.delegateFor('inlineEditorDidCommitEditing', this.inlineEditorDelegate, target); 155 if(del) del.inlineEditorDidCommitEditing(this, this.get('value'), target); 156 157 return YES; 158 }, 159 160 /** 161 Tells the editor to discard its value and end editing. Like 162 commitEditing, this should only be called by other methods of the 163 editor. For example, the handle for the escape key might call 164 discardEditing. 165 166 Will fail if the editor is not editing or if the delegate returns NO to 167 inlineEditorShouldDiscardEditing. 168 169 @returns {Boolean} whether the editor was allowed to discard its value 170 */ 171 discardEditing:function() { 172 if(!this.get('isEditing')) return NO; 173 174 // if the handler was invoked but never went off, call it now 175 if(this._didBeginInvoked) this._callDidBegin(); 176 177 var del, target = this._target; 178 179 del = this.delegateFor('inlineEditorShouldDiscardEditing', this.inlineEditorDelegate, target); 180 if(del && !del.inlineEditorShouldDiscardEditing(this, target)) return NO; 181 182 del = this.delegateFor('inlineEditorWillDiscardEditing', this.inlineEditorDelegate, target); 183 if(del) del.inlineEditorWillDiscardEditing(this, target); 184 185 this._endEditing(); 186 187 del = this.delegateFor('inlineEditorDidDiscardEditing', this.inlineEditorDelegate, target); 188 if(del) del.inlineEditorDidDiscardEditing(this, target); 189 190 return YES; 191 }, 192 193 /** 194 @private 195 Performs the cleanup functionality shared between discardEditing and 196 commitEditing. 197 */ 198 _endEditing: function() { 199 this.set('isEditing', NO); 200 this._target = null; 201 } 202 203 }; 204