// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2011 Strobe Inc. and contributors.
// ©2008-2011 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
sc_require('views/template');
/**
@class
*/
SC.TextFieldSupport = /** @scope SC.TextFieldSupport */{
$input: function() {
return this.$('input');
},
/** @private
Used internally to store value because the layer may not exist
*/
_value: null,
/**
The problem this property is trying to solve is twofold:
1. Make it possible to set the value of a text field that has
not yet been inserted into the DOM
2. Make sure that `value` properly reflects changes made directly
to the element's `value` property.
In order to achieve (2), we need to make the property volatile,
so that SproutCore will call the getter no matter what if get()
is called.
In order to achieve (1), we need to store a local cache of the
value, so that SproutCore can set the proper value as soon as
the underlying DOM element is created.
@type String
@default null
*/
value: function(key, value) {
var input = this.$input();
if (value !== undefined) {
// We don't want to unnecessarily set the value.
// Doing that could cause the selection to be lost.
if (this._value !== value) { this._value = value; }
if (input.val() !== value) { input.val(value); }
} else {
if (input.length > 0) {
value = this._value = input.val();
} else {
value = this._value;
}
}
return value;
}.property().idempotent(),
didCreateLayer: function() {
var input = this.$input(),
self = this;
input.val(this._value);
if (SC.browser.msie) {
SC.Event.add(input, 'focusin', this, this.focusIn);
SC.Event.add(input, 'focusout', this, this.focusOut);
} else {
SC.Event.add(input, 'focus', this, this.focusIn);
SC.Event.add(input, 'blur', this, this.focusOut);
}
},
willDestroyLayerMixin: function() {
var input = this.$input();
if (SC.browser.msie) {
SC.Event.remove(input, 'focusin', this, this.focusIn);
SC.Event.remove(input, 'focusout', this, this.focusOut);
} else {
SC.Event.remove(input, 'focus', this, this.focusIn);
SC.Event.remove(input, 'blur', this, this.focusOut);
}
},
focusIn: function(event) {
this.becomeFirstResponder();
this.tryToPerform('focus', event);
},
focusOut: function(event) {
this.resignFirstResponder();
this.tryToPerform('blur', event);
},
touchStart: function(evt) {
evt.allowDefault();
return YES;
},
touchEnd: function(evt) {
evt.allowDefault();
return YES;
},
/** @private
Make sure our input value is synced with any bindings.
In some cases, such as auto-filling, a value can get
changed without an event firing. We could do this
on focusOut, but blur can potentially get called
after other events.
*/
willLoseFirstResponder: function() {
this.notifyPropertyChange('value');
},
domValueDidChange: function(jquery) {
this.set('value', jquery.val());
},
keyUp: function(event) {
this.domValueDidChange(this.$input());
if (event.keyCode === SC.Event.KEY_RETURN) {
return this.tryToPerform('insertNewline', event);
} else if (event.keyCode === SC.Event.KEY_ESC) {
return this.tryToPerform('cancel', event);
}
}
};
/**
@class
@extends SC.TemplateView
@extends SC.TextFieldSupport
*/
SC.TextField = SC.TemplateView.extend(SC.TextFieldSupport,
/** @scope SC.TextField.prototype */ {
classNames: ['sc-text-field'],
/**
If set to `YES` uses textarea tag instead of input to
accommodate multi-line strings.
@type Boolean
@default NO
*/
isMultiline: NO,
// we can't use bindAttr because of a race condition:
//
// when `value` is set, the bindAttr observer immediately calls
// `get` in order to persist it to the DOM, but because we made
// the `value` property idempotent, when it gets called by
// bindAttr, it fetches the not-yet-updated value from the DOM
// and returns it.
//
// In short, because we need to be able to catch changes to the
// DOM made directly, we cannot also rely on bindAttr to update
// the property: a chicken-and-egg problem.
template: function(){
return SC.Handlebars.compile(this.get('isMultiline') ? '' : '');
}.property('isMultiline').cacheable(),
$input: function() {
var tagName = this.get('isMultiline') ? 'textarea' : 'input';
return this.$(tagName);
}
});