sproutit-sproutcore 1.0.20090721145281 → 1.0.20090721145282

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. data/Buildfile +4 -3
  2. data/VERSION.yml +2 -2
  3. data/buildtasks/entry.rake +3 -0
  4. data/buildtasks/manifest.rake +35 -9
  5. data/buildtasks/target.rake +25 -6
  6. data/frameworks/sproutcore/Buildfile +10 -0
  7. data/frameworks/sproutcore/frameworks/datastore/data_sources/data_source.js +41 -20
  8. data/frameworks/sproutcore/frameworks/datastore/data_sources/fixtures.js +14 -43
  9. data/frameworks/sproutcore/frameworks/datastore/models/record.js +11 -0
  10. data/frameworks/sproutcore/frameworks/datastore/models/record_attribute.js +6 -3
  11. data/frameworks/sproutcore/frameworks/datastore/system/nested_store.js +5 -1
  12. data/frameworks/sproutcore/frameworks/datastore/system/query.js +10 -7
  13. data/frameworks/sproutcore/frameworks/datastore/system/record_array.js +19 -20
  14. data/frameworks/sproutcore/frameworks/datastore/system/store.js +126 -93
  15. data/frameworks/sproutcore/frameworks/datastore/tests/data_sources/fixtures.js +9 -3
  16. data/frameworks/sproutcore/frameworks/datastore/tests/models/many_attribute.js +6 -1
  17. data/frameworks/sproutcore/frameworks/datastore/tests/models/record/core_methods.js +28 -3
  18. data/frameworks/sproutcore/frameworks/datastore/tests/models/record/destroy.js +13 -5
  19. data/frameworks/sproutcore/frameworks/datastore/tests/models/record/storeDidChangeProperties.js +46 -23
  20. data/frameworks/sproutcore/frameworks/datastore/tests/models/record/writeAttribute.js +29 -5
  21. data/frameworks/sproutcore/frameworks/datastore/tests/models/record_attribute.js +13 -4
  22. data/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/chain.js +109 -0
  23. data/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/commitChanges.js +69 -15
  24. data/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/commitChangesFromNestedStore.js +20 -1
  25. data/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/dataHashDidChange.js +4 -1
  26. data/frameworks/sproutcore/frameworks/datastore/tests/system/query/find_all.js +56 -6
  27. data/frameworks/sproutcore/frameworks/datastore/tests/system/store/commitRecord.js +9 -2
  28. data/frameworks/sproutcore/frameworks/datastore/tests/system/store/core_methods.js +45 -2
  29. data/frameworks/sproutcore/frameworks/datastore/tests/system/store/recordDidChange.js +0 -1
  30. data/frameworks/sproutcore/frameworks/datastore/tests/system/store/retrieveRecord.js +53 -6
  31. data/frameworks/sproutcore/frameworks/datastore/tests/system/store/writeDataHash.js +0 -5
  32. data/frameworks/sproutcore/frameworks/desktop/panes/menu.js +47 -27
  33. data/frameworks/sproutcore/frameworks/desktop/system/drag.js +5 -4
  34. data/frameworks/sproutcore/frameworks/desktop/tests/views/collection/mouse.js +23 -12
  35. data/frameworks/sproutcore/frameworks/desktop/tests/views/list/render.js +92 -0
  36. data/frameworks/sproutcore/frameworks/desktop/tests/views/select_field/methods.js +104 -53
  37. data/frameworks/sproutcore/frameworks/desktop/tests/views/select_field/ui.js +2 -0
  38. data/frameworks/sproutcore/frameworks/desktop/views/button.js +4 -3
  39. data/frameworks/sproutcore/frameworks/desktop/views/collection.js +6 -2
  40. data/frameworks/sproutcore/frameworks/desktop/views/list.js +9 -0
  41. data/frameworks/sproutcore/frameworks/desktop/views/list_item.js +2 -2
  42. data/frameworks/sproutcore/frameworks/desktop/views/menu_item.js +3 -3
  43. data/frameworks/sproutcore/frameworks/desktop/views/popup_button.js +9 -1
  44. data/frameworks/sproutcore/frameworks/desktop/views/select_field.js +80 -102
  45. data/frameworks/sproutcore/frameworks/foundation/controllers/array.js +0 -1
  46. data/frameworks/sproutcore/frameworks/foundation/english.lproj/text_field.css +5 -1
  47. data/frameworks/sproutcore/frameworks/foundation/panes/pane.js +8 -1
  48. data/frameworks/sproutcore/frameworks/foundation/private/tree_item_observer.js +0 -1
  49. data/frameworks/sproutcore/frameworks/foundation/system/datetime.js +31 -3
  50. data/frameworks/sproutcore/frameworks/foundation/system/event.js +0 -4
  51. data/frameworks/sproutcore/frameworks/foundation/system/render_context.js +3 -7
  52. data/frameworks/sproutcore/frameworks/foundation/system/request.js +3 -4
  53. data/frameworks/sproutcore/frameworks/foundation/system/user_defaults.js +78 -17
  54. data/frameworks/sproutcore/frameworks/foundation/system/utils.js +9 -0
  55. data/frameworks/sproutcore/frameworks/foundation/tests/controllers/array/single_case.js +2 -2
  56. data/frameworks/sproutcore/frameworks/foundation/tests/system/core_query/jquery_selector.js +2 -2
  57. data/frameworks/sproutcore/frameworks/foundation/tests/system/datetime.js +5 -0
  58. data/frameworks/sproutcore/frameworks/foundation/tests/system/request.js +2 -2
  59. data/frameworks/sproutcore/frameworks/foundation/tests/system/user_defaults.js +1 -4
  60. data/frameworks/sproutcore/frameworks/foundation/tests/validators/validator.js +20 -0
  61. data/frameworks/sproutcore/frameworks/foundation/tests/views/image/ui.js +13 -0
  62. data/frameworks/sproutcore/frameworks/foundation/tests/views/text_field/ui.js +132 -0
  63. data/frameworks/sproutcore/frameworks/foundation/tests/views/view/isVisibleInWindow.js +6 -3
  64. data/frameworks/sproutcore/frameworks/foundation/validators/validator.js +8 -5
  65. data/frameworks/sproutcore/frameworks/foundation/views/image.js +18 -5
  66. data/frameworks/sproutcore/frameworks/foundation/views/text_field.js +292 -21
  67. data/frameworks/sproutcore/frameworks/foundation/views/view.js +13 -14
  68. data/frameworks/sproutcore/frameworks/mini/license.js +28 -0
  69. data/frameworks/sproutcore/frameworks/runtime/core.js +35 -0
  70. data/frameworks/sproutcore/frameworks/runtime/mixins/enumerable.js +1 -1
  71. data/frameworks/sproutcore/frameworks/runtime/system/sparse_array.js +79 -5
  72. data/frameworks/sproutcore/frameworks/runtime/tests/mixins/enumerable.js +6 -6
  73. data/frameworks/sproutcore/frameworks/runtime/tests/system/sparse_array.js +53 -0
  74. data/frameworks/sproutcore/frameworks/testing/system/plan.js +4 -0
  75. data/frameworks/sproutcore/frameworks/testing/system/runner.js +1 -1
  76. data/frameworks/sproutcore/themes/standard_theme/english.lproj/radio.css +4 -0
  77. data/gen/design/Buildfile +23 -0
  78. data/gen/design/README +1 -0
  79. data/gen/design/USAGE +10 -0
  80. data/gen/design/templates/english.lproj/@filename@.js +16 -0
  81. data/gen/page/Buildfile +36 -0
  82. data/gen/page/README +1 -0
  83. data/gen/page/USAGE +15 -0
  84. data/gen/page/templates/pages/@target_name@/Buildfile +16 -0
  85. data/gen/page/templates/pages/@target_name@/core.js +22 -0
  86. data/gen/page/templates/pages/@target_name@/english.lproj/body.css +1 -0
  87. data/gen/page/templates/pages/@target_name@/english.lproj/body.rhtml +7 -0
  88. data/gen/page/templates/pages/@target_name@/english.lproj/strings.js +15 -0
  89. data/gen/view/README +1 -1
  90. data/gen/view/USAGE +5 -5
  91. data/lib/sproutcore/builders/base.rb +13 -2
  92. data/lib/sproutcore/builders/html.rb +28 -1
  93. data/lib/sproutcore/builders/minify.rb +84 -18
  94. data/lib/sproutcore/builders/test.rb +2 -1
  95. data/lib/sproutcore/helpers/entry_sorter.rb +16 -1
  96. data/lib/sproutcore/helpers/static_helper.rb +32 -4
  97. data/lib/sproutcore/helpers/tag_helper.rb +65 -0
  98. data/lib/sproutcore/models/manifest.rb +40 -6
  99. data/lib/sproutcore/models/target.rb +12 -3
  100. data/lib/sproutcore/rack/builder.rb +56 -4
  101. data/lib/sproutcore/tools/manifest.rb +1 -0
  102. data/lib/sproutcore/tools/server.rb +1 -0
  103. data/lib/sproutcore/tools.rb +21 -1
  104. data/lib/sproutcore.rb +13 -0
  105. metadata +16 -1
@@ -0,0 +1,20 @@
1
+ // ========================================================================
2
+ // SC.Validator Tests
3
+ // ========================================================================
4
+ /*globals module test ok isObj equals expects */
5
+
6
+ module("SC.Validator");
7
+
8
+ test("Calling fieldValueForObject() should not fail if this.prototypefieldValueForObject() is null", function() {
9
+ var Klass = SC.Validator.extend({
10
+ fieldValueForObject: null
11
+ });
12
+ equals(null, Klass.fieldValueForObject({}, null, null), "should return null") ;
13
+ });
14
+
15
+ test("Calling objectForFieldValue() should not fail if this.prototype.objectForFieldValue() is null", function() {
16
+ var Klass = SC.Validator.extend({
17
+ objectForFieldValue: null
18
+ });
19
+ equals(null, Klass.objectForFieldValue({}, null, null), "should return null") ;
20
+ });
@@ -35,5 +35,18 @@ htmlbody('<style> .sc-static-layout { border: 1px red dotted; } </style>');
35
35
  equals(pane.view('image_loaded').$().attr('src'), appleURL, "should be the same url");
36
36
  });
37
37
 
38
+ test("Verify that the tooltip is correctly being set as both the title and attribute (disabling localization for this test)", function() {
39
+ var imageView = pane.view('image_loaded');
40
+ var testToolTip = 'This is a test tooltip';
41
+
42
+ SC.RunLoop.begin();
43
+ imageView.set('localization', NO);
44
+ imageView.set('toolTip', testToolTip);
45
+ SC.RunLoop.end();
46
+
47
+ ok((imageView.$().attr('title') === testToolTip), "The title attribute should be set to \"" + testToolTip + "\"");
48
+ ok((imageView.$().attr('alt') === testToolTip), "The alt attribute should be set to \"" + testToolTip + "\"");
49
+ });
50
+
38
51
  })();
39
52
 
@@ -205,6 +205,138 @@ test("enabling disabled view", function() {
205
205
  });
206
206
 
207
207
 
208
+ // ..........................................................
209
+ // TEST ACCESSORY VIEWS
210
+ //
211
+
212
+ test("Adding left accessory view", function() {
213
+ var view = pane.view('with value');
214
+
215
+ // test adding accessory view adds the view like it should
216
+ SC.RunLoop.begin();
217
+ var accessoryView = SC.View.create({
218
+ layout: { top:1, left:2, width:16, height:16 }
219
+ });
220
+ view.set('leftAccessoryView', accessoryView);
221
+ SC.RunLoop.end();
222
+
223
+ ok(view.get('leftAccessoryView') === accessoryView, 'left accessory view should be set to ' + accessoryView.toString());
224
+ ok(view.get('childViews').length === 1, 'there should only be one child view');
225
+ ok(view.get('childViews')[0] === accessoryView, 'first child view should be set to ' + accessoryView.toString());
226
+
227
+
228
+ // The hint and field elements should automatically have their padding-left
229
+ // set to the accessory view's offset + width
230
+ // (18 = 2 left offset + 16 width)
231
+ var hintElement = view.$('.sc-hint')[0];
232
+ var fieldElement = view.$field()[0];
233
+ ok(hintElement.style.paddingLeft === '18px', 'hint element should get 18px padding-left');
234
+ ok(fieldElement.style.paddingLeft === '18px', 'field element should get 18px padding-left');
235
+
236
+ // Test removing the accessory view.
237
+ SC.RunLoop.begin();
238
+ view.set('leftAccessoryView', null);
239
+ SC.RunLoop.end();
240
+ ok(view.get('childViews').length === 0, 'after removing the left accessory view there should be no child views left');
241
+ ok(!hintElement.style.paddingLeft, 'after removing the left accessory view the hint element should have no padding-left style');
242
+ ok(!fieldElement.style.paddingLeft, 'after removing the left accessory view the field element should have no padding-left style');
243
+ });
244
+
245
+ test("Adding right accessory view", function() {
246
+ var view = pane.view('with value');
247
+
248
+ // test adding accessory view adds the view like it should
249
+ SC.RunLoop.begin();
250
+ var accessoryView = SC.View.create({
251
+ layout: { top:1, right:3, width:17, height:16 }
252
+ });
253
+ view.set('rightAccessoryView', accessoryView);
254
+ SC.RunLoop.end();
255
+
256
+ ok(view.get('rightAccessoryView') === accessoryView, 'right accessory view should be set to ' + accessoryView.toString());
257
+ ok(view.get('childViews').length === 1, 'there should only be one child view');
258
+ ok(view.get('childViews')[0] === accessoryView, 'first child view should be set to ' + accessoryView.toString());
259
+
260
+
261
+ // The hint and field elements should automatically have their padding-right
262
+ // set to the accessory view's offset + width
263
+ // (20 = 3 right offset + 17 width)
264
+ var hintElement = view.$('.sc-hint')[0];
265
+ var fieldElement = view.$field()[0];
266
+ ok(hintElement.style.paddingRight === '20px', 'hint element should get 20px padding-right');
267
+ ok(fieldElement.style.paddingRight === '20px', 'field element should get 20px padding-right');
268
+
269
+
270
+ // If a right accessory view is set with only 'left' (and not 'right')
271
+ // defined in its layout, 'left' should be cleared out and 'right' should
272
+ // be set to 0.
273
+ SC.RunLoop.begin();
274
+ var accessoryView = SC.View.create({
275
+ layout: { top:1, left:2, width:16, height:16 }
276
+ });
277
+ view.set('rightAccessoryView', accessoryView);
278
+ SC.RunLoop.end();
279
+
280
+ ok(view.get('rightAccessoryView').get('layout').left === null, "right accessory view created with 'left' rather than 'right' in layout should have layout.left set to null");
281
+ ok(view.get('rightAccessoryView').get('layout').right === 0, "right accessory view created with 'left' rather than 'right' in layout should have layout.right set to 0");
282
+
283
+
284
+ // Test removing the accessory view.
285
+ SC.RunLoop.begin();
286
+ view.set('rightAccessoryView', null);
287
+ SC.RunLoop.end();
288
+ ok(view.get('childViews').length === 0, 'after removing the right accessory view there should be no child views left');
289
+ ok(!hintElement.style.paddingRight, 'after removing the right accessory view the hint element should have no padding-right style');
290
+ ok(!fieldElement.style.paddingRight, 'after removing the right accessory view the field element should have no padding-right style');
291
+ });
292
+
293
+ test("Adding both left and right accessory views", function() {
294
+ var view = pane.view('with value');
295
+
296
+ // test adding accessory view adds the view like it should
297
+ SC.RunLoop.begin();
298
+ var leftAccessoryView = SC.View.create({
299
+ layout: { top:1, left:2, width:16, height:16 }
300
+ });
301
+ view.set('leftAccessoryView', leftAccessoryView);
302
+ var rightAccessoryView = SC.View.create({
303
+ layout: { top:1, right:3, width:17, height:16 }
304
+ });
305
+ view.set('rightAccessoryView', rightAccessoryView);
306
+ SC.RunLoop.end();
307
+
308
+ ok(view.get('childViews').length === 2, 'we should have two child views since we added both a left and a right accessory view');
309
+
310
+
311
+ // The hint and field elements should automatically have their padding-left
312
+ // and padding-right values set to the accessory views' offset + width
313
+ // * left: 18 = 2 left offset + 16 width)
314
+ // * right: 20 = 3 left offset + 17 width)
315
+ var hintElement = view.$('.sc-hint')[0];
316
+ var fieldElement = view.$field()[0];
317
+ ok(hintElement.style.paddingLeft === '18px', 'hint element should get 18px padding-left');
318
+ ok(fieldElement.style.paddingLeft === '18px', 'field element should get 18px padding-left');
319
+ ok(hintElement.style.paddingRight === '20px', 'hint element should get 20px padding-right');
320
+ ok(fieldElement.style.paddingRight === '20px', 'field element should get 20px padding-right');
321
+
322
+
323
+ // Test removing the accessory views.
324
+ SC.RunLoop.begin();
325
+ view.set('rightAccessoryView', null);
326
+ SC.RunLoop.end();
327
+ ok(view.get('childViews').length === 1, 'after removing the right accessory view there should be one child view left (the left accessory view)');
328
+ ok(!hintElement.style.paddingRight, 'after removing the right accessory view the hint element should have no padding-right style');
329
+ ok(!fieldElement.style.paddingRight, 'after removing the right accessory view the field element should have no padding-right style');
330
+ SC.RunLoop.begin();
331
+ view.set('leftAccessoryView', null);
332
+ SC.RunLoop.end();
333
+ ok(view.get('childViews').length === 0, 'after removing both accessory views there should be no child views left');
334
+ ok(!hintElement.style.paddingLeft, 'after removing the left accessory view the hint element should have no padding-left style');
335
+ ok(!fieldElement.style.paddingLeft, 'after removing the left accessory view the field element should have no padding-left style');
336
+ });
337
+
338
+
339
+
208
340
  // ..........................................................
209
341
  // TEST EVENTS
210
342
  //
@@ -63,19 +63,22 @@ test("removing a view from a visible pane should make it invisible again", funct
63
63
  test("updateLayer should not be invoked even if layer becomes dirty until isVisibleInWindow changes, then it should invoke", function() {
64
64
 
65
65
  var callCount = 0 ;
66
- view.updateLayer = function() { callCount++; };
66
+ view.createLayer = function() {
67
+ SC.View.prototype.createLayer.apply(this, arguments);
68
+ callCount++;
69
+ };
67
70
  ok(!view.get('isVisibleInWindow'), 'precond - view should not be visible to start');
68
71
 
69
72
  SC.RunLoop.begin();
70
73
  view.displayDidChange();
71
74
  SC.RunLoop.end();
72
- equals(callCount, 0, 'updateLayer should not run b/c its not visible');
75
+ equals(callCount, 0, 'createLayer should not run b/c its not visible');
73
76
 
74
77
  SC.RunLoop.begin();
75
78
  pane.appendChild(view); // make visible in window...
76
79
  ok(view.get('isVisibleInWindow'), 'view should now be visible in window');
77
80
  SC.RunLoop.end();
78
- equals(callCount, 1, 'updateLayer should exec now b/c isVisibleInWindow is YES');
81
+ equals(callCount, 1, 'createLayer should exec now b/c isVisibleInWindow is YES');
79
82
  });
80
83
 
81
84
  test("layoutChildViewsIfNeeded should not be invoked even if layer needs layout until isVisibleInWindow changes, then it should invoke", function() {
@@ -296,16 +296,19 @@ SC.Validator.mixin(/** @scope SC.Validator */ {
296
296
  method you define in your subclass.
297
297
  */
298
298
  fieldValueForObject: function(object, form, field) {
299
- return this.prototype.fieldValueForObject(object,form,field) ;
299
+ if (this.prototype && this.prototype.fieldValueForObject)
300
+ return this.prototype.fieldValueForObject(object,form,field) ;
301
+ else return null ;
300
302
  },
301
-
303
+
302
304
  /**
303
305
  Convenience class method to call the objectForFieldValue() instance
304
306
  method you define in your subclass.
305
307
  */
306
308
  objectForFieldValue: function(value, form, field) {
307
- return this.prototype.objectForFieldValue(value,form,field) ;
309
+ if (this.prototype && this.prototype.objectForFieldValue)
310
+ return this.prototype.objectForFieldValue(value,form,field) ;
311
+ else return null ;
308
312
  }
309
313
 
310
- }) ;
311
-
314
+ });
@@ -89,17 +89,30 @@ SC.ImageView = SC.View.extend(SC.Control,
89
89
  */
90
90
  canLoadInBackground: NO,
91
91
 
92
- displayProperties: 'status'.w(),
92
+ /**
93
+ If YES, any specified toolTip will be localized before display.
94
+ */
95
+ localize: YES,
96
+
97
+ displayProperties: 'status toolTip'.w(),
93
98
 
94
99
  render: function(context, firstTime) {
95
100
  // the image source is the value if the status is LOADED or blank
96
- var status = this.get('status'), value = this.get('value');
101
+ var status = this.get('status'), value = this.get('value') ;
97
102
 
98
103
  if (status === SC.IMAGE_STATE_NONE && value) this._image_valueDidChange() ; // setup initial state
99
104
 
100
- var src = (status === SC.IMAGE_STATE_LOADED) ? value : SC.BLANK_IMAGE_URL;
101
- if (status === SC.IMAGE_STATE_SPRITE) context.addClass(value);
102
- context.attr('src', src);
105
+ var src = (status === SC.IMAGE_STATE_LOADED) ? value : SC.BLANK_IMAGE_URL ;
106
+ if (status === SC.IMAGE_STATE_SPRITE) context.addClass(value) ;
107
+ context.attr('src', src) ;
108
+
109
+ // If there is a toolTip set, grab it and localize if necessary.
110
+ var toolTip = this.get('toolTip') ;
111
+ if (SC.typeOf(toolTip) === SC.T_STRING) {
112
+ if (this.get('localize')) toolTip = toolTip.loc() ;
113
+ context.attr('title', toolTip) ;
114
+ context.attr('alt', toolTip) ;
115
+ }
103
116
  },
104
117
 
105
118
  /** @private -
@@ -38,9 +38,68 @@ SC.TextFieldView = SC.FieldView.extend(SC.StaticLayout, SC.Editable,
38
38
  hint: null,
39
39
 
40
40
  /**
41
- If YES then the text field is currently editing.
41
+ If YES then the text field is currently editing.
42
42
  */
43
43
  isEditing: NO,
44
+
45
+ /**
46
+ An optional view instance, or view class reference, which will be visible
47
+ on the left side of the text field. Visually the accessory view will look
48
+ to be inside the field but the text editing will not overlap the accessory
49
+ view.
50
+
51
+ The view will be rooted to the top-left of the text field. You should use
52
+ a layout with 'left' and/or 'top' specified if you would like to adjust
53
+ the offset from the top-left.
54
+
55
+ One example use would be for a web site's icon, found to the left of the
56
+ URL field, in many popular web browsers.
57
+
58
+ Because the padding is set for you, it is recommended that you use the
59
+ 'border-box' box-sizing CSS declaration for your hint and field elements
60
+ if you do not want them to resize as accessory views are added and
61
+ removed.
62
+
63
+ Note: If you set a left accessory view, the left padding of the text
64
+ field will automatically be set to the width of the accessory view,
65
+ overriding any CSS padding-left you may have defined. If you would like
66
+ to customize the amount of left padding used when the accessory view is
67
+ visible, make the accessory view wider, with empty space on the right.
68
+ */
69
+ leftAccessoryView: null,
70
+
71
+ /**
72
+ An optional view instance, or view class reference, which will be visible
73
+ on the right side of the text field. Visually the accessory view will
74
+ look to be inside the field but the text editing will not overlap the
75
+ accessory view.
76
+
77
+ The view will be rooted to the top-right of the text field. You should
78
+ use a layout with 'right' and/or 'top' specified if you would like to
79
+ adjust the offset from the top-right. If 'left' is specified in the
80
+ layout it will be cleared.
81
+
82
+ One example use would be for a button to clear the contents of the text
83
+ field.
84
+
85
+ Note: If you set a right accessory view, the left padding of the text
86
+ field will automatically be set to the width of the accessory view,
87
+ overriding any CSS padding-right you may have defined. If you would like
88
+ to customize the amount of right padding used when the accessory view is
89
+ visible, make the accessory view wider, with empty space on the left.
90
+
91
+ Because the padding is set for you, it is recommended that you use the
92
+ 'border-box' box-sizing CSS declaration for your hint and field elements
93
+ if you do not want them to resize as accessory views are added and
94
+ removed.
95
+
96
+ IMPORTANT NOTE: In Internet Explorer 7 and 8, fixed-width text views do
97
+ not respect the right padding property, so if you are using a right
98
+ accessory view in such a situation be aware that it may float over the
99
+ text if the text is long enough. You may wish to use a translucent
100
+ background to somewhat mitigate this issue.
101
+ */
102
+ rightAccessoryView: null,
44
103
 
45
104
 
46
105
  /** isEditable maps to isEnabled with a TextField. */
@@ -52,44 +111,256 @@ SC.TextFieldView = SC.FieldView.extend(SC.StaticLayout, SC.Editable,
52
111
  // INTERNAL SUPPORT
53
112
  //
54
113
 
55
- displayProperties: 'hint fieldValue isEditing'.w(),
114
+ displayProperties: 'hint fieldValue isEditing leftAccessoryView rightAccessoryView'.w(),
115
+
116
+
117
+ createChildViews: function() {
118
+ this.accessoryViewObserver() ;
119
+ },
120
+
121
+
122
+ accessoryViewObserver: function() {
123
+ var viewProperties = ['leftAccessoryView', 'rightAccessoryView'] ;
124
+ var len = viewProperties.length ;
125
+ for (var i=0; i<len; i++) {
126
+ var viewProperty = viewProperties[i] ;
127
+
128
+ // Is there an accessory view specified?
129
+ var previousView = this['_'+viewProperty] ;
130
+ var accessoryView = this.get(viewProperty) ;
131
+
132
+ // If the view is the same, there's nothing to do. Otherwise, remove
133
+ // the old one (if any) and add the new one.
134
+ if (! (previousView
135
+ && accessoryView
136
+ && (previousView === accessoryView) ) ) {
137
+
138
+ // If there was a previous previous accessory view, remove it now.
139
+ if (previousView) {
140
+ // Remove the "sc-text-field-accessory-view" class name that we had
141
+ // added earlier.
142
+ var classNames = previousView.get('classNames') ;
143
+ classNames = classNames.without('sc-text-field-accessory-view') ;
144
+ previousView.set('classNames', classNames) ;
145
+ this.removeChild(previousView) ;
146
+ previousView = null ;
147
+ this['_'+viewProperty] = null ;
148
+ }
149
+
150
+ // If there's a new accessory view to add, do so now.
151
+ if (accessoryView) {
152
+ // If the user passed in a class rather than an instance, create an
153
+ // instance now.
154
+ if (accessoryView.isClass) {
155
+ accessoryView = accessoryView.create({
156
+ layoutView: this
157
+ }) ;
158
+ }
159
+
160
+ // Add in the "sc-text-field-accessory-view" class name so that the
161
+ // z-index gets set correctly.
162
+ var classNames = accessoryView.get('classNames') ;
163
+ var className = 'sc-text-field-accessory-view' ;
164
+ if (classNames.indexOf(className) < 0) {
165
+ classNames.push(className) ;
166
+ }
167
+
168
+ // Actually add the view to our hierarchy and cache a reference.
169
+ this.appendChild(accessoryView) ;
170
+ this['_'+viewProperty] = accessoryView ;
171
+ }
172
+ }
173
+ }
174
+ }.observes('leftAccessoryView', 'rightAccessoryView'),
175
+
176
+
177
+ layoutChildViewsIfNeeded: function(isVisible) {
178
+ // For the right accessory view, adjust the positioning such that the view
179
+ // is right-justified, unless 'right' is specified.
180
+ if (!isVisible) isVisible = this.get('isVisibleInWindow') ;
181
+ if (isVisible && this.get('childViewsNeedLayout')) {
182
+ var rightAccessoryView = this.get('rightAccessoryView') ;
183
+ if (rightAccessoryView && rightAccessoryView.get) {
184
+ var layout = rightAccessoryView.get('layout') ;
185
+ if (layout) {
186
+ // Clear out any 'left' value.
187
+ layout.left = null;
188
+
189
+ // Unless the user specified a 'right' value, specify a default to
190
+ // right-justify the view.
191
+ if (!layout.right) layout.right = 0 ;
192
+
193
+ rightAccessoryView.adjust({ layout: layout }) ;
194
+ }
195
+ }
196
+ }
197
+
198
+ sc_super() ;
199
+ },
200
+
56
201
 
57
202
  render: function(context, firstTime) {
203
+ sc_super() ;
58
204
 
59
205
  var disabled = this.get('isEnabled') ? '' : 'disabled="disabled"';
60
206
  var name = SC.guidFor(this);
61
- var hint = this.get('hint');
62
207
  var type = this.get('isPassword') ? 'password' : 'text';
63
208
 
64
209
  // always have at least an empty string
65
210
  var v = this.get('fieldValue');
66
- if (SC.none(v)) v = '';
211
+ if (SC.none(v)) v = '';
67
212
 
68
213
  // update layer classes always
69
- context.setClass('not-empty', v.length>0);
70
-
214
+ context.setClass('not-empty', v.length > 0);
215
+
216
+ // If we have accessory views, we'll want to update the padding on the
217
+ // hint to compensate for the width of the accessory view. (It'd be nice
218
+ // if we could add in the original padding, too, but there's no efficient
219
+ // way to do that without first rendering the element somewhere on/off-
220
+ // screen, and we don't want to take the performance hit.)
221
+ var accessoryViewWidths = this._getAccessoryViewWidths();
222
+ var leftPadding = accessoryViewWidths['left'] ;
223
+ var rightPadding = accessoryViewWidths['right'] ;
224
+ if (leftPadding) leftPadding += 'px' ;
225
+ if (rightPadding) rightPadding += 'px' ;
226
+
227
+
228
+ this._renderHint(context, firstTime, leftPadding, rightPadding);
229
+ this._renderField(context, firstTime, v, leftPadding, rightPadding);
230
+ },
231
+
232
+ _renderHint: function(context, firstTime, leftPadding, rightPadding) {
233
+ // TODO: The cleanest thing might be to create a sub- rendering context
234
+ // here, but currently SC.RenderContext will render sibling
235
+ // contexts as parent/child.
236
+ var hint = this.get('hint') ;
237
+
71
238
  if (firstTime) {
72
- context.push('<span class="sc-hint">', hint, '</span>');
73
- if(this.get('isTextArea')){
74
- context.push('<textarea name="%@" %@ value="%@"></textarea>'.fmt(name, disabled, v));
75
- }else{
76
- context.push('<input type="%@" name="%@" %@ value="%@"></input>'.fmt(type, name, disabled, v));
239
+ var paddingStyle = '' ;
240
+ if (leftPadding || rightPadding) {
241
+ paddingStyle = 'style="' ;
242
+ if (leftPadding) paddingStyle += 'padding-left: ' + leftPadding + '; ' ;
243
+ if (rightPadding) paddingStyle += 'padding-right: ' + rightPadding + ';' ;
244
+ paddingStyle += '"' ;
77
245
  }
78
- // if this is not first time rendering, update the hint itself since we
79
- // can't just blow away the text field like we might most other controls
80
- } else {
246
+ context.push('<span class="sc-hint" %@>'.fmt(paddingStyle), hint, '</span>') ;
247
+ }
248
+ else {
249
+ // If this is not first time rendering, update the hint itself since we
250
+ // can't just blow away the text field like we might most other controls
251
+ var elements = this.$('.sc-hint') ;
81
252
  if (hint !== this._textField_currentHint) {
82
253
  this._textField_currentHint = hint ;
83
- this.$('.sc-hint').text(hint);
84
- }
85
- if(!this.get('isEnabled')){
86
- this.$field()[0].disabled='true';
87
- }else{
88
- this.$field()[0].disabled=null;
254
+ elements.text(hint) ;
255
+ }
256
+
257
+ // Adjust the padding to accommodate any accessory views.
258
+ var element = elements[0] ;
259
+ if (element) {
260
+ if (leftPadding) {
261
+ if (element.style.paddingLeft !== leftPadding) {
262
+ element.style.paddingLeft = leftPadding ;
263
+ }
264
+ }
265
+ else {
266
+ element.style.paddingLeft = null ;
267
+ }
268
+
269
+ if (rightPadding) {
270
+ if (element.style.paddingRight !== rightPadding) {
271
+ element.style.paddingRight = rightPadding ;
272
+ }
273
+ }
274
+ else {
275
+ element.style.paddingRight = null ;
276
+ }
89
277
  }
90
-
91
278
  }
92
279
  },
280
+
281
+ _renderField: function(context, firstTime, value, leftPadding, rightPadding) {
282
+ // TODO: The cleanest thing might be to create a sub- rendering context
283
+ // here, but currently SC.RenderContext will render sibling
284
+ // contexts as parent/child.
285
+ if (firstTime) {
286
+ var disabled = this.get('isEnabled') ? '' : 'disabled="disabled"' ;
287
+ var name = SC.guidFor(this) ;
288
+
289
+ var paddingStyle = '' ;
290
+ if (leftPadding || rightPadding) {
291
+ paddingStyle = 'style="' ;
292
+ if (leftPadding) paddingStyle += 'padding-left: ' + leftPadding + '; ' ;
293
+ if (rightPadding) paddingStyle += 'padding-right: ' + rightPadding + ';' ;
294
+ paddingStyle += '"' ;
295
+ }
296
+
297
+ if (this.get('isTextArea')) {
298
+ context.push('<textarea name="%@" %@ value="%@" %@></textarea>'.fmt(name, disabled, value, paddingStyle)) ;
299
+ }
300
+ else {
301
+ var type = this.get('isPassword') ? 'password' : 'text' ;
302
+ context.push('<input type="%@" name="%@" %@ value="%@" %@></input>'.fmt(type, name, disabled, value, paddingStyle)) ;
303
+ }
304
+ }
305
+ else {
306
+ var element = this.$field()[0];
307
+ if (element) {
308
+ if (!this.get('isEnabled')) {
309
+ element.disabled = 'true' ;
310
+ }
311
+ else {
312
+ element.disabled = null ;
313
+ }
314
+
315
+ // Adjust the padding to accommodate any accessory views.
316
+ if (leftPadding) {
317
+ if (element.style.paddingLeft !== leftPadding) {
318
+ element.style.paddingLeft = leftPadding ;
319
+ }
320
+ }
321
+ else {
322
+ element.style.paddingLeft = null ;
323
+ }
324
+
325
+ if (rightPadding) {
326
+ if (element.style.paddingRight !== rightPadding) {
327
+ element.style.paddingRight = rightPadding ;
328
+ }
329
+ }
330
+ else {
331
+ element.style.paddingRight = null ;
332
+ }
333
+ }
334
+ }
335
+ },
336
+
337
+ _getAccessoryViewWidths: function() {
338
+ var widths = {};
339
+ var accessoryViewPositions = ['left', 'right'] ;
340
+ var numberOfAccessoryViewPositions = accessoryViewPositions.length ;
341
+ for (var i = 0; i < numberOfAccessoryViewPositions; i++) {
342
+ var position = accessoryViewPositions[i];
343
+ var accessoryView = this.get(position + 'AccessoryView');
344
+ if (accessoryView && accessoryView.get) {
345
+ var frame = accessoryView.get('frame');
346
+ if (frame) {
347
+ var width = frame.width;
348
+ if (width) {
349
+ // Also account for the accessory view's inset.
350
+ var layout = accessoryView.get('layout');
351
+ if (layout) {
352
+ var offset = layout[position];
353
+ width += offset;
354
+ }
355
+ widths[position] = width;
356
+ }
357
+ }
358
+ }
359
+ }
360
+ return widths;
361
+ },
362
+
363
+
93
364
 
94
365
  // more efficient input
95
366
  $field: function() {