uki 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/VERSION +1 -1
  2. data/bin/uki +53 -2
  3. data/frameworks/uki/README.rdoc +49 -7
  4. data/frameworks/uki/spec/unit/data/model.spec.js +16 -5
  5. data/frameworks/uki/spec/unit/dom.spec.js +1 -1
  6. data/frameworks/uki/spec/unit/utils.spec.js +1 -1
  7. data/frameworks/uki/src/uki-core/background/sliced9.js +1 -1
  8. data/frameworks/uki/src/uki-core/collection.js +5 -5
  9. data/frameworks/uki/src/uki-core/dom/dnd.js +4 -0
  10. data/frameworks/uki/src/uki-core/dom/event.js +15 -8
  11. data/frameworks/uki/src/uki-core/dom/w3cdnd.js +18 -3
  12. data/frameworks/uki/src/uki-core/dom.js +1 -3
  13. data/frameworks/uki/src/uki-core/uki.js +2 -1
  14. data/frameworks/uki/src/uki-core/utils.js +2 -2
  15. data/frameworks/uki/src/uki-core/view/base.js +4 -3
  16. data/frameworks/uki/src/uki-core/view/focusable.js +61 -41
  17. data/frameworks/uki/src/uki-core/view/observable.js +3 -2
  18. data/frameworks/uki/src/uki-core/view/styleable.js +6 -6
  19. data/frameworks/uki/src/uki-data/model.js +83 -17
  20. data/frameworks/uki/src/uki-data/observable.js +8 -5
  21. data/frameworks/uki/src/uki-more/more/view/treeList/render.js +4 -3
  22. data/frameworks/uki/src/uki-more/more/view/treeList.js +18 -7
  23. data/frameworks/uki/src/uki-view/view/checkbox.js +2 -0
  24. data/frameworks/uki/src/uki-view/view/flow.js +28 -9
  25. data/frameworks/uki/src/uki-view/view/list.js +59 -16
  26. data/frameworks/uki/src/uki-view/view/radio.js +2 -1
  27. data/frameworks/uki/src/uki-view/view/scrollPane.js +5 -6
  28. data/frameworks/uki/src/uki-view/view/slider.js +4 -4
  29. data/frameworks/uki/src/uki-view/view/splitPane.js +26 -29
  30. data/frameworks/uki/src/uki-view/view/table/column.js +6 -5
  31. data/frameworks/uki/src/uki-view/view/table/header.js +28 -18
  32. data/frameworks/uki/src/uki-view/view/table.js +41 -2
  33. data/frameworks/uki/src/uki-view/view/textField.js +2 -3
  34. data/lib/uki/project.rb +55 -15
  35. data/templates/controller.js.erb +5 -0
  36. data/templates/layout.js.erb +5 -0
  37. data/templates/myapp.js.erb +1 -2
  38. data/uki.gemspec +4 -2
  39. metadata +5 -3
@@ -33,11 +33,10 @@ uki.view.Styleable = new function() {
33
33
  // };
34
34
  // });
35
35
 
36
- var probe = uki.createElement('div').style,
37
- proto = this;
38
- uki.each(['userSelect', 'MozUserSelect', 'WebkitUserSelect'], function() {
39
- if (typeof probe[this] == 'string') proto._textSelectProp = this;
40
- });
36
+ var probe = uki.createElement('div').style;
37
+ uki.each(['userSelect', 'MozUserSelect', 'WebkitUserSelect'], function(i, name) {
38
+ if (typeof probe[name] == 'string') this._textSelectProp = name;
39
+ }, this);
41
40
 
42
41
  /**
43
42
  * Sets whether text of the view can be selected.
@@ -56,7 +55,7 @@ uki.view.Styleable = new function() {
56
55
  } else {
57
56
  uki.dom[state ? 'unbind' : 'bind'](this.dom(), 'selectstart', uki.dom.preventDefaultHandler);
58
57
  }
59
- this._dom.style.cursor = state ? 'text' : 'default';
58
+ this._dom.style.cursor = state ? '' : 'default';
60
59
  return this;
61
60
  };
62
61
 
@@ -64,6 +63,7 @@ uki.view.Styleable = new function() {
64
63
  if (state === undefined) return this._dom.getAttribute('draggable');
65
64
  this._dom.setAttribute('draggable', true);
66
65
  this._dom.style.WebkitUserDrag = 'element';
66
+ return this;
67
67
  };
68
68
 
69
69
  /**#@-*/
@@ -1,28 +1,94 @@
1
1
  include('observable.js');
2
2
 
3
+ /**
4
+ *
5
+ * @example
6
+ * myModel = uki.newClass(uki.data.Model, function() {
7
+ * uki.data.model.addField(this, ['name', 'age', 'sex']);
8
+ * })
9
+ *
10
+ * var m = new myModel({ age: 22, name: 'Jonh Smith', sex: 'm' })
11
+ * m.bind('change.sex', function() {
12
+ * alert('omg! wtf!');
13
+ * });
14
+ *
15
+ * m.bind('change', function(e) {
16
+ * console.log(e.fields);
17
+ * });
18
+ *
19
+ * m.name('Joe Black') // triggers 'change' and 'change.name'
20
+ * m.sex('w') // triggers'change' and 'change.sex'
21
+ */
3
22
  uki.data.Model = uki.newClass(uki.data.Observable, function(Observable) {
4
23
 
5
- this.change = this.update = function(values, arg2) {
6
- var changes = {}, fields = [];
7
- if (arg2 !== undefined) {
8
- var tmp = {};
9
- tmp[values] = arg2;
10
- values = tmp;
11
- }
12
- uki.each(values, function(i) {
13
- if (this[i] != values[i]) {
14
- changes[i] = true
15
- fields.push(i);
16
- this[i] = values[i];
24
+ this._fields = [];
25
+
26
+ this.init = function(values) {
27
+ this.update(values || {});
28
+ };
29
+
30
+ this.fields = function() {
31
+ return this._fields;
32
+ };
33
+
34
+ this.update = function(values) {
35
+ var fields = [], changes = {};
36
+
37
+ uki.each(values, function(name, value) {
38
+ var field = uki.isFunction(this[name]) ? '_' + name : name;
39
+ if (this[field] != value) {
40
+ changes[name] = true;
41
+ fields.push(name);
42
+ this[field] = value;
43
+ this.trigger('change.' + name, { model: this });
17
44
  }
18
45
  }, this);
19
46
 
20
- this._triggerChange({changes: changes, fields: fields, model: this});
47
+ if (fields.length) this.trigger('change', {changes: changes, fields: fields, model: this});
21
48
  return this;
22
49
  };
23
50
 
24
- this._triggerChange = function(e) {
25
- this.trigger('change', e);
26
- };
51
+ });
52
+
53
+ uki.data.model = {
54
+ _newProp: function(name) {
55
+ return uki.newProp('_' + name, function(v) {
56
+ var changes = {};
57
+ changes[name] = v;
58
+ this.update(changes);
59
+ });
60
+ },
27
61
 
28
- });
62
+ addFields: function(target, names) {
63
+ for (var i=0; i < names.length; i++) {
64
+ target[names[i]] = uki.data.model._newProp(names[i]);
65
+ }
66
+ // do not break prototypes
67
+ target._fields = names.concat(target._fields || []);
68
+ },
69
+
70
+ newLoader: function(name, options) {
71
+ return function(callback) {
72
+ callback = callback || uki.F;
73
+ if (this['loaded.' + name]) {
74
+ callback.call(this, this[name]());
75
+ } else {
76
+ this.bind('load.' + name, callback);
77
+ if (!this['loading.' + name]) {
78
+ this['loading.' + name] = true;
79
+ uki.ajax(uki.extend({
80
+ url: uki.isFunction(options.url) ? options.url.call(this) : options.url,
81
+ data: uki.isFunction(options.data) ? options.data.call(this) : options.data ? options.data : { id: this.id() },
82
+ success: uki.proxy(function(val) {
83
+ this['loading.' + name] = false;
84
+ this['loaded.' + name] = true;
85
+ options.setter ? options.setter.call(this, val) : this['_' + name] = val;
86
+ this.trigger('load.' + name, val);
87
+ this.unbind('load.' + name);
88
+ }, this)
89
+ }, options.ajaxOptions || {}));
90
+ }
91
+ }
92
+ };
93
+ }
94
+ };
@@ -5,6 +5,7 @@ include('data.js');
5
5
  uki.data.Observable = {
6
6
  bind: function(name, callback) {
7
7
  var _this = this;
8
+ callback.huid = callback.huid || uki.guid++;
8
9
  uki.each(name.split(' '), function(i, name) {
9
10
  _this._observersFor(name).push(callback);
10
11
  });
@@ -13,20 +14,22 @@ uki.data.Observable = {
13
14
  unbind: function(name, callback) {
14
15
  var _this = this;
15
16
  uki.each(name.split(' '), function(i, name) {
16
- _this._observers[name] = uki.grep(_this._observersFor(name), function(c) {
17
- return c != callback;
17
+ _this._observers[name] = !callback ? [] : uki.grep(_this._observersFor(name), function(c) {
18
+ return c != callback && c.huid != callback.huid;
18
19
  });
20
+ if (_this._observers[name]) delete _this._observers[name];
19
21
  });
20
22
  },
21
23
 
22
24
  trigger: function(name/*, data1, data2*/) {
23
25
  var attrs = Array.prototype.slice.call(arguments, 1);
24
- uki.each(this._observersFor(name), function(i, callback) {
26
+ uki.each(this._observersFor(name, true), function(i, callback) {
25
27
  callback.apply(this, attrs);
26
- });
28
+ }, this);
27
29
  },
28
30
 
29
- _observersFor: function(name) {
31
+ _observersFor: function(name, skipCreate) {
32
+ if (skipCreate && (!this._observers || !this._observers[name])) return [];
30
33
  if (!this._observers) this._observers = {};
31
34
  if (!this._observers[name]) this._observers[name] = [];
32
35
  return this._observers[name];
@@ -11,7 +11,7 @@ uki.more.view.treeList.Render = uki.newClass(uki.view.list.Render, new function(
11
11
  );
12
12
 
13
13
  this.initStyles = function() {
14
- this.classPrefix = 'treeList-' + uki.dom.guid++;
14
+ this.classPrefix = 'treeList-' + uki.guid++;
15
15
  var style = new uki.theme.Template(
16
16
  '.${classPrefix}-row { color: #333; position:relative; padding-top:3px; } ' +
17
17
  '.${classPrefix}-toggle { overflow: hidden; position:absolute; left:-15px; top:5px; width: 10px; height:9px; } ' +
@@ -30,8 +30,9 @@ uki.more.view.treeList.Render = uki.newClass(uki.view.list.Render, new function(
30
30
 
31
31
  this.render = function(row, rect, i) {
32
32
  this.classPrefix || this.initStyles();
33
- var text = row.data;
34
- if (row.children && row.children.length) {
33
+ var text = row.data,
34
+ children = uki.attr(row, 'children');
35
+ if (children && children.length) {
35
36
  return this._parentTemplate.render({
36
37
  text: text,
37
38
  indent: row.__indent*18 + 22,
@@ -13,7 +13,16 @@ uki.view.declare('uki.more.view.TreeList', uki.view.List, function(Base) {
13
13
 
14
14
  this.data = uki.newProp('_treeData', function(v) {
15
15
  this._treeData = v;
16
- this.listData(this._treeNodeToListData(v));
16
+ this._data = this._treeNodeToListData(v);
17
+ var children = this.listData(), opened = false;
18
+ for (var i=children.length - 1; i >= 0 ; i--) {
19
+ if (this._data[i].__opened) {
20
+ opened = true;
21
+ this._openSubElement(i);
22
+ }
23
+ };
24
+ this.listData(this._data);
25
+ if (opened) this.trigger('open');
17
26
  });
18
27
 
19
28
  this._treeNodeToListData = function(node, indent) {
@@ -35,7 +44,7 @@ uki.view.declare('uki.more.view.TreeList', uki.view.List, function(Base) {
35
44
  }
36
45
 
37
46
  function recursiveLength (item) {
38
- var children = item.children,
47
+ var children = uki.attr(item, 'children'),
39
48
  length = children.length;
40
49
 
41
50
  for (var i=0; i < children.length; i++) {
@@ -46,8 +55,8 @@ uki.view.declare('uki.more.view.TreeList', uki.view.List, function(Base) {
46
55
 
47
56
  this._openSubElement = function(index) {
48
57
  var item = this._data[index],
49
- children = item.children;
50
-
58
+ children = uki.attr(item, 'children');
59
+
51
60
  if (!children || !children.length) return 0;
52
61
  var length = children.length;
53
62
 
@@ -80,13 +89,14 @@ uki.view.declare('uki.more.view.TreeList', uki.view.List, function(Base) {
80
89
  this.listData(this._data);
81
90
  this.selectedIndexes(indexes);
82
91
  this._lastClickIndex = clickIndex > index ? clickIndex + length : clickIndex;
92
+ this.trigger('open');
83
93
  return this;
84
94
  };
85
95
 
86
96
  this.close = function(index) {
87
97
  var item = this._data[index],
88
98
  indexes = this._selectedIndexes,
89
- children = item.children;
99
+ children = uki.attr(item, 'children');
90
100
  if (!children || !children.length || !item.__opened) return;
91
101
 
92
102
  var length = recursiveLength(item);
@@ -109,12 +119,13 @@ uki.view.declare('uki.more.view.TreeList', uki.view.List, function(Base) {
109
119
  this.listData(this._data);
110
120
  this.selectedIndexes(indexes);
111
121
  this._lastClickIndex = clickIndex > index ? clickIndex - length : clickIndex;
122
+ this.trigger('close');
112
123
  };
113
124
 
114
125
  this._mousedown = function(e) {
115
- if (e.domEvent.target.className.indexOf('toggle-tree') > -1) {
126
+ if (e.target.className.indexOf('toggle-tree') > -1) {
116
127
  var o = uki.dom.offset(this._dom),
117
- y = e.domEvent.pageY - o.y,
128
+ y = e.pageY - o.y,
118
129
  p = y / this._rowHeight << 0;
119
130
  this.toggle(p);
120
131
  } else {
@@ -23,8 +23,10 @@ uki.view.declare('uki.view.Checkbox', uki.view.Button, function(Base) {
23
23
  };
24
24
 
25
25
  this.value = this.checked = uki.newProp('_checked', function(state) {
26
+ var changed = this._checked != !!state;
26
27
  this._checked = !!state;
27
28
  this._updateBg();
29
+ if (changed) this.trigger('change', {checked: this._checked, source: this});
28
30
  });
29
31
 
30
32
  this._mouseup = function(e) {
@@ -8,23 +8,41 @@ uki.view.declare('uki.view.VFlow', uki.view.Container, function(Base) {
8
8
 
9
9
  this.hidePartlyVisible = uki.newProp('_hidePartlyVisible');
10
10
 
11
- this._layoutChildViews = function(oldRect) {
11
+ this.resizeToContents = function(autosizeStr) {
12
+ this._resizeChildViews(this._rect);
13
+ return Base.resizeToContents.call(this, autosizeStr);
14
+ }
15
+
16
+ uki.each(['appendChild', 'removeChild', 'insertBefore'], function(i, name) {
17
+ this[name] = function(arg1, arg2) {
18
+ this._contentChanged = true;
19
+ return Base[name].call(this, arg1, arg2);
20
+ };
21
+ }, this)
22
+
23
+ this.layout = function() {
24
+ if (this._contentChanged) this._resizeChildViews(this._rect);
25
+ return Base.layout.call(this);
26
+ };
27
+
28
+ // resize in layout
29
+ this._resizeChildViews = function(oldRect) {
30
+ this._contentChanged = false;
12
31
  var offset = 0, rect, view;
13
32
  for (var i=0, childViews = this.childViews(); i < childViews.length; i++) {
14
33
  view = childViews[i];
15
- view.rect(new Rect(view._rect.x, offset, view._rect.width, view._rect.height));
34
+ view.parentResized(oldRect, this._rect);
35
+ view.rect().y = offset;
36
+ // view.rect(new Rect(view._rect.x, offset, view._rect.width, view._rect.height));
16
37
  if (this._hidePartlyVisible) {
17
38
  view.visible(view._rect.height + offset <= this._rect.height);
18
39
  }
19
40
  if (view.visible()) offset += view._rect.height;
20
41
  };
21
- Base._layoutChildViews.call(this);
22
42
  };
23
43
  });
24
44
 
25
- uki.view.declare('uki.view.HFlow', uki.view.Container, function(Base) {
26
- this.hidePartlyVisible = uki.newProp('_hidePartlyVisible');
27
-
45
+ uki.view.declare('uki.view.HFlow', uki.view.VFlow, function(Base) {
28
46
  this.contentsSize = function() {
29
47
  var value = uki.reduce(0, this._childViews, function(sum, e) {
30
48
  return sum + (e.visible() ? e.rect().width : 0);
@@ -32,17 +50,18 @@ uki.view.declare('uki.view.HFlow', uki.view.Container, function(Base) {
32
50
  return new Size( value, this.contentsHeight() );
33
51
  };
34
52
 
35
- this._layoutChildViews = function(oldRect) {
53
+ this._resizeChildViews = function(oldRect) {
36
54
  var offset = 0, rect, view;
37
55
  for (var i=0, childViews = this.childViews(); i < childViews.length; i++) {
38
56
  view = childViews[i];
39
- view.rect(new Rect(offset, view._rect.y, view._rect.width, view._rect.height));
57
+ view.parentResized(oldRect, this._rect);
58
+ view.rect().x = offset;
59
+ // view.rect(new Rect(offset, view._rect.y, view._rect.width, view._rect.height));
40
60
  if (this._hidePartlyVisible) {
41
61
  view.visible(view._rect.width + offset <= this._rect.width);
42
62
  }
43
63
  if (view.visible()) offset += view._rect.width;
44
64
  };
45
- Base._layoutChildViews.call(this);
46
65
  };
47
66
 
48
67
  });
@@ -3,7 +3,7 @@ include('scrollPane.js');
3
3
  uki.view.list = {};
4
4
  uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Base, Focusable) {
5
5
 
6
- this._throttle = 5; // do not try to render more often than every 5ms
6
+ this._throttle = 42; // do not try to render more often than every 42ms
7
7
  this._visibleRectExt = 300; // extend visible rect by 300 px overflow
8
8
  this._defaultBackground = 'theme(list)';
9
9
 
@@ -22,13 +22,15 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
22
22
  return uki.theme.background('list', this._rowHeight);
23
23
  };
24
24
 
25
- uki.addProps(this, ['render', 'packSize', 'visibleRectExt', 'throttle', 'contentDraggable', 'lastClickIndex', 'multiselect']);
25
+ uki.addProps(this, ['render', 'packSize', 'visibleRectExt', 'throttle', 'contentDraggable', 'lastClickIndex', 'multiselect', 'lastClickIndex']);
26
26
 
27
27
  this.rowHeight = uki.newProp('_rowHeight', function(val) {
28
28
  this._rowHeight = val;
29
+ this.minSize(new Size(this.minSize().width, this._rowHeight * this._data.length));
29
30
  if (this._background) this._background.detach();
30
31
  this._background = null;
31
32
  if (this.background()) this.background().attachTo(this);
33
+ this._relayoutParent();
32
34
  });
33
35
 
34
36
  this.data = function(d) {
@@ -37,8 +39,8 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
37
39
  this._data = d;
38
40
  this._packs[0].itemFrom = this._packs[0].itemTo = this._packs[1].itemFrom = this._packs[1].itemTo = 0;
39
41
 
40
- var minWidth = this._minSize ? this._minSize.width : 0;
41
- this.minSize(new Size(minWidth, this._rowHeight * this._data.length));
42
+ this.minSize(new Size(this.minSize().width, this._rowHeight * this._data.length));
43
+ this.trigger('selection', {source: this})
42
44
  this._relayoutParent();
43
45
  return this;
44
46
  };
@@ -48,16 +50,51 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
48
50
  this.layout();
49
51
  };
50
52
 
53
+ this.contentsSize = function() {
54
+ return new Size(this.rect().width, this._rowHeight * this._data.length);
55
+ };
56
+
57
+ // used in search. should be fast
51
58
  this.addRow = function(position, data) {
52
- this.clearSelection();
53
59
  this._data.splice(position, 0, data);
54
- this.data(this._data);
60
+ var item = this._itemAt(position);
61
+ if (item) {
62
+ var container = doc.createElement('div');
63
+
64
+ container.innerHTML = this._rowTemplate.render({
65
+ height: this._rowHeight,
66
+ text: this._render.render(this._data[position], this._rowRect(position), position)
67
+ });
68
+ item.parentNode.insertBefore(container.firstChild, item);
69
+ if (position < this._packs[0].itemTo) {
70
+ this._packs[0].itemTo++;
71
+ this._packs[1].itemFrom++;
72
+ this._packs[1].itemTo++;
73
+ this._packs[1].dom.style.top = this._packs[1].itemFrom*this._rowHeight + 'px';
74
+ } else {
75
+ this._packs[1].itemTo++;
76
+ }
77
+ }
78
+
79
+ // offset selection
80
+ var selectionPosition = uki.binarySearch(position, this.selectedIndexes());
81
+ for (var i = selectionPosition; i < this._selectedIndexes.length; i++) {
82
+ this._selectedIndexes[i]++;
83
+ };
84
+
85
+ return this;
55
86
  };
56
87
 
57
88
  this.removeRow = function(position, data) {
58
- this.clearSelection();
59
89
  this._data.splice(position, 1);
60
90
  this.data(this._data);
91
+ return this;
92
+ };
93
+
94
+ this.redrawRow = function(row) {
95
+ var item = this._itemAt(row);
96
+ if (item) item.innerHTML = this._render.render(this._data[row], this._rowRect(row), row);
97
+ return this;
61
98
  };
62
99
 
63
100
  this.selectedIndex = function(position) {
@@ -69,12 +106,13 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
69
106
 
70
107
  this.selectedIndexes = function(indexes) {
71
108
  if (indexes === undefined) return this._selectedIndexes;
109
+ var changed = indexes != this._selectedIndexes;
72
110
  this.clearSelection(true);
73
111
  this._selectedIndexes = indexes;
74
112
  for (var i=0; i < this._selectedIndexes.length; i++) {
75
113
  this._setSelected(this._selectedIndexes[i], true);
76
114
  };
77
- this.trigger('selection', {source: this});
115
+ if (changed) this.trigger('selection', {source: this});
78
116
  return this;
79
117
  };
80
118
 
@@ -120,6 +158,10 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
120
158
  if (p > initialP) array.splice(initialP, p - initialP);
121
159
  }
122
160
 
161
+ this._rowRect = function(p) {
162
+ return new Rect(0, p*this._rowHeight, this.rect().width, this._rowHeight);
163
+ };
164
+
123
165
  this._toggleSelection = function(p) {
124
166
  var indexes = [].concat(this._selectedIndexes);
125
167
  var addTo = uki.binarySearch(p, indexes);
@@ -222,6 +264,9 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
222
264
  } else if (e.which == 40 || e.keyCode == 40) { // DOWN
223
265
  nextIndex = Math.min(this._data.length-1, this._lastClickIndex + 1);
224
266
  e.preventDefault();
267
+ } else if (this._multiselect && (e.which == 97 || e.which == 65) && e.metaKey) {
268
+ e.preventDefault();
269
+ this.selectedIndexes(range(0, this._data.length -1));
225
270
  }
226
271
  if (nextIndex > -1 && nextIndex != this._lastClickIndex) {
227
272
  if (e.shiftKey && this._multiselect) {
@@ -230,6 +275,7 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
230
275
  } else {
231
276
  this._toggleSelection(nextIndex);
232
277
  }
278
+ this._scrollToPosition(nextIndex);
233
279
  } else {
234
280
  this.selectedIndex(nextIndex);
235
281
  }
@@ -292,14 +338,12 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
292
338
  this._rowTemplate = new uki.theme.Template('<div style="width:100%;height:${height}px;overflow:hidden;">${text}</div>')
293
339
 
294
340
  this._renderPack = function(pack, itemFrom, itemTo) {
295
- var html = [], position,
296
- rect = new Rect(0, itemFrom*this._rowHeight, this.rect().width, this._rowHeight);
341
+ var html = [], position;
297
342
  for (i=itemFrom; i < itemTo; i++) {
298
343
  html[html.length] = this._rowTemplate.render({
299
344
  height: this._rowHeight,
300
- text: this._render.render(this._data[i], rect, i)
345
+ text: this._render.render(this._data[i], this._rowRect(i), i)
301
346
  });
302
- rect.y += this._rowHeight;
303
347
  };
304
348
  pack.dom.innerHTML = html.join('');
305
349
  pack.itemFrom = itemFrom;
@@ -344,7 +388,7 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
344
388
  scrollableParent = this._scrollableParent;
345
389
 
346
390
  this._visibleRect = uki.view.visibleRect(this, scrollableParent);
347
-
391
+ if (this._focusTarget) this._focusTarget.style.top = this._visibleRect.y + 'px';
348
392
  var prefferedPackSize = CEIL((this._visibleRect.height + this._visibleRectExt*2) / this._rowHeight),
349
393
 
350
394
  minVisibleY = MAX(0, this._visibleRect.y - this._visibleRectExt),
@@ -355,7 +399,6 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
355
399
  itemFrom, itemTo, startAt, updated = true;
356
400
 
357
401
  Base._layoutDom.call(this, rect);
358
-
359
402
  if (
360
403
  maxVisibleY <= minRenderedY || minVisibleY >= maxRenderedY || // both packs below/above visible area
361
404
  (maxVisibleY > maxRenderedY && this._packs[1].itemFrom * this._rowHeight > this._visibleRect.y && this._packs[1].itemTo > this._packs[1].itemFrom) || // need to render below, and pack 2 is not enough to cover
@@ -396,7 +439,7 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
396
439
  } else {
397
440
  updated = false;
398
441
  }
399
- if (updated && /MSIE 7/.test(ua)) this.dom().className += '';
442
+ if (updated && /MSIE 6|7/.test(ua)) this.dom().className += '';
400
443
  };
401
444
 
402
445
  this._bindToDom = function(name) {
@@ -419,7 +462,7 @@ uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Ba
419
462
 
420
463
  });
421
464
 
422
- uki.Collection.addAttrs(['data','selectedIndex']);
465
+ uki.Collection.addAttrs(['data','selectedIndex', 'selectedIndexes', 'selectedRows']);
423
466
 
424
467
  uki.view.declare('uki.view.ScrollableList', uki.view.ScrollPane, function(Base) {
425
468
 
@@ -13,9 +13,11 @@ include('checkbox.js');
13
13
  });
14
14
 
15
15
  this.value = this.checked = uki.newProp('_checked', function(state) {
16
+ var changed = this._checked != !!state;
16
17
  this._checked = !!state;
17
18
  if (state) manager.clearGroup(this);
18
19
  this._updateBg();
20
+ if (changed) this.trigger('change', {checked: this._checked, source: this});
19
21
  });
20
22
 
21
23
  this._mouseup = function() {
@@ -23,7 +25,6 @@ include('checkbox.js');
23
25
  this._down = false;
24
26
  if (!this._checked && !this._disabled) {
25
27
  this.checked(!this._checked);
26
- this.trigger('change', {checked: this._checked, source: this});
27
28
  }
28
29
  }
29
30
  });
@@ -52,12 +52,6 @@
52
52
  if (dy) this.scrollTop(this.scrollTop() + dy);
53
53
  };
54
54
 
55
- uki.each(['appendChild', 'removeChild', 'insertBefore'], function(i, name) {
56
- this[name] = function(arg1, arg2) {
57
- return Base[name].call(this, arg1, arg2);
58
- };
59
- }, this);
60
-
61
55
  uki.each(['scrollTop', 'scrollLeft'], function(i, name) {
62
56
  this[name] = function(v) {
63
57
  if (v == undefined) return this._dom[name];
@@ -86,6 +80,11 @@
86
80
  this.trigger('resize', {oldRect: oldRect, newRect: this._rect, source: this});
87
81
  return this;
88
82
  };
83
+
84
+ this._createDom = function() {
85
+ Base._createDom.call(this);
86
+ if (ua.indexOf('Gecko/') > -1) this._dom.tabIndex = '-1';
87
+ };
89
88
 
90
89
  this._recalcClientRects = function() {
91
90
  initScrollWidth();
@@ -71,7 +71,7 @@ uki.view.declare('uki.view.Slider', uki.view.Base, uki.view.Focusable, function(
71
71
  }, this);
72
72
 
73
73
  this.bind('click', this._click);
74
- this.bind('keydown', this._keydown);
74
+ this.bind(uki.view.List.prototype.keyPressEvent(), this._keypress);
75
75
  };
76
76
 
77
77
  this._mouseenter = function() {
@@ -90,10 +90,10 @@ uki.view.declare('uki.view.Slider', uki.view.Base, uki.view.Focusable, function(
90
90
  this._cachedIndex = undefined;
91
91
  };
92
92
 
93
- this._keydown = function(e) {
94
- if (e.which == 39) {
93
+ this._keypress = function(e) {
94
+ if (e.which == 39 || e.keyCode == 39) {
95
95
  this.value(this.value() + this._keyStep * (this._max - this._min));
96
- } else if (e.which == 37) {
96
+ } else if (e.which == 37 || e.keyCode == 37) {
97
97
  this.value(this.value() - this._keyStep * (this._max - this._min));
98
98
  }
99
99
  };