uki 1.0.1 → 1.0.2

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 (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
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.0.2
data/bin/uki CHANGED
@@ -7,7 +7,7 @@ require 'commander/import'
7
7
  require File.join(UKI_ROOT, 'lib', 'uki')
8
8
 
9
9
  program :name, 'uki tools'
10
- program :version, '1.0.0'
10
+ program :version, File.read(File.join(UKI_ROOT, 'VERSION'))
11
11
  program :description, 'uki development tools'
12
12
 
13
13
 
@@ -63,6 +63,40 @@ command :'new model' do |c|
63
63
  end
64
64
  end
65
65
 
66
+ command :'new layout' do |c|
67
+ c.syntax = 'uki new layout <name>'
68
+ c.summary = 'Create uki layout'
69
+ c.description = "Create template for uki layout with given <name>
70
+ and add include to the project file"
71
+ c.example "Create myapp.layout.editor()", "uki new layout editor"
72
+ c.example "Create view in arbitary package", "uki new layout mypackage.editor"
73
+
74
+ c.when_called do |args, options|
75
+ path = args.shift or raise 'Layout name required'
76
+ project = Uki::Project.new('.')
77
+ path = "#{project.name}.layout.#{path}"
78
+ project.create_function 'layout.js', path
79
+ say "Layout #{path} created"
80
+ end
81
+ end
82
+
83
+ command :'new controller' do |c|
84
+ c.syntax = 'uki new controller <name>'
85
+ c.summary = 'Create uki controller'
86
+ c.description = "Create template for uki controller with given <name>
87
+ and add include to the project file"
88
+ c.example "Create myapp.controller.editor()", "uki new controller editor"
89
+ c.example "Create view in arbitary package", "uki new controller mypackage.editor"
90
+
91
+ c.when_called do |args, options|
92
+ path = args.shift or raise 'Controller name required'
93
+ project = Uki::Project.new('.')
94
+ path = "#{project.name}.controller.#{path}"
95
+ project.create_function 'controller.js', path
96
+ say "Controller #{path} created"
97
+ end
98
+ end
99
+
66
100
  command :run do |c|
67
101
  c.syntax = 'uki run [host[:port]]'
68
102
  c.summary = 'Run development server'
@@ -96,7 +130,24 @@ command :build do |c|
96
130
  target = args.shift || 'build'
97
131
  project = Uki::Project.new('.')
98
132
  project.build target, :compile => !option.nocompiler
99
- say "Build complete at `#{target}'"
133
+ say "Build complete at '#{target}'"
134
+ end
135
+ end
136
+
137
+ command :ie_images do |c|
138
+ c.syntax = 'uki ie_images [file]'
139
+ c.summary = 'Creates image files for IE6,7'
140
+ c.description = 'Creates image files for IE6,7 from
141
+ data urls in the theme file.
142
+
143
+ [file] defaults to <project_name>/theme.js
144
+ '
145
+ c.when_called do |args, option|
146
+ project = Uki::Project.new('.')
147
+ target = args.shift || File.join(project.name, 'theme.js')
148
+ place = project.ie_images target
149
+ say "IE images are at #{place}"
100
150
  end
151
+
101
152
  end
102
153
 
@@ -1,22 +1,64 @@
1
1
  = UKI – simple UiKit for complex Web apps
2
- Uki is a
3
- JavaScript
4
- user interface toolkit for desktop-like web applications.
5
- It comes with
6
- a rich view-component library
7
- ranging from Sliders to Grids and SplitPanes.
2
+ Uki is a JavaScript user interface toolkit for desktop-like web applications.
3
+ It comes with a rich view-component library ranging from Sliders to Grids and SplitPanes.
8
4
 
9
5
  uki({ view: 'Button', text: 'Click me', rect: '10 10 100 24' }).attachTo( window );
10
6
 
11
7
  uki('Button[text^=Click]').click(function() { alert(this.text()); });
12
8
 
9
+
10
+ == All you have to know in 5 minutes
11
+ 1. Uki interfaces are created from Views the same way web pages are created from DOM nodes.
12
+ Views even behave similar to DOM nodes. You can appendView, insertBefore, access parentView etc.
13
+ panel.appendView(button)
14
+ Examples of views: Slider, SplitPane or Table
15
+ 2. View have attributes. You can read them by calling attrname() without params and write with
16
+ attrname(newValue).
17
+ label.text('Lorem') // set text to a label
18
+ label.text() == 'Lorem' // get text
19
+ splitPane.handlePosition(300) // move the split pane handle to 300px
20
+ 3. You can create Views with uki() function.
21
+ Once created view can be attached to any block DOM container with attachTo()
22
+ uki({ view: 'Label', text: 'Lorem', ... more attributes ... }).attachTo( window )
23
+ 4. You can find attached views using css-like selectors.
24
+ uki('Label') // find all labels on page
25
+ uki('Box[name=main] > Label') // find all immediate descendant Labels in a box with name = "main"
26
+ 5. uki() calls return Collection of Views. This is similar to jQuery('expression') result. You can
27
+ access individual views with [index]. You can manipulate all views at the same
28
+ time with a number of collection methods (http://ukijs.org/docs/symbols/uki.Collection.html)
29
+ uki('Label')[3] // get 3-d found label
30
+ 6. Events are bound to Views (not individual DOM nodes) with the bind() function
31
+ uki('Label')[0].bind('click', function() { handle the event here }) // bind click to the first label
32
+ uki('Label').bind('click', function() { handle the event here }) // bind click to all labels
33
+ uki('Label').unbind('click') // unbind all click handlers from all labels
34
+ 7. Views are laid out with initial position and resize rules. Initial position is set with the rect property
35
+ button.rect('50 20 100 22') // set left = 50px, top = 20px, width = 100px, height = 22px
36
+ Resize rules are set with the anchors property:
37
+ button.anchors('left top') // stay at the left top when container resizes
38
+ button.anchors('right bottom') // move with the bottom right corner of the container
39
+ button.anchors('left top right') // stay at the top, resize width with the container
40
+ You can pass both rect and anchors to the uki() function:
41
+ uki({ view: 'Button', rect: '50 20 100 22', anchors: 'left top right' })
42
+ 8. You can change visual appearance of the views with themes. Theme is basically a collection of
43
+ resources like images, styled dom nodes and backgrounds. You can find an example of one
44
+ at http://github.com/voloko/uki/blob/master/src/uki-theme/airport.js
45
+ 9. If your adding uki to existing project then it is better to simply add
46
+ <script src='http://static.ukijs.org/pkg/0.1.3/uki.gz.js'></script>
47
+ to your pages and it will work. If you start a new one you might try
48
+ uki-tools (http://github.com/voloko/uki-tools)
49
+ 10. See the available resources in Links section and have fun
50
+
51
+
52
+
53
+
13
54
  == Links
14
55
  * API docs at http://ukijs.org/docs/
15
56
  * Google group http://groups.google.com/group/ukijs
16
- * Code examples at http://github.com/voloko/uki/tree/master/app/functional/
57
+ * Code examples at http://ukijs.org/examples/
17
58
  * Development docs and tutorial at http://wiki.github.com/voloko/uki/
18
59
  * Google wave in 100 lines of code example: http://ukijs.org/examples/core-examples/wave
19
60
 
61
+
20
62
  == Contribute
21
63
  To install development server
22
64
  1. Install ruby http://ruby-lang.org
@@ -9,7 +9,7 @@ describe 'uki.data.Model'
9
9
  end
10
10
 
11
11
  it 'should update model'
12
- model.change({ firstName: 'John', lastName: 'Smith' })
12
+ model.update({ firstName: 'John', lastName: 'Smith' })
13
13
  model.firstName.should.be 'John'
14
14
  model.lastName.should.be 'Smith'
15
15
  end
@@ -19,11 +19,22 @@ describe 'uki.data.Model'
19
19
  e.fields.should.eql ['name']
20
20
  e.changes.name.should.not.be_null
21
21
  });
22
- model.change({ name: 'something' });
22
+ model.update({ name: 'something' });
23
23
  end
24
24
 
25
- it 'should use 2 params call'
26
- model.change('name', 'John Smith')
27
- model.name.should.be 'John Smith'
25
+ it 'should allow accesor methods'
26
+ uki.data.model.addFields(model, ['firstName', 'lastName'])
27
+ model.update({ firstName: 'John', lastName: 'Smith' })
28
+ model.firstName().should.be 'John'
29
+ model.lastName().should.be 'Smith'
30
+ end
31
+
32
+ it 'should trigger change with accessor methods'
33
+ uki.data.model.addFields(model, ['firstName', 'lastName'])
34
+ model.bind('change', function(e) {
35
+ e.fields.should.eql ['firstName']
36
+ e.changes.firstName.should.be_true
37
+ });
38
+ model.firstName('something')
28
39
  end
29
40
  end
@@ -9,7 +9,7 @@ describe 'uki.dom'
9
9
 
10
10
  it 'should create stylesheets'
11
11
  x = uki.createElement('div')
12
- x.className = 'test' + uki.dom.guid++
12
+ x.className = 'test' + uki.guid++
13
13
  uki.dom.createStylesheet('.' + x.className + ' { display: inline !important; }')
14
14
  uki.dom.probe(x, function() {
15
15
  uki.dom.computedStyle(x).display.should.be 'inline'
@@ -1,5 +1,5 @@
1
1
  describe 'uki.utils'
2
- utils = uki.utils;
2
+ utils = uki;
3
3
 
4
4
  it 'should test isFunction'
5
5
  var f1 = function() {};
@@ -52,7 +52,7 @@ uki.background.Sliced9 = uki.newClass(new function() {
52
52
 
53
53
  /** @ignore */
54
54
  function img (setting, style) {
55
- return uki.imageHTML(setting[0], setting[1], setting[2], ' ondragstart="return false;" galleryimg="no" style="-webkit-user-drag:none;position:absolute;' + style + '"');
55
+ return uki.imageHTML(setting[0], setting[1], setting[2], ' ondragstart="return false;" galleryimg="no" style="-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;position:absolute;' + style + '"');
56
56
  }
57
57
 
58
58
  /** @ignore */
@@ -62,9 +62,9 @@ uki.fn = uki.Collection.prototype = new function() {
62
62
  */
63
63
  this.attr = function( name, value ) {
64
64
  if (value !== undefined) {
65
- this.each(function() {
66
- uki.attr( this, name, value );
67
- });
65
+ for (var i=0; i < this.length; i++) {
66
+ uki.attr( this[i], name, value );
67
+ };
68
68
  return this;
69
69
  } else {
70
70
  return this[0] ? uki.attr( this[0], name ) : '';
@@ -167,7 +167,7 @@ uki.fn = uki.Collection.prototype = new function() {
167
167
  @name uki.Collection#focusable */
168
168
  /** @function
169
169
  @name uki.Collection#style */
170
- uki.Collection.addAttrs('dom html text background value rect checked anchors childViews typeName id name visible disabled focusable style draggable textSelectable'.split(' '));
170
+ uki.Collection.addAttrs('dom html text background value rect checked anchors childViews typeName id name visible disabled focusable style draggable textSelectable width height minX maxX minY maxY left top x y'.split(' '));
171
171
 
172
172
  /** @function
173
173
  @name uki.Collection#parent */
@@ -267,7 +267,7 @@ uki.fn = uki.Collection.prototype = new function() {
267
267
  this.bind(name, handler);
268
268
  } else {
269
269
  for (var i=0; i < this.length; i++) {
270
- this[i].trigger(name);
270
+ this[i][name] ? this[i][name]() : this[i].trigger(name);
271
271
  };
272
272
  }
273
273
  return this;
@@ -54,6 +54,7 @@ var dragEndEvents = 'mouseup ' + (dnd.nativeDnD ? ' dragend' : '');
54
54
  // if (window.attachEvent && !window.opera) dragEndEvents += ' mouseleave';
55
55
 
56
56
  function startGesture (el) {
57
+ if (dnd.draggable) return;
57
58
  dnd.draggable = el;
58
59
  uki.dom.bind(doc, 'mousemove scroll', draggesture);
59
60
  uki.dom.bind(doc, dragEndEvents, draggestureend);
@@ -68,6 +69,7 @@ function stopGesture () {
68
69
  }
69
70
 
70
71
  function draggesturestart (e) {
72
+ e = new uki.dom.Event(e);
71
73
  e.type = 'draggesturestart';
72
74
  uki.dom.handler.apply(this, arguments);
73
75
  if (!e.isDefaultPrevented()) {
@@ -77,6 +79,7 @@ function draggesturestart (e) {
77
79
  }
78
80
 
79
81
  function draggesture (e) {
82
+ e = new uki.dom.Event(e);
80
83
  e.type = 'draggesture';
81
84
  e.dragOffset = (new Point(e.pageX, e.pageY)).offset(dnd.position);
82
85
  uki.dom.handler.apply(dnd.draggable, arguments);
@@ -84,6 +87,7 @@ function draggesture (e) {
84
87
  }
85
88
 
86
89
  function draggestureend (e) {
90
+ e = new uki.dom.Event(e);
87
91
  e.type = 'draggestureend';
88
92
  e.dragOffset = (new Point(e.pageX, e.pageY)).offset(dnd.position);
89
93
  uki.dom.handler.apply(dnd.draggable, arguments);
@@ -48,9 +48,9 @@ uki.extend(uki.dom, /** @lends uki.dom */ {
48
48
  if ( el.setInterval && el != window )
49
49
  el = window;
50
50
 
51
- listener.huid = listener.huid || uki.dom.guid++;
51
+ listener.huid = listener.huid || uki.guid++;
52
52
 
53
- var id = el[expando] = el[expando] || uki.dom.guid++,
53
+ var id = el[expando] = el[expando] || uki.guid++,
54
54
  handler = uki.dom.handlers[id] = uki.dom.handlers[id] || function() {
55
55
  uki.dom.handler.apply(arguments.callee.elem, arguments);
56
56
  },
@@ -76,13 +76,18 @@ uki.extend(uki.dom, /** @lends uki.dom */ {
76
76
 
77
77
  unbind: function(el, types, listener) {
78
78
  var id = el[expando],
79
- huid = listener.huid,
79
+ huid = listener && listener.huid,
80
80
  i, type;
81
- types = types.split(' ');
81
+ if (types) {
82
+ types = types.split(' ');
83
+ } else {
84
+ types = [];
85
+ uki.each(uki.dom.bound[id] || [], function(k, v) { types.push(k); });
86
+ }
82
87
  for (i=0; i < types.length; i++) {
83
88
  type = types[i];
84
- if (!huid || !id || !uki.dom.bound[id] || !uki.dom.bound[id][type]) continue;
85
- uki.dom.bound[id][type] = uki.grep(uki.dom.bound[id][type], function(h) { return h.huid !== huid; });
89
+ if (!id || !uki.dom.bound[id] || !uki.dom.bound[id][type]) continue;
90
+ uki.dom.bound[id][type] = listener ? uki.grep(uki.dom.bound[id][type], function(h) { return h.huid !== huid; }) : [];
86
91
 
87
92
  if (uki.dom.bound[id][type].length == 0) {
88
93
  var handler = uki.dom.handlers[id];
@@ -103,8 +108,10 @@ uki.extend(uki.dom, /** @lends uki.dom */ {
103
108
  handlers = uki.dom.bound[id],
104
109
  i;
105
110
 
106
- e = new uki.dom.Event(e);
107
- e = uki.dom.fix( e );
111
+ if (!e.domEvent) {
112
+ e = new uki.dom.Event(e);
113
+ e = uki.dom.fix( e );
114
+ }
108
115
 
109
116
  if (!id || !handlers || !handlers[type]) return;
110
117
 
@@ -178,9 +178,19 @@ include('event.js');
178
178
 
179
179
  var handler = function(e) {
180
180
  if (dnd.dataTransfer && retriggering) {
181
+ e = new uki.dom.Event(e);
181
182
  e.type = source;
182
183
  e.dataTransfer = dnd.dataTransfer;
184
+ if (source == 'dragover') {
185
+ dnd.__canDrop = false;
186
+ } else {
187
+ stopW3Cdrag(this);
188
+ if (!dnd.__canDrop) return;
189
+ }
183
190
  uki.dom.handler.apply(this, arguments);
191
+ if (e.isDefaultPrevented()) {
192
+ dnd.__canDrop = true;
193
+ }
184
194
  }
185
195
  };
186
196
 
@@ -211,6 +221,7 @@ include('event.js');
211
221
  e.dataTransfer = new uki.dom.DataTransferWrapper(dataTransfer);
212
222
  uki.dom.handler.apply(this, arguments);
213
223
  dataTransfer.effectAllowed = e.dataTransfer.effectAllowed;
224
+ dataTransfer.dropEffect = e.dataTransfer.dropEffect;
214
225
  }
215
226
 
216
227
  function startW3Cdrag (element) {
@@ -218,14 +229,16 @@ include('event.js');
218
229
  }
219
230
 
220
231
  function stopW3Cdrag (element) {
232
+ if (!dnd.dataTransfer) return;
221
233
  dnd.dataTransfer.cleanup();
222
234
  dnd.dragOver = dnd.dataTransfer = dnd.target = null;
223
235
  uki.dom.unbind( element, 'draggestureend', dragend );
224
236
  }
225
237
 
226
238
  function dragenter (e) {
227
- if (!dnd.dataTransfer || e.domEvent.__dragOver) return;
228
- e.domEvent.__dragOver = true;
239
+ if (!dnd.dataTransfer || e.domEvent.__dragEntered || !retriggering) return;
240
+ e = new uki.dom.Event(e);
241
+ e.domEvent.__dragEntered = true;
229
242
  if (dnd.dragOver == this) return;
230
243
  dnd.dragOver = this;
231
244
  e.type = 'dragenter';
@@ -234,7 +247,8 @@ include('event.js');
234
247
 
235
248
  function drag (e) {
236
249
  if (retriggering) {
237
- if (!e.domEvent.__dragOver && dnd.dragOver) {
250
+ if (!e.domEvent.__dragEntered && dnd.dragOver) {
251
+ e = new uki.dom.Event(e);
238
252
  e.type = 'dragleave';
239
253
  uki.dom.handler.apply(dnd.dragOver, arguments);
240
254
  dnd.dragOver = null;
@@ -262,6 +276,7 @@ include('event.js');
262
276
  } else {
263
277
  return;
264
278
  }
279
+ e = new uki.dom.Event(e);
265
280
  uki.dom.handler.apply(this, arguments);
266
281
  if (e.isDefaultPrevented()) {
267
282
  stopW3Cdrag(this);
@@ -8,8 +8,6 @@ include('utils.js');
8
8
  * @author voloko
9
9
  */
10
10
  uki.dom = {
11
- guid: 1,
12
-
13
11
  /**
14
12
  * Convenience wrapper around document.createElement
15
13
  * Creates dom element with given tagName, cssText and innerHTML
@@ -23,7 +21,7 @@ uki.dom = {
23
21
  var e = doc.createElement(tagName);
24
22
  if (cssText) e.style.cssText = cssText;
25
23
  if (innerHTML) e.innerHTML = innerHTML;
26
- e[expando] = uki.dom.guid++;
24
+ e[expando] = uki.guid++;
27
25
  return e;
28
26
  },
29
27
 
@@ -28,7 +28,8 @@ root.uki = root.uki || function(val, context) {
28
28
  * @type string
29
29
  * @field
30
30
  */
31
- uki.version = '0.1.2';
31
+ uki.version = '0.1.3';
32
+ uki.guid = 1;
32
33
 
33
34
  /**
34
35
  * Empty function
@@ -58,7 +58,7 @@ var utils = {
58
58
  result = function() {
59
59
  return fn.apply(context, args.concat(slice.call(arguments, 0)));
60
60
  };
61
- result.huid = fn.huid = fn.huid || uki.dom.guid++;
61
+ result.huid = fn.huid = fn.huid || uki.guid++;
62
62
  return result;
63
63
  },
64
64
 
@@ -107,7 +107,7 @@ var utils = {
107
107
  '"': '&quot;',
108
108
  "'": '&#x27;'
109
109
  };
110
- return html.replace(/[&<>\"\']/g, function(c) { return trans[c]; });
110
+ return (html + '').replace(/[&<>\"\']/g, function(c) { return trans[c]; });
111
111
  },
112
112
 
113
113
  /**
@@ -286,8 +286,9 @@ uki.view.declare('uki.view.Base', uki.view.Observable, uki.view.Styleable, funct
286
286
  if (s === undefined) return this[prop] || new Size();
287
287
  this[prop] = Size.create(s);
288
288
  this.rect(this._parentRect);
289
- if (this[prop].width) this._dom.style[name + 'Width'] = this[prop].width + PX;
290
- if (this[prop].height) this._dom.style[name + 'Height'] = this[prop].height + PX;
289
+ this._dom.style[name + 'Width'] = this[prop].width ? this[prop].width + PX : '';
290
+ this._dom.style[name + 'Height'] = this[prop].height ? this[prop].height + PX : '';
291
+ return this;
291
292
  };
292
293
  }, this);
293
294
 
@@ -426,7 +427,7 @@ uki.view.declare('uki.view.Base', uki.view.Observable, uki.view.Styleable, funct
426
427
  @name uki.view.Base#left */
427
428
  /** @function
428
429
  @name uki.view.Base#top */
429
- uki.each(['width', 'height', 'minX', 'maxX', 'minY', 'maxY', 'left', 'top'], function(index, attr) {
430
+ uki.each(['width', 'height', 'minX', 'maxX', 'minY', 'maxY', 'x', 'y', 'left', 'top'], function(index, attr) {
430
431
  this[attr] = function(value) {
431
432
  if (value === undefined) return uki.attr(this.rect(), attr);
432
433
  uki.attr(this.rect(), attr, value);
@@ -4,40 +4,48 @@ include('observable.js');
4
4
  /**
5
5
  * @class
6
6
  */
7
- uki.view.Focusable = /** @lends uki.view.Focusable.prototype */ {
7
+ uki.view.Focusable = new function() {/** @lends uki.view.Focusable.prototype */
8
+
8
9
  // dom: function() {
9
10
  // return null; // should implement
10
11
  // },
11
- _focusable: true, // default value
12
+ this._focusable = true; // default value
13
+ this._focusOnClick = true;
14
+
15
+ this.focusOnClick = uki.newProp('_focusOnClick');
12
16
 
13
- focusable: uki.newProp('_focusable', function(v) {
17
+ this.focusable = uki.newProp('_focusable', function(v) {
14
18
  this._focusable = v;
15
- this._updateTabIndex();
19
+ if (v) this._initFocusable();
20
+ this._updateFocusable();
16
21
  }),
17
22
 
18
- disabled: uki.newProp('_disabled', function(d) {
23
+ this.disabled = uki.newProp('_disabled', function(d) {
24
+ var changed = d !== !!this._disabled;
19
25
  this._disabled = d;
20
26
  if (d) this.blur();
21
- this._updateTabIndex();
22
- if (this._updateBg) this._updateBg();
27
+ this._updateFocusable();
28
+ if (changed && this._updateBg) this._updateBg();
23
29
  }),
24
30
 
25
- _updateTabIndex: function() {
26
- if (this._focusTarget) {
27
- if (this._focusable && !this._disabled) this._focusTarget.setAttribute('tabIndex', 1)
28
- else this._focusTarget.removeAttribute('tabIndex');
31
+ this._updateFocusable = function() {
32
+ if (this._preCreatedFocusTarget || !this._focusTarget) return;
33
+
34
+ if (this._focusable && !this._disabled) {
35
+ this._focusTarget.style.display = 'block';
36
+ } else {
37
+ this._focusTarget.style.display = 'none';
29
38
  }
30
39
  },
31
40
 
32
- _initFocusable: function(preCreatedInput) {
33
- this._focusTarget = preCreatedInput;
34
-
35
- if (!preCreatedInput) {
36
- this._focusTarget = root.opera ? uki.createElement('div', 'position:absolute;left:-999px;top:0;width:1px;height:1px;') : this.dom();
37
- if (root.opera) this.dom().appendChild(this._focusTarget);
38
- this._updateTabIndex();
39
- this._focusTarget.style.outline = 'none';
40
- this._focusTarget.hideFocus = true;
41
+ this._initFocusable = function(preCreatedFocusTarget) {
42
+ if ((!preCreatedFocusTarget && !this._focusable) || this._focusTarget) return;
43
+ this._focusTarget = preCreatedFocusTarget;
44
+ this._preCreatedFocusTarget = preCreatedFocusTarget;
45
+
46
+ if (!preCreatedFocusTarget) {
47
+ this._focusTarget = uki.createElement('input', 'position:absolute;left:-9999px;top:0;width:1px;height:1px;');
48
+ this.dom().appendChild(this._focusTarget);
41
49
  }
42
50
  this._hasFocus = false;
43
51
  this._firstFocus = true;
@@ -45,48 +53,60 @@ uki.view.Focusable = /** @lends uki.view.Focusable.prototype */ {
45
53
  uki.dom.bind(this._focusTarget, 'focus', uki.proxy(function(e) {
46
54
  if (!this._hasFocus) this._focus(e);
47
55
  }, this));
56
+
48
57
  uki.dom.bind(this._focusTarget, 'blur', uki.proxy(function(e) {
49
- if (this._hasFocus) this._blur(e);
58
+ if (this._hasFocus) {
59
+ this._hasFocus = false;
60
+ setTimeout(uki.proxy(function() { // wait for mousedown refocusing
61
+ if (!this._hasFocus) this._blur();
62
+ }, this), 1);
63
+ }
50
64
  }, this));
51
65
 
52
- if (!preCreatedInput) this.bind('mousedown', function(e) {
53
- setTimeout(uki.proxy(function() {
54
- try { this.focus(); } catch (e) {};
55
- }, this), 1);
66
+ if (!preCreatedFocusTarget) this.bind('mousedown', function(e) {
67
+ if (this._focusOnClick) this.focus();
56
68
  });
57
- },
69
+ this._updateFocusable();
70
+ }
58
71
 
59
- _focus: function(e) {
72
+ this._focus = function(e) {
60
73
  this._hasFocus = true;
61
74
  this._firstFocus = false;
62
- },
75
+ }
63
76
 
64
- _blur: function(e) {
77
+ this._blur = function(e) {
65
78
  this._hasFocus = false;
66
- },
79
+ }
67
80
 
68
- focus: function() {
69
- try {
70
- if (this._focusable && !this._disabled) this._focusTarget.focus();
71
- } catch(e) {}
81
+ this.focus = function() {
82
+ if (this._focusable && !this._disabled) {
83
+ if (!this._hasFocus) this._focus();
84
+ var target = this._focusTarget;
85
+ setTimeout(function() {
86
+ try {
87
+ target.focus();
88
+ } catch(e) { }
89
+ target = null;
90
+ }, 1);
91
+ }
72
92
  return this;
73
93
  },
74
94
 
75
- blur: function() {
95
+ this.blur = function() {
76
96
  try {
77
97
  this._focusTarget.blur();
78
98
  } catch(e) {}
79
99
  return this;
80
- },
100
+ }
81
101
 
82
- hasFocus: function() {
102
+ this.hasFocus = function() {
83
103
  return this._hasFocus;
84
- },
104
+ }
85
105
 
86
- _bindToDom: function(name) {
106
+ this._bindToDom = function(name) {
87
107
  if (!this._focusTarget || 'keyup keydown keypress focus blur'.indexOf(name) == -1) return false;
88
-
89
- return uki.view.Observable._bindToDom.call(this, name, this._focusableInput);
108
+
109
+ return uki.view.Observable._bindToDom.call(this, name, this._focusTarget);
90
110
  }
91
111
 
92
112
 
@@ -9,6 +9,7 @@ uki.view.Observable = /** @lends uki.view.Observable.prototype */ {
9
9
  // },
10
10
 
11
11
  bind: function(name, callback) {
12
+ callback.huid = callback.huid || uki.guid++;
12
13
  uki.each(name.split(' '), function(i, name) {
13
14
  if (!this._bound(name)) this._bindToDom(name);
14
15
  this._observersFor(name).push(callback);
@@ -18,8 +19,8 @@ uki.view.Observable = /** @lends uki.view.Observable.prototype */ {
18
19
 
19
20
  unbind: function(name, callback) {
20
21
  uki.each(name.split(' '), function(i, name) {
21
- this._observers[name] = uki.grep(this._observers[name], function(observer) {
22
- return observer != callback;
22
+ this._observers[name] = !callback ? [] : uki.grep(this._observersFor(name, true), function(observer) {
23
+ return observer != callback && observer.huid != callback.huid;
23
24
  });
24
25
  if (this._observers[name].length == 0) {
25
26
  this._unbindFromDom(name);