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
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);